Skip to content

Commit 73c7851

Browse files
committed
Extension deletion tooltip
1 parent 4c23b62 commit 73c7851

File tree

3 files changed

+95
-45
lines changed

3 files changed

+95
-45
lines changed

app/src/main/kotlin/com/w2sv/filenavigator/ui/screen/navigatorsettings/NavigatorSettingsScreen.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import com.w2sv.filenavigator.ui.designsystem.Padding
6060
import com.w2sv.filenavigator.ui.designsystem.SnackbarKind
6161
import com.w2sv.filenavigator.ui.screen.navigatorsettings.components.AddFileTypesBottomSheet
6262
import com.w2sv.filenavigator.ui.screen.navigatorsettings.components.AutoMoveIntroductionDialogIfNotYetShown
63+
import com.w2sv.filenavigator.ui.screen.navigatorsettings.components.FileTypeCreationDialog
6364
import com.w2sv.filenavigator.ui.screen.navigatorsettings.components.NavigatorConfigurationColumn
6465
import com.w2sv.filenavigator.ui.theme.AppTheme
6566
import com.w2sv.filenavigator.ui.util.Easing
@@ -104,6 +105,9 @@ fun NavigatorSettingsScreen(
104105
AutoMoveIntroductionDialogIfNotYetShown()
105106

106107
var showAddFileTypesBottomSheet by rememberSaveable {
108+
mutableStateOf(false)
109+
}
110+
var showFileTypeCreationDialog by rememberSaveable {
107111
mutableStateOf(true)
108112
}
109113

@@ -173,7 +177,13 @@ fun NavigatorSettingsScreen(
173177
}
174178
}
175179
},
176-
onDismissRequest = remember { { showAddFileTypesBottomSheet = false } }
180+
onDismissRequest = remember { { showAddFileTypesBottomSheet = false } },
181+
onCreateFileTypeCardClick = remember { { showFileTypeCreationDialog = true } }
182+
)
183+
}
184+
if (showFileTypeCreationDialog) {
185+
FileTypeCreationDialog(
186+
onDismissRequest = { showFileTypeCreationDialog = false }
177187
)
178188
}
179189
}

app/src/main/kotlin/com/w2sv/filenavigator/ui/screen/navigatorsettings/components/AddFileTypesBottomSheet.kt

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,9 @@ import androidx.compose.material3.Text
3030
import androidx.compose.material3.rememberModalBottomSheetState
3131
import androidx.compose.runtime.Composable
3232
import androidx.compose.runtime.Stable
33-
import androidx.compose.runtime.getValue
3433
import androidx.compose.runtime.mutableStateMapOf
35-
import androidx.compose.runtime.mutableStateOf
3634
import androidx.compose.runtime.remember
3735
import androidx.compose.runtime.rememberCoroutineScope
38-
import androidx.compose.runtime.saveable.rememberSaveable
39-
import androidx.compose.runtime.setValue
4036
import androidx.compose.runtime.snapshots.SnapshotStateList
4137
import androidx.compose.runtime.snapshots.SnapshotStateMap
4238
import androidx.compose.runtime.toMutableStateList
@@ -67,6 +63,7 @@ fun AddFileTypesBottomSheet(
6763
disabledFileTypes: ImmutableList<FileType>,
6864
addFileTypes: (List<FileType>) -> Unit,
6965
onDismissRequest: () -> Unit,
66+
onCreateFileTypeCardClick: () -> Unit,
7067
modifier: Modifier = Modifier,
7168
sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
7269
scope: CoroutineScope = rememberCoroutineScope()
@@ -79,8 +76,6 @@ fun AddFileTypesBottomSheet(
7976
)
8077
}
8178

82-
var showFileTypeCreationDialog by rememberSaveable { mutableStateOf(true) }
83-
8479
ModalBottomSheet(
8580
onDismissRequest = onDismissRequest,
8681
sheetState = sheetState,
@@ -109,7 +104,7 @@ fun AddFileTypesBottomSheet(
109104
}
110105
item {
111106
CreateFileTypeCard(
112-
onClick = { showFileTypeCreationDialog = true },
107+
onClick = onCreateFileTypeCardClick,
113108
modifier = Modifier
114109
.padding(bottom = 12.dp)
115110
)
@@ -139,12 +134,6 @@ fun AddFileTypesBottomSheet(
139134
)
140135
Spacer(modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBarsIgnoringVisibility))
141136
}
142-
143-
if (showFileTypeCreationDialog) {
144-
FileTypeCreationDialog(
145-
onDismissRequest = { showFileTypeCreationDialog = false }
146-
)
147-
}
148137
}
149138

