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
This commit is contained in:
parent
912fddc7c4
commit
024a646b1a
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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<String>()) }
|
||||
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<String>()) }
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +45,6 @@ class CustomizeSearchableSheetVM(
|
||||
|
||||
fun pickIcon(icon: CustomIcon?) {
|
||||
iconService.setCustomIcon(searchable, icon)
|
||||
closeIconPicker()
|
||||
}
|
||||
|
||||
fun setCustomLabel(label: String) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -86,6 +86,8 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
||||
.map { Tag(it) }
|
||||
this.pinnedTags.value = pinnedTags
|
||||
|
||||
createShortcutTarget.value = null
|
||||
|
||||
buildItemList()
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -47,7 +47,6 @@ fun FailedGestureSheet(
|
||||
})
|
||||
|
||||
BottomSheetDialog(
|
||||
title = { Text(actionName) },
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
Column(
|
||||
|
||||
@ -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<SavableSearchable>,
|
||||
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
|
||||
|
||||
@ -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 = {},
|
||||
|
||||
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<LinkedFileConflictStrategy?>(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
|
||||
|
||||
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -289,9 +289,6 @@ fun SearchActionsSettingsScreen() {
|
||||
},
|
||||
onDismiss = {
|
||||
viewModel.dismissDialogs()
|
||||
},
|
||||
onDelete = {
|
||||
viewModel.removeAction(editAction!!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -732,6 +732,7 @@
|
||||
<string name="tag_exists_error">A tag with this name already exists.</string>
|
||||
<string name="tag_exists_message">A tag with this name already exists. If you continue, the two tags will be merged.</string>
|
||||
<string name="tag_no_items_message">No items are assigned to this tag. If you continue, the tag will be deleted.</string>
|
||||
<string name="tag_empty_name">A tag cannot exist without a name. If you continue, the tag will be deleted.</string>
|
||||
<string name="tag_select_items">Select items:</string>
|
||||
<string name="tag_name">Tag name</string>
|
||||
<plurals name="tag_selected_items">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user