Long press tag to edit

This commit is contained in:
MM20 2024-10-20 21:42:02 +02:00
parent 4f5e80dede
commit 52bba7f5f2
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
4 changed files with 244 additions and 136 deletions

View File

@ -2,9 +2,9 @@ package de.mm20.launcher2.ui.common
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.EnterExitState
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.core.animateFloat
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -54,70 +54,142 @@ fun FavoritesTagSelector(
) {
val sheetManager = LocalBottomSheetManager.current
AnimatedContent(
modifier = Modifier
.fillMaxWidth()
.padding(
top = if (reverse) 8.dp else 4.dp,
bottom = if (reverse) 4.dp else 8.dp,
end = if (editButton) 8.dp else 0.dp
),
targetState = expanded,
) {
if (!it) {
val canScroll by remember {
derivedStateOf { scrollState.canScrollForward || scrollState.canScrollBackward }
}
Row {
Row(
AnimatedContent(
modifier = Modifier
.fillMaxWidth()
.padding(
top = if (reverse) 8.dp else 4.dp,
bottom = if (reverse) 4.dp else 8.dp,
end = if (editButton) 8.dp else 0.dp
),
targetState = expanded,
) {
if (!it) {
val canScroll by remember {
derivedStateOf { scrollState.canScrollForward || scrollState.canScrollBackward }
}
Row {
Row(
modifier = Modifier
.weight(1f)
.consumeAllScrolling()
.horizontalScroll(scrollState)
.padding(end = 12.dp),
) {
FilterChip(
modifier = Modifier
.weight(1f)
.consumeAllScrolling()
.horizontalScroll(scrollState)
.padding(end = 12.dp),
) {
FilterChip(
modifier = Modifier
.padding(start = 16.dp),
selected = selectedTag == null,
onClick = { onSelectTag(null) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize),
)
},
label = { Text(stringResource(R.string.favorites)) }
)
for (tag in tags) {
TagChip(
modifier = Modifier
.padding(start = 8.dp),
tag = tag,
selected = selectedTag == tag.tag,
onClick = {
if (selectedTag == tag.tag) {
onSelectTag(null)
} else {
onSelectTag(tag.tag)
}
},
.padding(start = 16.dp),
selected = selectedTag == null,
onClick = { onSelectTag(null) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize),
)
}
if (canScroll) {
val rot by transition.animateFloat {
if (it == EnterExitState.Visible) 0f else 180f
}
IconButton(
modifier = Modifier
.rotate(rot),
onClick = { onExpand(true) }) {
Icon(Icons.Rounded.ExpandMore, null)
},
label = { Text(stringResource(R.string.favorites)) }
)
for (tag in tags) {
TagChip(
modifier = Modifier
.padding(start = 8.dp),
tag = tag,
selected = selectedTag == tag.tag,
onClick = {
if (selectedTag == tag.tag) {
onSelectTag(null)
} else {
onSelectTag(tag.tag)
}
},
onLongClick = {
sheetManager.showEditTagSheet(tag.tag)
}
)
}
if (canScroll) {
val rot by transition.animateFloat {
if (it == EnterExitState.Visible) 0f else 180f
}
IconButton(
modifier = Modifier
.rotate(rot),
onClick = { onExpand(true) }) {
Icon(Icons.Rounded.ExpandMore, null)
}
}
}
if (editButton) {
SmallFloatingActionButton(
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(),
onClick = { sheetManager.showEditFavoritesSheet() }
) {
Icon(
imageVector = Icons.Rounded.Edit,
contentDescription = null
)
}
}
}
} else {
Row(
verticalAlignment = if (reverse) Alignment.Top else Alignment.Bottom,
) {
FlowRow(
modifier = Modifier
.weight(1f)
.padding(end = 12.dp, start = 16.dp),
) {
FilterChip(
modifier = Modifier
.padding(end = 8.dp),
selected = selectedTag == null,
onClick = { onSelectTag(null) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize),
)
},
label = { Text(stringResource(R.string.favorites)) }
)
for (tag in tags) {
TagChip(
modifier = Modifier
.padding(end = 8.dp),
tag = tag,
selected = selectedTag == tag.tag,
onClick = {
if (selectedTag == tag.tag) {
onSelectTag(null)
} else {
onSelectTag(tag.tag)
}
},
onLongClick = {
sheetManager.showEditTagSheet(tag.tag)
}
)
}
}
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = if (reverse) Arrangement.TopReversed else Arrangement.Bottom,
) {
val rot by transition.animateFloat {
if (it == EnterExitState.Visible) 0f else 180f
}
IconButton(
modifier = Modifier
.rotate(rot),
onClick = { onExpand(false) }
) {
Icon(Icons.Rounded.ExpandLess, null)
}
if (editButton) {
@ -132,75 +204,8 @@ fun FavoritesTagSelector(
}
}
}
} else {
Row(
verticalAlignment = if (reverse) Alignment.Top else Alignment.Bottom,
) {
FlowRow(
modifier = Modifier
.weight(1f)
.padding(end = 12.dp, start = 16.dp),
) {
FilterChip(
modifier = Modifier
.padding(end = 8.dp),
selected = selectedTag == null,
onClick = { onSelectTag(null) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize),
)
},
label = { Text(stringResource(R.string.favorites)) }
)
for (tag in tags) {
TagChip(
modifier = Modifier
.padding(end = 8.dp),
tag = tag,
selected = selectedTag == tag.tag,
onClick = {
if (selectedTag == tag.tag) {
onSelectTag(null)
} else {
onSelectTag(tag.tag)
}
},
)
}
}
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = if (reverse) Arrangement.TopReversed else Arrangement.Bottom,
) {
val rot by transition.animateFloat {
if (it == EnterExitState.Visible) 0f else 180f
}
IconButton(
modifier = Modifier
.rotate(rot),
onClick = { onExpand(false) }
) {
Icon(Icons.Rounded.ExpandLess, null)
}
if (editButton) {
SmallFloatingActionButton(
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(),
onClick = { sheetManager.showEditFavoritesSheet() }
) {
Icon(
imageVector = Icons.Rounded.Edit,
contentDescription = null
)
}
}
}
}
}
}
}
}