150139
@Stable

app/src/main/kotlin/com/w2sv/filenavigator/ui/screen/navigatorsettings/components/FileTypeCreationDialog.kt

Lines changed: 82 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.w2sv.filenavigator.ui.screen.navigatorsettings.components
22

3+
import android.annotation.SuppressLint
4+
import androidx.compose.foundation.clickable
35
import androidx.compose.foundation.gestures.detectTapGestures
46
import androidx.compose.foundation.layout.Arrangement
57
import androidx.compose.foundation.layout.Column
@@ -10,15 +12,22 @@ import androidx.compose.foundation.layout.height
1012
import androidx.compose.foundation.layout.padding
1113
import androidx.compose.foundation.layout.width
1214
import androidx.compose.material.icons.Icons
13-
import androidx.compose.material.icons.filled.Check
15+
import androidx.compose.material.icons.filled.Add
16+
import androidx.compose.material.icons.filled.Delete
1417
import androidx.compose.material.icons.outlined.Warning
1518
import androidx.compose.material3.AlertDialog
1619
import androidx.compose.material3.Badge
20+
import androidx.compose.material3.FilledTonalIconButton
1721
import androidx.compose.material3.Icon
1822
import androidx.compose.material3.IconButton
1923
import androidx.compose.material3.MaterialTheme
2024
import androidx.compose.material3.OutlinedTextField
25+
import androidx.compose.material3.PlainTooltip
2126
import androidx.compose.material3.Text
27+
import androidx.compose.material3.TooltipBox
28+
import androidx.compose.material3.TooltipDefaults
29+
import androidx.compose.material3.TooltipScope
30+
import androidx.compose.material3.TooltipState
2231
import androidx.compose.runtime.Composable
2332
import androidx.compose.runtime.Stable
2433
import androidx.compose.runtime.derivedStateOf
@@ -40,6 +49,7 @@ import com.w2sv.kotlinutils.coroutines.flow.emit
4049
import kotlinx.coroutines.CoroutineScope
4150
import kotlinx.coroutines.flow.MutableSharedFlow
4251
import kotlinx.coroutines.flow.asSharedFlow
52+
import kotlinx.coroutines.launch
4353

4454
private enum class FileExtensionInvalidityReason(val errorMessage: String) {
4555
ContainsSpecialCharacter("Extension must not contain special characters"),
@@ -55,7 +65,11 @@ private class CustomFileType(private val scope: CoroutineScope) {
5565
name = value.trim().replaceFirstChar(Char::titlecase)
5666
}
5767

58-
val fileExtensions = mutableStateListOf<String>()
68+
val extensions = mutableStateListOf<String>()
69+
70+
fun deleteExtension(index: Int) {
71+
extensions.removeAt(index)
72+
}
5973

6074
var newFileExtension by mutableStateOf("")
6175
private set
@@ -67,18 +81,18 @@ private class CustomFileType(private val scope: CoroutineScope) {
6781
val newFileExtensionInvalidityReason by derivedStateOf {
6882
when {
6983
newFileExtension.any { !it.isLetterOrDigit() } -> FileExtensionInvalidityReason.ContainsSpecialCharacter
70-
fileExtensions.contains(newFileExtension) -> FileExtensionInvalidityReason.AlreadyAmongstFileExtensions
84+
extensions.contains(newFileExtension) -> FileExtensionInvalidityReason.AlreadyAmongstFileExtensions
7185
else -> null
7286
}
7387
}
7488
val newFileExtensionCanBeAdded by derivedStateOf { newFileExtensionInvalidityReason == null && newFileExtension.isNotBlank() }
7589

7690
fun addNewFileExtension() {
77-
fileExtensions.add(newFileExtension)
91+
extensions.add(newFileExtension)
7892
newFileExtension = ""
7993
}
8094

81-
val canBeCreated by derivedStateOf { name.isNotEmpty() && fileExtensions.isNotEmpty() }
95+
val canBeCreated by derivedStateOf { name.isNotEmpty() && extensions.isNotEmpty() }
8296

8397
val clearFocus get() = _clearFocus.asSharedFlow()
8498
private val _clearFocus = MutableSharedFlow<Unit>()
@@ -114,35 +128,15 @@ private fun StatelessFileTypeCreationDialog(customFileType: CustomFileType, onDi
114128
placeholder = { Text("Enter name") },
115129
singleLine = true
116130
)
117-
// Text(
118-
// text = "File Extensions",
119-
// modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
120-
// style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium)
121-
// )
122-
// LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 32.dp), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
123-
// items(customFileType.fileExtensions) {
124-
// if (it.isNotEmpty()) {
125-
// Badge {
126-
// Text(it)
127-
// }
128-
// }
129-
// }
130-
// }
131131
FileExtensionTextField(
132132
customFileType = customFileType,
133133
modifier = Modifier
134134
.width(192.dp)
135135
.padding(vertical = 16.dp)
136136
)
137137
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
138-
customFileType.fileExtensions.forEachIndexed { i, el ->
139-
Badge(containerColor = MaterialTheme.colorScheme.secondaryContainer) {
140-
Text(
141-
text = el,
142-
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
143-
style = MaterialTheme.typography.labelLarge
144-
)
145-
}
138+
customFileType.extensions.forEachIndexed { i, extension ->
139+
FileExtensionBadgeWithTooltip(extension = extension, deleteExtension = { customFileType.deleteExtension(i) })
146140
}
147141
}
148142
}
@@ -151,6 +145,59 @@ private fun StatelessFileTypeCreationDialog(customFileType: CustomFileType, onDi
151145
)
152146
}
153147

