From 024a646b1acbcf39a45b6d6356a36dc50f73fbca Mon Sep 17 00:00:00 2001 From: leekleak <142348248+leekleak@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:57:32 +0200 Subject: [PATCH] Standardize bottom sheets (#1231) * move to prebuilt bottom sheet * improve searchable editing * fix too much space above widget picker sheet search bar * improve restore/backup sheets * import theme and location picker scaffolds * improve clock widget settings icons * continue migration * remove search action delete button * force maximise tag edit sheet * add background to icon pick button * accomodate long button name --- .../de/mm20/launcher2/ui/common/IconPicker.kt | 217 +++++---- .../launcher2/ui/common/ImportThemeSheet.kt | 31 +- .../launcher2/ui/common/RestoreBackupSheet.kt | 59 ++- .../launcher2/ui/common/SearchablePicker.kt | 3 +- .../ui/common/WeatherLocationSearchDialog.kt | 74 +-- .../ui/component/BottomSheetDialog.kt | 368 +-------------- .../launcher/sheets/ConfigureWidgetSheet.kt | 12 +- .../sheets/CustomizeSearchableSheet.kt | 432 ++++++++---------- .../sheets/CustomizeSearchableSheetVM.kt | 1 - .../ui/launcher/sheets/EditFavoritesSheet.kt | 24 +- .../launcher/sheets/EditFavoritesSheetVM.kt | 2 + .../ui/launcher/sheets/EditTagSheet.kt | 171 +++---- .../ui/launcher/sheets/EditTagSheetVM.kt | 4 +- .../ui/launcher/sheets/FailedGestureSheet.kt | 1 - .../ui/launcher/sheets/HiddenItemsSheet.kt | 34 +- .../ui/launcher/sheets/WidgetPickerSheet.kt | 10 +- .../ui/launcher/widgets/clock/ClockWidget.kt | 15 +- .../ui/launcher/widgets/notes/NotesWidget.kt | 87 ++-- .../ui/settings/backup/CreateBackupSheet.kt | 91 +--- .../gestures/GestureSettingsScreen.kt | 1 - .../searchactions/EditSearchActionSheet.kt | 59 +-- .../SearchActionsSettingsScreen.kt | 3 - core/i18n/src/main/res/values/strings.xml | 1 + 23 files changed, 591 insertions(+), 1109 deletions(-) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/IconPicker.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/IconPicker.kt index a664e1c2..67b0225c 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/IconPicker.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/IconPicker.kt @@ -4,6 +4,7 @@ import android.content.pm.PackageManager import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -22,7 +23,6 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Text @@ -79,7 +79,7 @@ fun IconPicker( var showIconPackFilter by remember { mutableStateOf(false) } val installedIconPacks by viewModel.installedIconPacks.collectAsState(null) - val noPacksInstalled = installedIconPacks?.isEmpty() == true + val packsInstalled = installedIconPacks?.isEmpty() == false val columns = LocalGridSettings.current.columnCount @@ -88,42 +88,38 @@ fun IconPicker( columns = GridCells.Fixed(columns), contentPadding = contentPadding, ) { - - item(span = { GridItemSpan(columns) }) { - SearchBar( - modifier = Modifier.padding(bottom = 16.dp), - expanded = false, - onExpandedChange = {}, - inputField = { - SearchBarDefaults.InputField( - enabled = !noPacksInstalled, - leadingIcon = { - Icon( - imageVector = Icons.Rounded.Search, - contentDescription = null - ) - }, - onSearch = {}, - expanded = false, - onExpandedChange = {}, - placeholder = { - Text( - stringResource( - if (noPacksInstalled) R.string.icon_picker_no_packs_installed else R.string.icon_picker_search_icon + if (packsInstalled) { + item(span = { GridItemSpan(columns) }) { + SearchBar( + windowInsets = WindowInsets(0.dp), + expanded = false, + onExpandedChange = {}, + inputField = { + SearchBarDefaults.InputField( + leadingIcon = { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = null ) - ) - }, - query = query, - onQueryChange = { - query = it - scope.launch { - viewModel.searchIcon(query, filterIconPack) - } - }, - ) - } - ) { + }, + onSearch = {}, + expanded = false, + onExpandedChange = {}, + placeholder = { + Text(stringResource(R.string.icon_picker_search_icon)) + }, + query = query, + onQueryChange = { + query = it + scope.launch { + viewModel.searchIcon(query, filterIconPack) + } + }, + ) + } + ) { + } } } @@ -138,11 +134,11 @@ fun IconPicker( }) } } - item(span = { GridItemSpan(columns) }) { - Separator(stringResource(R.string.icon_picker_suggestions)) - } if (suggestions.isNotEmpty()) { + item(span = { GridItemSpan(columns) }) { + Separator(stringResource(R.string.icon_picker_suggestions)) + } items(suggestions) { IconPreview( it, @@ -152,87 +148,82 @@ fun IconPicker( } } } else { - - if (!installedIconPacks.isNullOrEmpty()) { - item( - span = { GridItemSpan(columns) }, + item(span = { GridItemSpan(columns) }) { + Button( + onClick = { showIconPackFilter = !showIconPackFilter }, + modifier = Modifier + .wrapContentWidth(align = Alignment.CenterHorizontally) + .padding(16.dp), + contentPadding = PaddingValues( + horizontal = 16.dp, + vertical = 8.dp + ) ) { - Button( - onClick = { showIconPackFilter = !showIconPackFilter }, - modifier = Modifier - .wrapContentWidth(align = Alignment.CenterHorizontally) - .padding(bottom = 16.dp), - contentPadding = PaddingValues( - horizontal = 16.dp, - vertical = 8.dp - ) - ) { - if (filterIconPack == null) { - Icon( - modifier = Modifier - .padding(end = ButtonDefaults.IconSpacing) - .size(ButtonDefaults.IconSize), - imageVector = Icons.Rounded.FilterAlt, - contentDescription = null - ) - } else { - val icon = remember(filterIconPack?.packageName) { - try { - filterIconPack?.packageName?.let { pkg -> - context.packageManager.getApplicationIcon(pkg) - } - } catch (e: PackageManager.NameNotFoundException) { - null - } - } - AsyncImage( - modifier = Modifier - .padding(end = ButtonDefaults.IconSpacing) - .size(ButtonDefaults.IconSize), - model = icon, - contentDescription = null - ) - } - DropdownMenu( - expanded = showIconPackFilter, - onDismissRequest = { showIconPackFilter = false }) { - DropdownMenuItem( - text = { Text(stringResource(id = R.string.icon_picker_filter_all_packs)) }, - onClick = { - showIconPackFilter = false - filterIconPack = null - scope.launch { - viewModel.searchIcon(query, filterIconPack) - } - } - ) - installedIconPacks?.forEach { iconPack -> - DropdownMenuItem( - onClick = { - showIconPackFilter = false - filterIconPack = iconPack - scope.launch { - viewModel.searchIcon(query, filterIconPack) - } - }, - text = { - Text(iconPack.name) - }) - } - } - Text( - text = filterIconPack?.name - ?: stringResource(id = R.string.icon_picker_filter_all_packs), - modifier = Modifier.animateContentSize() - ) + if (filterIconPack == null) { Icon( - Icons.Rounded.ArrowDropDown, modifier = Modifier - .padding(start = ButtonDefaults.IconSpacing) + .padding(end = ButtonDefaults.IconSpacing) .size(ButtonDefaults.IconSize), + imageVector = Icons.Rounded.FilterAlt, + contentDescription = null + ) + } else { + val icon = remember(filterIconPack?.packageName) { + try { + filterIconPack?.packageName?.let { pkg -> + context.packageManager.getApplicationIcon(pkg) + } + } catch (e: PackageManager.NameNotFoundException) { + null + } + } + AsyncImage( + modifier = Modifier + .padding(end = ButtonDefaults.IconSpacing) + .size(ButtonDefaults.IconSize), + model = icon, contentDescription = null ) } + DropdownMenu( + expanded = showIconPackFilter, + onDismissRequest = { showIconPackFilter = false }) { + DropdownMenuItem( + text = { Text(stringResource(id = R.string.icon_picker_filter_all_packs)) }, + onClick = { + showIconPackFilter = false + filterIconPack = null + scope.launch { + viewModel.searchIcon(query, filterIconPack) + } + } + ) + installedIconPacks?.forEach { iconPack -> + DropdownMenuItem( + onClick = { + showIconPackFilter = false + filterIconPack = iconPack + scope.launch { + viewModel.searchIcon(query, filterIconPack) + } + }, + text = { + Text(iconPack.name) + }) + } + } + Text( + text = filterIconPack?.name + ?: stringResource(id = R.string.icon_picker_filter_all_packs), + modifier = Modifier.animateContentSize() + ) + Icon( + Icons.Rounded.ArrowDropDown, + modifier = Modifier + .padding(start = ButtonDefaults.IconSpacing) + .size(ButtonDefaults.IconSize), + contentDescription = null + ) } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheet.kt index dc78584e..de72b888 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheet.kt @@ -12,8 +12,6 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.DarkMode import androidx.compose.material.icons.rounded.ErrorOutline @@ -69,21 +67,7 @@ fun ImportThemeSheet( val error by viewModel.error var apply by viewModel.apply - BottomSheetDialog( - onDismissRequest = onDismiss, - confirmButton = if (theme != null && !error) { - { - Button( - onClick = { - viewModel.import() - onDismiss() - } - ) { - Text(stringResource(R.string.action_import)) - } - } - } else null, - ) { + BottomSheetDialog(onDismiss) { if (theme == null && !error) { Box( modifier = Modifier @@ -111,8 +95,8 @@ fun ImportThemeSheet( Column( modifier = Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()) - .padding(it) + .padding(it), + horizontalAlignment = Alignment.End ) { ThemePreview( theme!!, @@ -132,6 +116,15 @@ fun ImportThemeSheet( apply = it }) } + Button( + modifier = Modifier.padding(top = 8.dp), + onClick = { + viewModel.import() + onDismiss() + }, + ) { + Text(stringResource(R.string.action_import)) + } } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt index d86909fb..9028780d 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt @@ -2,12 +2,22 @@ package de.mm20.launcher2.ui.common import android.net.Uri import android.text.format.DateUtils -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.* -import androidx.compose.material3.* +import androidx.compose.material.icons.rounded.CheckCircleOutline +import androidx.compose.material.icons.rounded.ErrorOutline +import androidx.compose.material.icons.rounded.Info +import androidx.compose.material.icons.rounded.Warning +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -37,35 +47,12 @@ fun RestoreBackupSheet( val state by viewModel.state val compatibility by viewModel.compatibility - BottomSheetDialog( - onDismissRequest = onDismissRequest, - title = { - Text( - stringResource(id = R.string.preference_restore), - ) - }, - confirmButton = { - - if (state == RestoreBackupState.Ready && compatibility != BackupCompatibility.Incompatible) { - Button( - onClick = { viewModel.restore() }) { - Text(stringResource(R.string.preference_restore)) - } - } else if (state == RestoreBackupState.InvalidFile || state == RestoreBackupState.Restored || state == RestoreBackupState.Ready) { - OutlinedButton( - onClick = onDismissRequest - ) { - Text(stringResource(R.string.close)) - } - } - }, - ) { - Box( + BottomSheetDialog(onDismissRequest) { + Column ( modifier = Modifier .fillMaxWidth() - .wrapContentHeight() - .verticalScroll(rememberScrollState()) - .padding(it) + .padding(it), + horizontalAlignment = Alignment.End, ) { when (state) { RestoreBackupState.Parsing -> { @@ -159,6 +146,14 @@ fun RestoreBackupSheet( ) } } + + if (state == RestoreBackupState.Ready && compatibility != BackupCompatibility.Incompatible) { + Button( + onClick = { viewModel.restore() } + ) { + Text(stringResource(R.string.preference_restore)) + } + } } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/SearchablePicker.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/SearchablePicker.kt index fd10fd00..f783dc53 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/SearchablePicker.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/SearchablePicker.kt @@ -36,12 +36,11 @@ import de.mm20.launcher2.ui.ktx.toPixels fun SearchablePicker( value: SavableSearchable?, onValueChanged: (SavableSearchable?) -> Unit, - title: @Composable () -> Unit, onDismissRequest: () -> Unit, ) { val viewModel: SearchablePickerVM = viewModel() - BottomSheetDialog(onDismissRequest = onDismissRequest, title = title) { + BottomSheetDialog(onDismissRequest = onDismissRequest) { Column( modifier = Modifier .fillMaxSize() diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialog.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialog.kt index ef5cb76c..1595cba8 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialog.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialog.kt @@ -2,7 +2,7 @@ package de.mm20.launcher2.ui.common import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -11,8 +11,17 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Error import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -32,38 +41,39 @@ fun WeatherLocationSearchDialog( val isSearching by viewModel.isSearchingLocation val locations by viewModel.locationResults - BottomSheetDialog( - onDismissRequest = onDismissRequest, - title = { - Text( - text = stringResource(R.string.preference_location), - ) - }, - ) { + BottomSheetDialog(onDismissRequest) { var query by remember { mutableStateOf("") } Column( - modifier = Modifier.fillMaxSize().padding(it) + modifier = Modifier + .fillMaxSize() + .padding(it) ) { - Row( - Modifier.padding(bottom = 16.dp) - ) { - OutlinedTextField( - singleLine = true, - value = query, - textStyle = MaterialTheme.typography.bodyLarge, - onValueChange = { - query = it - scope.launch { - viewModel.searchLocation(it) - } - }, - leadingIcon = { - Icon(imageVector = Icons.Rounded.Search, contentDescription = null) - }, - modifier = Modifier - .weight(1f) - ) - } + SearchBar( + modifier = Modifier.padding(bottom = 8.dp), + windowInsets = WindowInsets(0.dp), + expanded = false, + onExpandedChange = {}, + inputField = { + SearchBarDefaults.InputField( + leadingIcon = { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = null + ) + }, + onSearch = {}, + expanded = false, + onExpandedChange = {}, + query = query, + onQueryChange = { + query = it + scope.launch { + viewModel.searchLocation(it) + } + }, + ) + } + ) {} if (isSearching) { CircularProgressIndicator( modifier = Modifier diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/BottomSheetDialog.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/BottomSheetDialog.kt index a96e4648..99896260 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/BottomSheetDialog.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/BottomSheetDialog.kt @@ -1,374 +1,42 @@ package de.mm20.launcher2.ui.component -import android.util.Log -import androidx.activity.compose.BackHandler -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.exponentialDecay -import androidx.compose.animation.core.spring -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.AnchoredDraggableState -import androidx.compose.foundation.gestures.DraggableAnchors -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.anchoredDraggable -import androidx.compose.foundation.gestures.animateTo -import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.material3.BottomSheetDefaults -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.LocalAbsoluteTonalElevation -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.SheetState +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntRect -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.PopupPositionProvider -import de.mm20.launcher2.ui.ktx.toPixels -import de.mm20.launcher2.ui.overlays.LocalZIndex -import de.mm20.launcher2.ui.overlays.Overlay -import kotlinx.coroutines.launch -import kotlin.math.min -import kotlin.math.roundToInt @Composable fun BottomSheetDialog( onDismissRequest: () -> Unit, - title: (@Composable () -> Unit)? = null, - actions: (@Composable RowScope.() -> Unit)? = null, - confirmButton: @Composable (() -> Unit)? = null, - dismissButton: @Composable (() -> Unit)? = null, - dismissible: () -> Boolean = { true }, - zIndex: Float = LocalZIndex.current + 1f, + footerItems: @Composable (() -> Unit)? = null, + bottomSheetState: SheetState = rememberModalBottomSheetState(), content: @Composable (paddingValues: PaddingValues) -> Unit, ) { - val scope = rememberCoroutineScope() - - val focusManager = LocalFocusManager.current - LaunchedEffect(Unit) { - focusManager.clearFocus(true) - } - - var isOpenAnimationFinished by remember { mutableStateOf(false) } - - val draggableState = remember { - AnchoredDraggableState( - initialValue = SwipeState.Dismiss, - positionalThreshold = { it * 0.5f }, - velocityThreshold = { 200f }, - snapAnimationSpec = spring(), - decayAnimationSpec = exponentialDecay(), - confirmValueChange = { - it != SwipeState.Dismiss || dismissible() - } - ) - } - - BackHandler { - if (dismissible()) { - scope.launch { - draggableState.animateTo(SwipeState.Dismiss) - onDismissRequest() - } - } - } - - LaunchedEffect(draggableState.settledValue) { - if (isOpenAnimationFinished && draggableState.settledValue == SwipeState.Dismiss) { - onDismissRequest() - } else { - isOpenAnimationFinished = true - } - } - - - val nestedScrollConnection = remember { - object : NestedScrollConnection { - - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - val delta = available.toFloat() - return if (delta < 0 && source == NestedScrollSource.Drag) { - draggableState.dispatchRawDelta(delta).toOffset() - } else { - Offset.Zero - } - } - - override fun onPostScroll( - consumed: Offset, - available: Offset, - source: NestedScrollSource - ): Offset { - return if (source == NestedScrollSource.Drag) { - draggableState.dispatchRawDelta(available.toFloat()).toOffset() - } else { - Offset.Zero - } - } - - override suspend fun onPreFling(available: Velocity): Velocity { - val toFling = Offset(available.x, available.y).toFloat() - return if (toFling < 0 && draggableState.offset > draggableState.anchors.minPosition()) { - draggableState.settle(velocity = toFling) - // since we go to the anchor with tween settling, consume all for the best UX - available - } else { - Velocity.Zero - } - } - - override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { - draggableState.settle(velocity = Offset(available.x, available.y).toFloat()) - return available - } - - private fun Float.toOffset(): Offset = Offset(0f, this) - - private fun Offset.toFloat(): Float = this.y - } - } - - CompositionLocalProvider( - LocalAbsoluteTonalElevation provides 0.dp, + ModalBottomSheet( + sheetState = bottomSheetState, + onDismissRequest = onDismissRequest, ) { - Overlay(zIndex = zIndex) { - BoxWithConstraints( + content(PaddingValues(horizontal = 24.dp, vertical = 8.dp)) + if (footerItems != null) { + Row( modifier = Modifier - .fillMaxSize() - .systemBarsPadding() - .imePadding(), - propagateMinConstraints = true, - contentAlignment = Alignment.BottomCenter + .fillMaxWidth() + .padding(start = 24.dp, end = 24.dp, bottom = 8.dp), + horizontalArrangement = Arrangement.spacedBy( + space = 16.dp, + alignment = Alignment.End, + ) ) { - val maxHeight = maxHeight - val scrimAlpha by animateFloatAsState( - if (draggableState.targetValue == SwipeState.Dismiss) 0f else 0.32f, - label = "Scrim alpha" - ) - - Box(modifier = Modifier - .background(MaterialTheme.colorScheme.scrim.copy(alpha = scrimAlpha)) - .fillMaxSize() - .pointerInput(onDismissRequest, dismissible) { - detectTapGestures { - if (dismissible()) { - scope.launch { - draggableState.animateTo(SwipeState.Dismiss) - onDismissRequest() - } - } - } - } - ) - - Box( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .clipToBounds(), - contentAlignment = Alignment.TopCenter, - ) { - var sheetHeight by remember { - mutableStateOf(0f) - } - - val maxHeightPx = maxHeight.toPixels() - LaunchedEffect(maxHeightPx, sheetHeight) { - val oldValue = draggableState.currentValue - val hasPeekAnchor = sheetHeight > 0f - val hasFullAnchor = sheetHeight > maxHeightPx * 0.5f - // If the sheet was hidden, move it to peek. Otherwise, try to keep the previous state, if possible. - val newValue = when { - oldValue == SwipeState.Dismiss && hasPeekAnchor -> SwipeState.Peek - oldValue == SwipeState.Peek && hasPeekAnchor -> SwipeState.Peek - oldValue == SwipeState.Full && hasFullAnchor -> SwipeState.Full - oldValue == SwipeState.Full && hasPeekAnchor -> SwipeState.Peek - else -> SwipeState.Dismiss - } - val newAnchors = DraggableAnchors { - SwipeState.Dismiss at 0f - if (hasPeekAnchor) SwipeState.Peek at -min( - maxHeightPx * 0.5f, - sheetHeight - ) - if (hasFullAnchor) SwipeState.Full at -min(maxHeightPx, sheetHeight) - } - draggableState.updateAnchors( - newAnchors, - with(draggableState) { - (if (!offset.isNaN()) { - newAnchors.closestAnchor(offset) ?: targetValue - } else targetValue).let { - if (it == SwipeState.Dismiss && targetValue != SwipeState.Dismiss && hasPeekAnchor) SwipeState.Peek else it - } - }, - ) - if (newValue != oldValue) { - draggableState.animateTo(newValue) - } - } - Surface( - modifier = Modifier - .nestedScroll(nestedScrollConnection) - .onSizeChanged { - sheetHeight = it.height.toFloat() - } - .offset { - IntOffset(0, - maxHeightPx.toInt() + - (draggableState.offset - .takeIf { !it.isNaN() } - ?.roundToInt() ?: 0) - ) - } - .anchoredDraggable( - state = draggableState, - orientation = Orientation.Vertical, - ) - .fillMaxWidth(), - shape = MaterialTheme.shapes.extraLarge.copy( - bottomStart = CornerSize(0), - bottomEnd = CornerSize(0), - ), - shadowElevation = 16.dp, - tonalElevation = 1.dp, - color = MaterialTheme.colorScheme.surfaceContainerLow, - ) { - Column { - if (title != null || actions != null) { - CenterAlignedTopAppBar( - title = title ?: { BottomSheetDefaults.DragHandle() }, - actions = actions ?: {}, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent, - ), - ) - } else { - Box( - modifier = Modifier.align(Alignment.CenterHorizontally) - ) { - BottomSheetDefaults.DragHandle() - } - } - Box( - modifier = Modifier - .fillMaxWidth() - .then( - if (confirmButton != null || dismissButton != null) Modifier.padding( - bottom = 64.dp - ) else Modifier - ) - .wrapContentHeight(), - propagateMinConstraints = true, - contentAlignment = Alignment.Center - ) { - content(PaddingValues(horizontal = 24.dp, vertical = 8.dp)) - } - - } - } - - if (confirmButton != null || dismissButton != null) { - val elevation = 1.dp - val heightPx = -64.dp.toPixels() - Surface( - modifier = Modifier - .height(64.dp) - .offset { - IntOffset( - 0, - maxHeightPx.toInt() + - (draggableState.offset - .takeIf { !it.isNaN() } - ?.roundToInt() - ?: 0).coerceAtLeast(heightPx.toInt()) - ) - } - .fillMaxWidth(), - tonalElevation = elevation, - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.End - ) { - if (dismissButton != null) { - dismissButton() - } - if (confirmButton != null && dismissButton != null) { - Spacer(modifier = Modifier.width(16.dp)) - } - if (confirmButton != null) { - confirmButton() - } - } - - } - - } - } + footerItems() } } } -} - -private enum class SwipeState { - Full, Peek, Dismiss -} - -private class BottomSheetPositionProvider(val insets: WindowInsets, val density: Density) : - PopupPositionProvider { - override fun calculatePosition( - anchorBounds: IntRect, - windowSize: IntSize, - layoutDirection: LayoutDirection, - popupContentSize: IntSize - ): IntOffset { - return IntOffset.Zero + IntOffset( - insets.getLeft(density, layoutDirection), - insets.getTop(density) - ) - } } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt index cf7f1979..3aa75eb7 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt @@ -106,17 +106,7 @@ fun ConfigureWidgetSheet( onWidgetUpdated: (Widget) -> Unit, onDismiss: () -> Unit, ) { - BottomSheetDialog(onDismissRequest = onDismiss, - title = { - Box( - modifier = Modifier - .width(32.dp) - .height(4.dp) - .clip(MaterialTheme.shapes.small) - .background(MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f)) - ) - } - ) { + BottomSheetDialog(onDismissRequest = onDismiss) { Column( modifier = Modifier .fillMaxWidth() diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheet.kt index 49a9d59d..a1ecaf24 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheet.kt @@ -1,44 +1,27 @@ package de.mm20.launcher2.ui.launcher.sheets -import android.content.pm.PackageManager -import androidx.activity.compose.BackHandler -import androidx.compose.animation.animateContentSize import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.Label import androidx.compose.material.icons.outlined.Visibility -import androidx.compose.material.icons.rounded.ArrowDropDown import androidx.compose.material.icons.rounded.Edit -import androidx.compose.material.icons.rounded.FilterAlt -import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.Tag import androidx.compose.material.icons.rounded.Visibility import androidx.compose.material.icons.rounded.VisibilityOff -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MenuAnchorType -import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -50,16 +33,13 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.BadgeIcon import de.mm20.launcher2.icons.CustomIconWithPreview -import de.mm20.launcher2.icons.IconPack import de.mm20.launcher2.search.Application import de.mm20.launcher2.search.CalendarEvent import de.mm20.launcher2.search.SavableSearchable @@ -70,7 +50,6 @@ import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.OutlinedTagsInputField import de.mm20.launcher2.ui.component.ShapedLauncherIcon import de.mm20.launcher2.ui.ktx.toPixels -import de.mm20.launcher2.ui.locals.LocalGridSettings import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -79,246 +58,231 @@ fun CustomizeSearchableSheet( searchable: SavableSearchable, onDismiss: () -> Unit, ) { + val scope = rememberCoroutineScope() val viewModel: CustomizeSearchableSheetVM = remember(searchable.key) { CustomizeSearchableSheetVM(searchable) } val pickIcon by viewModel.isIconPickerOpen - if (pickIcon) { - BackHandler { - viewModel.closeIconPicker() - } - } + BottomSheetDialog(onDismissRequest = { if (!pickIcon) onDismiss() }) { + Column( + modifier = Modifier.padding(it), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val iconSize = 64.dp + val iconSizePx = iconSize.toPixels() + val icon by remember { viewModel.getIcon(iconSizePx.toInt()) }.collectAsState(null) - BottomSheetDialog( - onDismissRequest = onDismiss, - title = if (pickIcon) { - { - Text(stringResource(R.string.icon_picker_title)) - } - } else null, - dismissible = { !pickIcon }, - confirmButton = if (pickIcon) { - { - OutlinedButton(onClick = { viewModel.closeIconPicker() }) { - Text(stringResource(id = android.R.string.cancel)) + ShapedLauncherIcon( + size = iconSize, + icon = { icon }, + badge = { + Badge( + icon = BadgeIcon(Icons.Rounded.Edit) + ) + }, + modifier = Modifier.clickable { + viewModel.openIconPicker() } + ) + + var customLabelValue by remember { + mutableStateOf(searchable.labelOverride ?: "") } - } else null, - zIndex = 100f, - ) { - if (!pickIcon) { - Column( + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = 24.dp, bottom = 16.dp), + value = customLabelValue, + onValueChange = { + customLabelValue = it + }, + singleLine = true, + label = { + Text(stringResource(R.string.customize_item_label)) + }, + placeholder = { + Text(searchable.label) + }, + leadingIcon = { + Icon(Icons.AutoMirrored.Rounded.Label, null) + } + ) + + var tags by remember { mutableStateOf(emptyList()) } + var visibility by remember { mutableStateOf(VisibilityLevel.Default) } + + LaunchedEffect(searchable.key) { + visibility = viewModel.getVisibility().first() + tags = viewModel.getTags().first() + } + + OutlinedTagsInputField( modifier = Modifier .padding(top = 8.dp) - .padding(it), - horizontalAlignment = Alignment.CenterHorizontally - ) { - - val iconSize = 64.dp - val iconSizePx = iconSize.toPixels() - val icon by remember { viewModel.getIcon(iconSizePx.toInt()) }.collectAsState(null) - - ShapedLauncherIcon( - size = iconSize, - icon = { icon }, - badge = { - Badge( - icon = BadgeIcon(Icons.Rounded.Edit) - ) - }, - modifier = Modifier.clickable { - viewModel.openIconPicker() - } - ) - - var customLabelValue by remember { - mutableStateOf(searchable.labelOverride ?: "") + .fillMaxWidth(), + tags = tags, onTagsChange = { tags = it.distinct() }, + label = { + Text(stringResource(R.string.customize_item_tags)) + }, + onAutocomplete = { + viewModel.autocompleteTags(it).minus(tags.toSet()) + }, + leadingIcon = { + Icon(Icons.Rounded.Tag, null) } + ) + + var showDropdown by remember { + mutableStateOf(false) + } + + ExposedDropdownMenuBox( + expanded = showDropdown, + onExpandedChange = { showDropdown = it }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + ) { OutlinedTextField( modifier = Modifier .fillMaxWidth() - .padding(top = 24.dp, bottom = 16.dp), - value = customLabelValue, - onValueChange = { - customLabelValue = it - }, - singleLine = true, - label = { - Text(stringResource(R.string.customize_item_label)) - }, - placeholder = { - Text(searchable.label) - }, - leadingIcon = { - Icon(Icons.AutoMirrored.Rounded.Label, null) - } - ) - - var tags by remember { mutableStateOf(emptyList()) } - var visibility by remember { mutableStateOf(VisibilityLevel.Default) } - - LaunchedEffect(searchable.key) { - visibility = viewModel.getVisibility().first() - tags = viewModel.getTags().first() - } - - OutlinedTagsInputField( - modifier = Modifier - .padding(top = 8.dp) - .fillMaxWidth(), - tags = tags, onTagsChange = { tags = it.distinct() }, - label = { - Text(stringResource(R.string.customize_item_tags)) - }, - onAutocomplete = { - viewModel.autocompleteTags(it).minus(tags.toSet()) - }, - leadingIcon = { - Icon(Icons.Rounded.Tag, null) - } - ) - - var showDropdown by remember { - mutableStateOf(false) - } - - ExposedDropdownMenuBox( - expanded = showDropdown, - onExpandedChange = { showDropdown = it }, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - ) { - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .menuAnchor(MenuAnchorType.PrimaryNotEditable), - value = when (visibility) { - VisibilityLevel.Default -> { - when (searchable) { - is Application -> stringResource(R.string.item_visibility_app_default) - is CalendarEvent -> stringResource(R.string.item_visibility_calendar_default) - else -> stringResource(R.string.item_visibility_search_only) - } + .menuAnchor(MenuAnchorType.PrimaryNotEditable), + value = when (visibility) { + VisibilityLevel.Default -> { + when (searchable) { + is Application -> stringResource(R.string.item_visibility_app_default) + is CalendarEvent -> stringResource(R.string.item_visibility_calendar_default) + else -> stringResource(R.string.item_visibility_search_only) } + } - VisibilityLevel.SearchOnly -> stringResource(R.string.item_visibility_search_only) - VisibilityLevel.Hidden -> stringResource(R.string.item_visibility_hidden) - }, - label = { - Text(stringResource(R.string.customize_item_visibility)) - }, - onValueChange = {}, - readOnly = true, - singleLine = true, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showDropdown) }, - leadingIcon = { - Icon( - when (visibility) { - VisibilityLevel.Default -> Icons.Rounded.Visibility - VisibilityLevel.SearchOnly -> Icons.Outlined.Visibility - VisibilityLevel.Hidden -> Icons.Rounded.VisibilityOff - }, - null - ) - }, - colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), - ) - ExposedDropdownMenu( - expanded = showDropdown, - onDismissRequest = { - showDropdown = false - } - ) { - if (searchable is Application) { - DropdownMenuItem( - onClick = { - visibility = VisibilityLevel.Default - showDropdown = false - }, - text = { - Text(stringResource(R.string.item_visibility_app_default)) - }, - leadingIcon = { - Icon(Icons.Rounded.Visibility, null) - } - ) - } else if (searchable is CalendarEvent) { - DropdownMenuItem( - onClick = { - visibility = VisibilityLevel.Default - showDropdown = false - }, - text = { - Text(stringResource(R.string.item_visibility_calendar_default)) - }, - leadingIcon = { - Icon(Icons.Rounded.Visibility, null) - } - ) - } else { - DropdownMenuItem( - onClick = { - visibility = VisibilityLevel.Default - showDropdown = false - }, - text = { - Text(stringResource(R.string.item_visibility_search_only)) - }, - leadingIcon = { - Icon(Icons.Rounded.Visibility, null) - } - ) - } - if (searchable is Application || searchable is CalendarEvent) { - DropdownMenuItem( - onClick = { - visibility = VisibilityLevel.SearchOnly - showDropdown = false - }, - text = { - Text(stringResource(R.string.item_visibility_search_only)) - }, - leadingIcon = { - Icon( - Icons.Outlined.Visibility, - null - ) - } - ) - } + VisibilityLevel.SearchOnly -> stringResource(R.string.item_visibility_search_only) + VisibilityLevel.Hidden -> stringResource(R.string.item_visibility_hidden) + }, + label = { + Text(stringResource(R.string.customize_item_visibility)) + }, + onValueChange = {}, + readOnly = true, + singleLine = true, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showDropdown) }, + leadingIcon = { + Icon( + when (visibility) { + VisibilityLevel.Default -> Icons.Rounded.Visibility + VisibilityLevel.SearchOnly -> Icons.Outlined.Visibility + VisibilityLevel.Hidden -> Icons.Rounded.VisibilityOff + }, + null + ) + }, + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + ) + ExposedDropdownMenu( + expanded = showDropdown, + onDismissRequest = { + showDropdown = false + } + ) { + if (searchable is Application) { DropdownMenuItem( onClick = { - visibility = VisibilityLevel.Hidden + visibility = VisibilityLevel.Default showDropdown = false }, text = { - Text(stringResource(R.string.item_visibility_hidden)) + Text(stringResource(R.string.item_visibility_app_default)) }, leadingIcon = { - Icon(Icons.Rounded.VisibilityOff, null) + Icon(Icons.Rounded.Visibility, null) + } + ) + } else if (searchable is CalendarEvent) { + DropdownMenuItem( + onClick = { + visibility = VisibilityLevel.Default + showDropdown = false + }, + text = { + Text(stringResource(R.string.item_visibility_calendar_default)) + }, + leadingIcon = { + Icon(Icons.Rounded.Visibility, null) + } + ) + } else { + DropdownMenuItem( + onClick = { + visibility = VisibilityLevel.Default + showDropdown = false + }, + text = { + Text(stringResource(R.string.item_visibility_search_only)) + }, + leadingIcon = { + Icon(Icons.Rounded.Visibility, null) } ) } - } - - DisposableEffect(searchable.key) { - onDispose { - viewModel.setCustomLabel(customLabelValue) - viewModel.setTags(tags) - viewModel.setVisibility(visibility) + if (searchable is Application || searchable is CalendarEvent) { + DropdownMenuItem( + onClick = { + visibility = VisibilityLevel.SearchOnly + showDropdown = false + }, + text = { + Text(stringResource(R.string.item_visibility_search_only)) + }, + leadingIcon = { + Icon( + Icons.Outlined.Visibility, + null + ) + } + ) } + DropdownMenuItem( + onClick = { + visibility = VisibilityLevel.Hidden + showDropdown = false + }, + text = { + Text(stringResource(R.string.item_visibility_hidden)) + }, + leadingIcon = { + Icon(Icons.Rounded.VisibilityOff, null) + } + ) } } - } else { - IconPicker( - searchable = searchable, - onSelect = { - viewModel.pickIcon(it) - }, - contentPadding = it, - ) + + DisposableEffect(searchable.key) { + onDispose { + viewModel.setCustomLabel(customLabelValue) + viewModel.setTags(tags) + viewModel.setVisibility(visibility) + } + } + } + if (pickIcon) { + val bottomSheetState = rememberModalBottomSheetState() + BottomSheetDialog ( + onDismissRequest = { viewModel.closeIconPicker() }, + bottomSheetState = bottomSheetState + ) { + IconPicker( + searchable = searchable, + onSelect = { + scope.launch { + viewModel.pickIcon(it) + bottomSheetState.hide() + viewModel.closeIconPicker() + } + }, + contentPadding = it, + ) + } } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheetVM.kt index b7ecbacb..204dfa34 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/CustomizeSearchableSheetVM.kt @@ -45,7 +45,6 @@ class CustomizeSearchableSheetVM( fun pickIcon(icon: CustomIcon?) { iconService.setCustomIcon(searchable, icon) - closeIconPicker() } fun setCustomLabel(label: String) { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheet.kt index 20751c5e..705b1783 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheet.kt @@ -39,7 +39,6 @@ import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults @@ -106,28 +105,7 @@ fun EditFavoritesSheet( val loading by viewModel.loading val createShortcutTarget by viewModel.createShortcutTarget - BottomSheetDialog( - onDismissRequest = onDismiss, - title = { - Text( - if (createShortcutTarget == null) { - stringResource(id = R.string.menu_item_edit_favs) - } else { - stringResource(id = R.string.create_app_shortcut) - } - ) - }, - dismissible = { - createShortcutTarget == null - }, - confirmButton = if (createShortcutTarget != null) { - { - OutlinedButton(onClick = { viewModel.cancelPickShortcut() }) { - Text(stringResource(id = android.R.string.cancel)) - } - } - } else null - ) { + BottomSheetDialog(onDismiss) { if (loading) { Box( modifier = Modifier diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheetVM.kt index 7b4450d3..d2e9c480 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditFavoritesSheetVM.kt @@ -86,6 +86,8 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent { .map { Tag(it) } this.pinnedTags.value = pinnedTags + createShortcutTarget.value = null + buildItemList() loading.value = false } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditTagSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditTagSheet.kt index 63e192f5..16a6c415 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditTagSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditTagSheet.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset @@ -31,14 +32,15 @@ import androidx.compose.material.icons.rounded.Warning import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -49,6 +51,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.platform.LocalDensity @@ -77,8 +80,6 @@ fun EditTagSheet( ) { val viewModel: EditTagSheetVM = viewModel() - val isCreatingNewTag = tag == null - val density = LocalDensity.current LaunchedEffect(tag) { @@ -88,48 +89,14 @@ fun EditTagSheet( if (viewModel.loading) return BottomSheetDialog( - title = { - Text( - stringResource( - if (viewModel.page == EditTagSheetPage.CustomizeTag || !isCreatingNewTag) R.string.edit_tag_title - else R.string.create_tag_title - ) - ) - }, - confirmButton = if (viewModel.page == EditTagSheetPage.CustomizeTag) { - null - } else if (isCreatingNewTag) { - { - Button( - enabled = (viewModel.tagName.isNotBlank() && viewModel.page == EditTagSheetPage.CreateTag && !viewModel.tagNameExists) - || (viewModel.page == EditTagSheetPage.PickItems && viewModel.taggedItems.isNotEmpty()), - onClick = { viewModel.onClickContinue() }) { - Text(stringResource(R.string.action_next)) - } - } - } else if (viewModel.page == EditTagSheetPage.PickItems) { - { - OutlinedButton(onClick = { viewModel.closeItemPicker() }) { - Text(stringResource(id = R.string.ok)) - } - } - } else { - { - OutlinedButton(onClick = { viewModel.closeIconPicker() }) { - Text(stringResource(id = android.R.string.cancel)) - } - } - }, + bottomSheetState = rememberModalBottomSheetState(true), onDismissRequest = { if (viewModel.page == EditTagSheetPage.CustomizeTag) { viewModel.save() onTagSaved(viewModel.tagName) } onDismiss() - }, - dismissible = { - !(!isCreatingNewTag && viewModel.page == EditTagSheetPage.PickItems) - }, + } ) { when (viewModel.page) { EditTagSheetPage.CreateTag -> CreateNewTagPage(viewModel, it) @@ -161,55 +128,91 @@ fun CreateNewTagPage(viewModel: EditTagSheetVM, paddingValues: PaddingValues) { value = viewModel.tagName, onValueChange = { viewModel.tagName = it } ) + + Button( + modifier = Modifier.align(Alignment.End), + enabled = (viewModel.tagName.isNotBlank() && viewModel.page == EditTagSheetPage.CreateTag && !viewModel.tagNameExists) + || (viewModel.page == EditTagSheetPage.PickItems && viewModel.taggedItems.isNotEmpty()), + onClick = { viewModel.onClickContinue() }) { + Text(stringResource(R.string.action_next)) + } } } @Composable fun PickItems(viewModel: EditTagSheetVM, paddingValues: PaddingValues) { val columns = LocalGridSettings.current.columnCount - 1 - LazyVerticalGrid( - modifier = Modifier.fillMaxWidth(), - columns = GridCells.Fixed(columns), - contentPadding = paddingValues, + + Scaffold ( + contentWindowInsets = WindowInsets(0.dp), + modifier = Modifier.padding(paddingValues), + containerColor = Color.Transparent, + bottomBar = { + Surface ( + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.surface.copy(alpha = 0.95f), + shape = MaterialTheme.shapes.medium + ) { + Box { + Button( + onClick = { viewModel.closeItemPicker() }, + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(all = 8.dp) + .padding(end = 12.dp) + ) { + Text(stringResource(R.string.action_next)) + } + } + } + } ) { - item(span = { GridItemSpan(columns) }) { - Text( - stringResource(id = R.string.tag_select_items), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.secondary, - modifier = Modifier.padding(bottom = 8.dp) - ) - } - items(viewModel.taggableApps) { - val iconSize = 32.dp.toPixels() - val icon by remember(it.item.key) { - viewModel.getIcon(it.item, iconSize.toInt()) - }.collectAsState(null) - ListItem(item = it, icon = icon, onTagChanged = { tagged -> - if (tagged) viewModel.tagItem(it.item) - else viewModel.untagItem(it.item) - }) - } + LazyVerticalGrid( + modifier = Modifier.fillMaxWidth(), + columns = GridCells.Fixed(columns), + contentPadding = it + ) { + item(span = { GridItemSpan(columns) }) { + Text( + stringResource(id = R.string.tag_select_items), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.secondary, + modifier = Modifier.padding(bottom = 8.dp) + ) + } + items(viewModel.taggableApps) { + val iconSize = 32.dp.toPixels() + val icon by remember(it.item.key) { + viewModel.getIcon(it.item, iconSize.toInt()) + }.collectAsState(null) + ListItem(item = it, icon = icon, onTagChanged = { tagged -> + if (tagged) viewModel.tagItem(it.item) + else viewModel.untagItem(it.item) + }) + } - item(span = { GridItemSpan(columns) }) { - Box( - modifier = Modifier - .padding(vertical = 8.dp) - .background(MaterialTheme.colorScheme.outlineVariant) - .fillMaxWidth() - .height(1.dp) - ) - } + if (viewModel.taggableOther.isNotEmpty()) { + item(span = { GridItemSpan(columns) }) { + Box( + modifier = Modifier + .padding(vertical = 8.dp) + .background(MaterialTheme.colorScheme.outlineVariant) + .fillMaxWidth() + .height(1.dp) + ) + } - items(viewModel.taggableOther) { - val iconSize = 32.dp.toPixels() - val icon by remember(it.item.key) { - viewModel.getIcon(it.item, iconSize.toInt()) - }.collectAsState(null) - ListItem(item = it, icon = icon, onTagChanged = { tagged -> - if (tagged) viewModel.tagItem(it.item) - else viewModel.untagItem(it.item) - }) + items(viewModel.taggableOther) { + val iconSize = 32.dp.toPixels() + val icon by remember(it.item.key) { + viewModel.getIcon(it.item, iconSize.toInt()) + }.collectAsState(null) + ListItem(item = it, icon = icon, onTagChanged = { tagged -> + if (tagged) viewModel.tagItem(it.item) + else viewModel.untagItem(it.item) + }) + } + } } } } @@ -284,7 +287,7 @@ fun CustomizeTag(viewModel: EditTagSheetVM, paddingValues: PaddingValues) { .clickable { viewModel.openIconPicker() } - .size(56.dp) + .size(72.dp) then ( if (tagIcon != null) { Modifier @@ -327,7 +330,7 @@ fun CustomizeTag(viewModel: EditTagSheetVM, paddingValues: PaddingValues) { OutlinedTextField( modifier = Modifier.weight(1f), singleLine = true, - placeholder = { Text(stringResource(R.string.tag_name)) }, + label = { Text(stringResource(R.string.tag_name)) }, value = viewModel.tagName, onValueChange = { viewModel.tagName = it }, ) @@ -391,12 +394,14 @@ fun CustomizeTag(viewModel: EditTagSheetVM, paddingValues: PaddingValues) { } } } - AnimatedVisibility(viewModel.tagNameExists || viewModel.taggedItems.isEmpty()) { + AnimatedVisibility(viewModel.tagNameExists || viewModel.taggedItems.isEmpty() || viewModel.tagName.isEmpty()) { SmallMessage( modifier = Modifier.fillMaxWidth(), icon = Icons.Rounded.Warning, text = stringResource( - if (viewModel.taggedItems.isEmpty()) R.string.tag_no_items_message else R.string.tag_exists_message + if (viewModel.taggedItems.isEmpty()) R.string.tag_no_items_message + else if (viewModel.tagNameExists) R.string.tag_exists_message + else R.string.tag_empty_name ) ) } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditTagSheetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditTagSheetVM.kt index 52e79187..962e6bbc 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditTagSheetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/EditTagSheetVM.kt @@ -20,9 +20,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -83,7 +81,7 @@ class EditTagSheetVM : ViewModel(), KoinComponent { val oldName = oldTagName val newName = tagName val tagIcon = tagCustomIcon - if (taggedItems.isEmpty() && oldName != null) tagService.deleteTag(oldName) + if ((taggedItems.isEmpty() || tagName.isEmpty()) && oldName != null) tagService.deleteTag(oldName) else if (oldName != null) tagService.updateTag(oldName, newName = newName, items = taggedItems) else tagService.createTag(tagName, taggedItems) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/FailedGestureSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/FailedGestureSheet.kt index 62dad461..9aa9270e 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/FailedGestureSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/FailedGestureSheet.kt @@ -47,7 +47,6 @@ fun FailedGestureSheet( }) BottomSheetDialog( - title = { Text(actionName) }, onDismissRequest = onDismiss, ) { Column( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/HiddenItemsSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/HiddenItemsSheet.kt index 2e7eb3b8..219bddc2 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/HiddenItemsSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/HiddenItemsSheet.kt @@ -3,21 +3,9 @@ package de.mm20.launcher2.ui.launcher.sheets import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Edit -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.search.SavableSearchable -import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid @@ -26,27 +14,7 @@ fun HiddenItemsSheet( items: List, onDismiss: () -> Unit ) { - val viewModel: HiddenItemsSheetVM = viewModel() - - val context = LocalContext.current - - BottomSheetDialog( - onDismissRequest = onDismiss, - title = { - Text( - stringResource(R.string.preference_hidden_items), - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(end = 16.dp), - maxLines = 1 - ) - }, - actions = { - IconButton(onClick = { viewModel.showHiddenItems(context) }) { - Icon(imageVector = Icons.Rounded.Edit, contentDescription = null) - } - }, - ) { - + BottomSheetDialog(onDismiss) { SearchResultGrid( items, modifier = Modifier diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/WidgetPickerSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/WidgetPickerSheet.kt index d200f2b9..2da1ccb8 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/WidgetPickerSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/WidgetPickerSheet.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -64,9 +65,9 @@ import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog -import de.mm20.launcher2.widgets.CalendarWidget import de.mm20.launcher2.widgets.AppWidget import de.mm20.launcher2.widgets.AppWidgetConfig +import de.mm20.launcher2.widgets.CalendarWidget import de.mm20.launcher2.widgets.FavoritesWidget import de.mm20.launcher2.widgets.MusicWidget import de.mm20.launcher2.widgets.NotesWidget @@ -277,10 +278,8 @@ fun WidgetPickerSheet( val query by viewModel.searchQuery.collectAsState("") BottomSheetDialog( - onDismissRequest = onDismiss, - title = { - Text(title) - }) { + onDismissRequest = onDismiss + ) { val builtIn by viewModel.builtInWidgets.collectAsState(emptyList()) LazyColumn( modifier = Modifier @@ -299,6 +298,7 @@ fun WidgetPickerSheet( ) } .padding(bottom = 16.dp, start = 16.dp, end = 16.dp), + windowInsets = WindowInsets(0.dp), query = query, onQueryChange = { viewModel.search(it) }, onSearch = {}, diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt index 1d33eca2..c05688b8 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.rounded.AccessTime import androidx.compose.material.icons.rounded.Alarm import androidx.compose.material.icons.rounded.AlignVerticalBottom @@ -370,11 +371,16 @@ fun ConfigureClockWidgetSheet( icon = { SegmentedButtonDefaults.Icon( active = compact == false, + activeContent = { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = null, + ) + } ) { Icon( imageVector = Icons.Rounded.HorizontalSplit, contentDescription = null, - modifier = Modifier.size(SegmentedButtonDefaults.IconSize) ) } } @@ -390,11 +396,16 @@ fun ConfigureClockWidgetSheet( icon = { SegmentedButtonDefaults.Icon( active = compact == true, + activeContent = { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = null, + ) + } ) { Icon( imageVector = Icons.Rounded.VerticalSplit, contentDescription = null, - modifier = Modifier.size(SegmentedButtonDefaults.IconSize) ) } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/notes/NotesWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/notes/NotesWidget.kt index ea6aaf15..fa31f931 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/notes/NotesWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/notes/NotesWidget.kt @@ -7,7 +7,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.Canvas import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box @@ -43,11 +42,13 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -57,7 +58,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource @@ -398,39 +398,11 @@ fun NoteWidgetConflictResolveSheet( onDismissRequest: () -> Unit, ) { var selectedStrategy by remember { mutableStateOf(null) } + val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) BottomSheetDialog( onDismissRequest = onDismissRequest, - confirmButton = { - Button( - onClick = { onResolve(selectedStrategy ?: return@Button) }, - enabled = selectedStrategy != null, - contentPadding = ButtonDefaults.ButtonWithIconContentPadding, - ) { - Icon( - Icons.Rounded.CheckCircleOutline, - null, - modifier = Modifier - .padding(end = ButtonDefaults.IconSpacing) - .size(ButtonDefaults.IconSize) - ) - Text(stringResource(R.string.note_widget_conflict_keep_selected)) - } - }, - dismissButton = { - TextButton( - onClick = { onResolve(LinkedFileConflictStrategy.Unlink) }, - contentPadding = ButtonDefaults.TextButtonWithIconContentPadding, - ) { - Icon( - Icons.Rounded.LinkOff, - null, - modifier = Modifier - .padding(end = ButtonDefaults.IconSpacing) - .size(ButtonDefaults.IconSize) - ) - Text(stringResource(R.string.note_widget_action_unlink_file)) - } - } + bottomSheetState = bottomSheetState + ) { Column( modifier = Modifier @@ -463,6 +435,40 @@ fun NoteWidgetConflictResolveSheet( selected = selectedStrategy == LinkedFileConflictStrategy.KeepFile, onSelect = { selectedStrategy = LinkedFileConflictStrategy.KeepFile }, ) + Column ( + modifier = Modifier.padding(top = 8.dp), + ){ + OutlinedButton ( + onClick = { onResolve(LinkedFileConflictStrategy.Unlink) }, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.error + ), + ) { + Icon( + Icons.Rounded.LinkOff, + null, + modifier = Modifier + .padding(end = ButtonDefaults.IconSpacing) + .size(ButtonDefaults.IconSize) + ) + Text(stringResource(R.string.note_widget_action_unlink_file)) + } + Button( + onClick = { onResolve(selectedStrategy ?: return@Button) }, + enabled = selectedStrategy != null, + modifier = Modifier.fillMaxWidth() + ) { + Icon( + Icons.Rounded.CheckCircleOutline, + null, + modifier = Modifier + .padding(end = ButtonDefaults.IconSpacing) + .size(ButtonDefaults.IconSize) + ) + Text(stringResource(R.string.note_widget_conflict_keep_selected)) + } + } } } } @@ -504,21 +510,6 @@ fun SelectableNoteContent( .padding(16.dp), text = content, onTextChange = {} ) - if (!expanded) { - Canvas( - modifier = Modifier - .height(32.dp) - .fillMaxWidth() - .align(Alignment.BottomCenter) - ) { - drawRect( - brush = Brush.verticalGradient( - 0f to color.copy(alpha = 0f), - 1f to color.copy(alpha = 0.8f), - ), - ) - } - } IconButton( modifier = Modifier.align(Alignment.TopEnd), onClick = onSelect diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt index e5787890..02ecd98d 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt @@ -2,26 +2,24 @@ package de.mm20.launcher2.ui.settings.backup import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.* -import androidx.compose.material3.* +import androidx.compose.material.icons.rounded.CheckCircleOutline +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.LargeMessage -import de.mm20.launcher2.ui.component.SmallMessage import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @@ -50,68 +48,29 @@ fun CreateBackupSheet( val state by viewModel.state - BottomSheetDialog( - onDismissRequest = onDismissRequest, - title = { - Text( - stringResource(id = R.string.preference_backup), - ) - }, - confirmButton = { - if (state == CreateBackupState.Ready) { - Button( - onClick = { - val fileName = "${ - ZonedDateTime.now().format( - DateTimeFormatter.ISO_INSTANT - ) - }.kvaesitso" - backupLauncher.launch(fileName) - }) { - Text(stringResource(R.string.preference_backup)) - } - } else if (state == CreateBackupState.BackedUp) { - OutlinedButton( - onClick = onDismissRequest + if (state == CreateBackupState.BackingUp || state == CreateBackupState.BackedUp) { + BottomSheetDialog(onDismissRequest) { + if (state == CreateBackupState.BackingUp) { + Box( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f), + contentAlignment = Alignment.Center ) { - Text(stringResource(R.string.close)) - } - } - }, - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .verticalScroll(rememberScrollState()) - .padding(it) - ) { - when (state) { - CreateBackupState.Ready -> { - - } - CreateBackupState.BackingUp -> { - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1f), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator( - modifier = Modifier.size(48.dp) - ) - } - } - CreateBackupState.BackedUp -> { - LargeMessage( - modifier = Modifier.aspectRatio(1f), - icon = Icons.Rounded.CheckCircleOutline, - text = stringResource( - id = R.string.backup_complete - ) + CircularProgressIndicator( + modifier = Modifier.size(48.dp) ) } } + else { + LargeMessage( + modifier = Modifier.aspectRatio(1f), + icon = Icons.Rounded.CheckCircleOutline, + text = stringResource( + id = R.string.backup_complete + ) + ) + } } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/gestures/GestureSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/gestures/GestureSettingsScreen.kt index 0e897a6b..653064de 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/gestures/GestureSettingsScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/gestures/GestureSettingsScreen.kt @@ -279,7 +279,6 @@ fun GesturePreference( if (!isOpenSearch && value is GestureAction.Launch && (showAppPicker || app == null)) { SearchablePicker( - title = { Text(title) }, onDismissRequest = { showAppPicker = false if (app == null) onValueChanged(GestureAction.NoAction) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheet.kt index e7e50772..39bca9c4 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheet.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -31,9 +32,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.Android import androidx.compose.material.icons.rounded.ArrowDropDown -import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.ManageSearch -import androidx.compose.material.icons.rounded.MoreVert import androidx.compose.material.icons.rounded.RemoveCircleOutline import androidx.compose.material.icons.rounded.ToggleOn import androidx.compose.material.icons.rounded.TravelExplore @@ -56,7 +55,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -85,8 +83,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.searchactions.actions.SearchActionIcon import de.mm20.launcher2.searchactions.builders.AppSearchActionBuilder import de.mm20.launcher2.searchactions.builders.CustomIntentActionBuilder -import de.mm20.launcher2.searchactions.builders.CustomizableSearchActionBuilder import de.mm20.launcher2.searchactions.builders.CustomWebsearchActionBuilder +import de.mm20.launcher2.searchactions.builders.CustomizableSearchActionBuilder import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.ExperimentalBadge @@ -99,13 +97,11 @@ fun EditSearchActionSheet( initialSearchAction: CustomizableSearchActionBuilder?, onSave: (CustomizableSearchActionBuilder) -> Unit, onDismiss: () -> Unit, - onDelete: () -> Unit = {}, ) { val viewModel: EditSearchActionSheetVM = viewModel() LaunchedEffect(initialSearchAction) { viewModel.init(initialSearchAction) } - val createNew by viewModel.createNew val page by viewModel.currentPage val searchAction by viewModel.searchAction @@ -114,10 +110,7 @@ fun EditSearchActionSheet( viewModel.onDismiss() onDismiss() }, - dismissible = { - page != EditSearchActionPage.PickIcon - }, - confirmButton = when (page) { + footerItems = when (page) { EditSearchActionPage.CustomizeAppSearch, EditSearchActionPage.CustomizeWebSearch, EditSearchActionPage.CustomizeCustomIntent -> { @@ -170,37 +163,6 @@ fun EditSearchActionSheet( } else -> null - }, - actions = { - var showMenu by remember { mutableStateOf(false) } - if (!createNew) { - IconButton(onClick = { showMenu = !showMenu }) { - Icon(imageVector = Icons.Rounded.MoreVert, contentDescription = null) - DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { - DropdownMenuItem( - text = { Text(stringResource(id = R.string.menu_delete)) }, - leadingIcon = { - Icon(imageVector = Icons.Rounded.Delete, contentDescription = null) - }, - onClick = { - onDelete() - showMenu = false - } - ) - } - } - } - }, - title = { - Text( - stringResource( - if (createNew) { - R.string.create_search_action_title - } else { - R.string.edit_search_action_title - } - ) - ) }) { when (page) { EditSearchActionPage.SelectType -> SelectTypePage(viewModel, it) @@ -741,7 +703,7 @@ fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) { if (action?.customIcon == null) { val availableIcons = - remember { SearchActionIcon.values().filter { it != SearchActionIcon.Custom } } + remember { SearchActionIcon.entries.filter { it != SearchActionIcon.Custom } } Column( modifier = Modifier.padding(paddingValues) @@ -787,7 +749,7 @@ fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) { } } TextButton( - modifier = Modifier.padding(top = 16.dp, bottom = 24.dp), + modifier = Modifier.padding(vertical = 8.dp), onClick = { pickIconLauncher.launch("image/*") }) { Text(stringResource(R.string.websearch_dialog_custom_icon)) } @@ -795,7 +757,8 @@ fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) { } else { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(paddingValues) + modifier = Modifier.padding(paddingValues).fillMaxWidth() + ) { SearchActionIconTile { SearchActionIcon(builder = action!!, size = 24.dp) @@ -817,15 +780,17 @@ fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) { Row( modifier = Modifier .padding(top = 24.dp) - .horizontalScroll(rememberScrollState()) + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy( + space = 16.dp, + alignment = Alignment.End + ) ) { OutlinedButton( - modifier = Modifier.padding(start = 16.dp), onClick = { pickIconLauncher.launch("image/*") }) { Text(stringResource(R.string.websearch_dialog_replace_icon)) } OutlinedButton( - modifier = Modifier.padding(start = 16.dp), onClick = { viewModel.setIcon(SearchActionIcon.Search) }, colors = ButtonDefaults.outlinedButtonColors( contentColor = MaterialTheme.colorScheme.error diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/SearchActionsSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/SearchActionsSettingsScreen.kt index 60ca4cd3..087a9dd9 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/SearchActionsSettingsScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/SearchActionsSettingsScreen.kt @@ -289,9 +289,6 @@ fun SearchActionsSettingsScreen() { }, onDismiss = { viewModel.dismissDialogs() - }, - onDelete = { - viewModel.removeAction(editAction!!) } ) } diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index 9b0435b7..c4eb4d17 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -732,6 +732,7 @@ A tag with this name already exists. A tag with this name already exists. If you continue, the two tags will be merged. No items are assigned to this tag. If you continue, the tag will be deleted. + A tag cannot exist without a name. If you continue, the tag will be deleted. Select items: Tag name