View File

@ -1,21 +1,36 @@
package de.mm20.launcher2.ui.common
import androidx.compose.animation.animateColor
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.Tag
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SelectableChipColors
import androidx.compose.material3.SelectableChipElevation
import androidx.compose.material3.Text
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.data.Tag
import de.mm20.launcher2.ui.ktx.splitLeadingEmoji
@ -25,6 +40,7 @@ fun TagChip(
tag: Tag,
selected: Boolean = false,
onClick: () -> Unit = {},
onLongClick: (() -> Unit)? = null,
clearable: Boolean = false,
onClear: (() -> Unit)? = null,
colors: SelectableChipColors = FilterChipDefaults.filterChipColors(),
@ -34,7 +50,76 @@ fun TagChip(
tag.tag.splitLeadingEmoji()
}
FilterChip(
val shape = MaterialTheme.shapes.small
val transition = updateTransition(selected)
val backgroundColor by transition.animateColor {
if (it) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surfaceContainerLow
}
val borderColor by transition.animateColor {
if (it) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.outlineVariant
}
val borderWidth by transition.animateDp {
if (it) 0.dp else 1.dp
}
val textColor by transition.animateColor {
if (it) MaterialTheme.colorScheme.onSecondaryContainer else MaterialTheme.colorScheme.onSurfaceVariant
}
val iconColor by transition.animateColor {
if (it) MaterialTheme.colorScheme.onSecondaryContainer else MaterialTheme.colorScheme.primary
}
Row(
modifier = modifier
.minimumInteractiveComponentSize()
.height(32.dp)
.background(backgroundColor, shape)
.border(borderWidth, borderColor, shape)
.clip(shape)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick
)
.padding(horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (emoji != null && tagName != null) {
Text(
emoji,
modifier = Modifier.width(FilterChipDefaults.IconSize),
textAlign = TextAlign.Center,
)
} else {
Icon(
modifier = Modifier
.size(FilterChipDefaults.IconSize),
imageVector = Icons.Rounded.Tag,
contentDescription = null,
tint = iconColor
)
}
Text(
tagName ?: emoji ?: "",
style = MaterialTheme.typography.labelLarge,
color = textColor,
modifier = Modifier.padding(horizontal = 8.dp)
)
if (clearable) {
Icon(
modifier = Modifier
.clickable {
onClear?.invoke()
}
.size(FilterChipDefaults.IconSize),
imageVector = Icons.Rounded.Close,
contentDescription = null,
)
}
}
/*FilterChip(
modifier = modifier,
selected = selected,
onClick = onClick,
@ -63,13 +148,15 @@ fun TagChip(
trailingIcon = if (clearable) {
{
Icon(
modifier = Modifier.clickable {
onClear?.invoke()
}.size(FilterChipDefaults.IconSize),
modifier = Modifier
.clickable {
onClear?.invoke()
}
.size(FilterChipDefaults.IconSize),
imageVector = Icons.Rounded.Close,
contentDescription = null,
)
}
} else null
)
)*/
}

View File

@ -15,6 +15,7 @@ class LauncherBottomSheetManager(registryOwner: SavedStateRegistryOwner) :
val customizeSearchableSheetShown = mutableStateOf<SavableSearchable?>(null)
val editFavoritesSheetShown = mutableStateOf(false)
val hiddenItemsSheetShown = mutableStateOf(false)
val editTagSheetShown = mutableStateOf<String?>(null)
init {
registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
@ -25,8 +26,9 @@ class LauncherBottomSheetManager(registryOwner: SavedStateRegistryOwner) :
val state = registry.consumeRestoredStateForKey(PROVIDER)
editFavoritesSheetShown.value = state?.getBoolean(FAVORITES) ?: false
hiddenItemsSheetShown.value = state?.getBoolean(HIDDEN) ?: false
editFavoritesSheetShown.value = state?.getBoolean(FAVORITES) == true
hiddenItemsSheetShown.value = state?.getBoolean(HIDDEN) == true
editTagSheetShown.value = state?.getString(TAG)
}
})
}
@ -35,6 +37,7 @@ class LauncherBottomSheetManager(registryOwner: SavedStateRegistryOwner) :
return bundleOf(
FAVORITES to editFavoritesSheetShown.value,
HIDDEN to hiddenItemsSheetShown.value,
TAG to editTagSheetShown.value,
)
}
@ -62,10 +65,19 @@ class LauncherBottomSheetManager(registryOwner: SavedStateRegistryOwner) :
hiddenItemsSheetShown.value = false
}
fun showEditTagSheet(tagName: String) {
editTagSheetShown.value = tagName
}
fun dismissEditTagSheet() {
editTagSheetShown.value = null
}
companion object {
private const val PROVIDER = "bottom_sheet_manager"
private const val FAVORITES = "favorites"
private const val HIDDEN = "hidden"
private const val TAG = "tag"
private const val WIDGETS = "widgets"
}

View File

@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.launcher.sheets
import androidx.compose.runtime.Composable
import de.mm20.launcher2.ui.settings.tags.EditTagSheet
@Composable
fun LauncherBottomSheets() {
@ -13,4 +14,7 @@ fun LauncherBottomSheets() {
if (bottomSheetManager.editFavoritesSheetShown.value) {
EditFavoritesSheet(onDismiss = { bottomSheetManager.dismissEditFavoritesSheet() })
}
bottomSheetManager.editTagSheetShown.value?.let {
EditTagSheet(tag = it, onDismiss = { bottomSheetManager.dismissEditTagSheet() })
}
}