148+
@Composable
149+
private fun FileExtensionBadgeWithTooltip(
150+
extension: String,
151+
deleteExtension: () -> Unit,
152+
modifier: Modifier = Modifier,
153+
scope: CoroutineScope = rememberCoroutineScope()
154+
) {
155+
val tooltipState = remember { TooltipState() }
156+
157+
TooltipBox(
158+
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
159+
tooltip = {
160+
FileExtensionDeletionTooltip(
161+
onClick = {
162+
deleteExtension()
163+
tooltipState.dismiss()
164+
}
165+
)
166+
},
167+
state = tooltipState,
168+
modifier = modifier
169+
) {
170+
FileExtensionBadge(extension, modifier = Modifier.clickable { scope.launch { tooltipState.show() } })
171+
}
172+
}
173+
174+
@SuppressLint("ComposeUnstableReceiver")
175+
@Composable
176+
private fun TooltipScope.FileExtensionDeletionTooltip(onClick: () -> Unit, modifier: Modifier = Modifier) {
177+
PlainTooltip(caretSize = TooltipDefaults.caretSize, tonalElevation = 4.dp, shadowElevation = 4.dp, modifier = modifier) {
178+
IconButton(onClick = onClick) {
179+
Icon(
180+
imageVector = Icons.Default.Delete,
181+
contentDescription = "Delete file extension",
182+
)
183+
}
184+
}
185+
}
186+
187+
@Composable
188+
private fun FileExtensionBadge(extension: String, modifier: Modifier = Modifier) {
189+
Badge(
190+
containerColor = MaterialTheme.colorScheme.secondaryContainer,
191+
modifier = modifier
192+
) {
193+
Text(
194+
text = extension,
195+
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
196+
style = MaterialTheme.typography.bodyLarge
197+
)
198+
}
199+
}
200+
154201
@Composable
155202
private fun FileExtensionTextField(customFileType: CustomFileType, modifier: Modifier = Modifier) {
156203
OutlinedTextField(
@@ -163,8 +210,8 @@ private fun FileExtensionTextField(customFileType: CustomFileType, modifier: Mod
163210
trailingIcon = when {
164211
customFileType.newFileExtensionCanBeAdded -> {
165212
{
166-
IconButton(onClick = customFileType::addNewFileExtension) {
167-
Icon(Icons.Default.Check, contentDescription = null, tint = AppColor.success)
213+
FilledTonalIconButton(onClick = customFileType::addNewFileExtension) {
214+
Icon(Icons.Default.Add, contentDescription = null, tint = AppColor.success)
168215
}
169216
}
170217
}
@@ -196,8 +243,12 @@ private fun FileExtensionTextField(customFileType: CustomFileType, modifier: Mod
196243
private fun StatelessFileTypeCreationDialogPrev() {
197244
AppTheme {
198245
StatelessFileTypeCreationDialog(
199-
CustomFileType(rememberCoroutineScope()).apply { fileExtensions.addAll(listOf("jpg", "png", "jpg", "jpg", "jpgasdf")) },
200-
{}
246+
customFileType = CustomFileType(rememberCoroutineScope())
247+
.apply {
248+
extensions.addAll(listOf("jpg", "png", "jpgasdf"))
249+
updateNewFileExtension("dot")
250+
},
251+
onDismissRequest = {}
201252
)
202253
}
203254
}

0 commit comments

Comments
 (0)