From 52bba7f5f2af10ee3f6fa27c2d5a60fa0898b570 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 20 Oct 2024 21:42:02 +0200 Subject: [PATCH] Long press tag to edit --- .../ui/common/FavoritesTagSelector.kt | 261 +++++++++--------- .../de/mm20/launcher2/ui/common/TagChip.kt | 99 ++++++- .../sheets/LauncherBottomSheetManager.kt | 16 +- .../launcher/sheets/LauncherBottomSheets.kt | 4 + 4 files changed, 244 insertions(+), 136 deletions(-) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesTagSelector.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesTagSelector.kt index 05363103..59550404 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesTagSelector.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesTagSelector.kt @@ -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 - ) - } - } - } - } } } } +} diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/TagChip.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/TagChip.kt index cc02b6bc..364f3dd1 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/TagChip.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/TagChip.kt @@ -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 - ) + )*/ } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/LauncherBottomSheetManager.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/LauncherBottomSheetManager.kt index 91f342fd..13bc5375 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/LauncherBottomSheetManager.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/LauncherBottomSheetManager.kt @@ -15,6 +15,7 @@ class LauncherBottomSheetManager(registryOwner: SavedStateRegistryOwner) : val customizeSearchableSheetShown = mutableStateOf(null) val editFavoritesSheetShown = mutableStateOf(false) val hiddenItemsSheetShown = mutableStateOf(false) + val editTagSheetShown = mutableStateOf(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" } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/LauncherBottomSheets.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/LauncherBottomSheets.kt index b11117c8..9f139329 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/LauncherBottomSheets.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/LauncherBottomSheets.kt @@ -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() }) + } } \ No newline at end of file