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.animation.animateContentSize
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
@ -22,7 +23,6 @@ import androidx.compose.material3.CircularProgressIndicator
|
|||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.SearchBar
|
import androidx.compose.material3.SearchBar
|
||||||
import androidx.compose.material3.SearchBarDefaults
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -79,7 +79,7 @@ fun IconPicker(
|
|||||||
|
|
||||||
var showIconPackFilter by remember { mutableStateOf(false) }
|
var showIconPackFilter by remember { mutableStateOf(false) }
|
||||||
val installedIconPacks by viewModel.installedIconPacks.collectAsState(null)
|
val installedIconPacks by viewModel.installedIconPacks.collectAsState(null)
|
||||||
val noPacksInstalled = installedIconPacks?.isEmpty() == true
|
val packsInstalled = installedIconPacks?.isEmpty() == false
|
||||||
|
|
||||||
val columns = LocalGridSettings.current.columnCount
|
val columns = LocalGridSettings.current.columnCount
|
||||||
|
|
||||||
@ -88,42 +88,38 @@ fun IconPicker(
|
|||||||
columns = GridCells.Fixed(columns),
|
columns = GridCells.Fixed(columns),
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
|
if (packsInstalled) {
|
||||||
item(span = { GridItemSpan(columns) }) {
|
item(span = { GridItemSpan(columns) }) {
|
||||||
SearchBar(
|
SearchBar(
|
||||||
modifier = Modifier.padding(bottom = 16.dp),
|
windowInsets = WindowInsets(0.dp),
|
||||||
expanded = false,
|
expanded = false,
|
||||||
onExpandedChange = {},
|
onExpandedChange = {},
|
||||||
inputField = {
|
inputField = {
|
||||||
SearchBarDefaults.InputField(
|
SearchBarDefaults.InputField(
|
||||||
enabled = !noPacksInstalled,
|
leadingIcon = {
|
||||||
leadingIcon = {
|
Icon(
|
||||||
Icon(
|
imageVector = Icons.Rounded.Search,
|
||||||
imageVector = Icons.Rounded.Search,
|
contentDescription = null
|
||||||
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
|
|
||||||
)
|
)
|
||||||
)
|
},
|
||||||
},
|
onSearch = {},
|
||||||
query = query,
|
expanded = false,
|
||||||
onQueryChange = {
|
onExpandedChange = {},
|
||||||
query = it
|
placeholder = {
|
||||||
scope.launch {
|
Text(stringResource(R.string.icon_picker_search_icon))
|
||||||
viewModel.searchIcon(query, filterIconPack)
|
},
|
||||||
}
|
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()) {
|
if (suggestions.isNotEmpty()) {
|
||||||
|
item(span = { GridItemSpan(columns) }) {
|
||||||
|
Separator(stringResource(R.string.icon_picker_suggestions))
|
||||||
|
}
|
||||||
items(suggestions) {
|
items(suggestions) {
|
||||||
IconPreview(
|
IconPreview(
|
||||||
it,
|
it,
|
||||||
@ -152,87 +148,82 @@ fun IconPicker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
item(span = { GridItemSpan(columns) }) {
|
||||||
if (!installedIconPacks.isNullOrEmpty()) {
|
Button(
|
||||||
item(
|
onClick = { showIconPackFilter = !showIconPackFilter },
|
||||||
span = { GridItemSpan(columns) },
|
modifier = Modifier
|
||||||
|
.wrapContentWidth(align = Alignment.CenterHorizontally)
|
||||||
|
.padding(16.dp),
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
horizontal = 16.dp,
|
||||||
|
vertical = 8.dp
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Button(
|
if (filterIconPack == null) {
|
||||||
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()
|
|
||||||
)
|
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Rounded.ArrowDropDown,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = ButtonDefaults.IconSpacing)
|
.padding(end = ButtonDefaults.IconSpacing)
|
||||||
.size(ButtonDefaults.IconSize),
|
.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
|
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.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
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.Icons
|
||||||
import androidx.compose.material.icons.rounded.DarkMode
|
import androidx.compose.material.icons.rounded.DarkMode
|
||||||
import androidx.compose.material.icons.rounded.ErrorOutline
|
import androidx.compose.material.icons.rounded.ErrorOutline
|
||||||
@ -69,21 +67,7 @@ fun ImportThemeSheet(
|
|||||||
val error by viewModel.error
|
val error by viewModel.error
|
||||||
var apply by viewModel.apply
|
var apply by viewModel.apply
|
||||||
|
|
||||||
BottomSheetDialog(
|
BottomSheetDialog(onDismiss) {
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
confirmButton = if (theme != null && !error) {
|
|
||||||
{
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
viewModel.import()
|
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.action_import))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else null,
|
|
||||||
) {
|
|
||||||
if (theme == null && !error) {
|
if (theme == null && !error) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -111,8 +95,8 @@ fun ImportThemeSheet(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.verticalScroll(rememberScrollState())
|
.padding(it),
|
||||||
.padding(it)
|
horizontalAlignment = Alignment.End
|
||||||
) {
|
) {
|
||||||
ThemePreview(
|
ThemePreview(
|
||||||
theme!!,
|
theme!!,
|
||||||
@ -132,6 +116,15 @@ fun ImportThemeSheet(
|
|||||||
apply = it
|
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.net.Uri
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.verticalScroll
|
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.Icons
|
||||||
import androidx.compose.material.icons.rounded.*
|
import androidx.compose.material.icons.rounded.CheckCircleOutline
|
||||||
import androidx.compose.material3.*
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -37,35 +47,12 @@ fun RestoreBackupSheet(
|
|||||||
val state by viewModel.state
|
val state by viewModel.state
|
||||||
val compatibility by viewModel.compatibility
|
val compatibility by viewModel.compatibility
|
||||||
|
|
||||||
BottomSheetDialog(
|
BottomSheetDialog(onDismissRequest) {
|
||||||
onDismissRequest = onDismissRequest,
|
Column (
|
||||||
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(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.padding(it),
|
||||||
.verticalScroll(rememberScrollState())
|
horizontalAlignment = Alignment.End,
|
||||||
.padding(it)
|
|
||||||
) {
|
) {
|
||||||
when (state) {
|
when (state) {
|
||||||
RestoreBackupState.Parsing -> {
|
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(
|
fun SearchablePicker(
|
||||||
value: SavableSearchable?,
|
value: SavableSearchable?,
|
||||||
onValueChanged: (SavableSearchable?) -> Unit,
|
onValueChanged: (SavableSearchable?) -> Unit,
|
||||||
title: @Composable () -> Unit,
|
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val viewModel: SearchablePickerVM = viewModel()
|
val viewModel: SearchablePickerVM = viewModel()
|
||||||
|
|
||||||
BottomSheetDialog(onDismissRequest = onDismissRequest, title = title) {
|
BottomSheetDialog(onDismissRequest = onDismissRequest) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package de.mm20.launcher2.ui.common
|
|||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
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.Icons
|
||||||
import androidx.compose.material.icons.rounded.Error
|
import androidx.compose.material.icons.rounded.Error
|
||||||
import androidx.compose.material.icons.rounded.Search
|
import androidx.compose.material.icons.rounded.Search
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.*
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@ -32,38 +41,39 @@ fun WeatherLocationSearchDialog(
|
|||||||
val isSearching by viewModel.isSearchingLocation
|
val isSearching by viewModel.isSearchingLocation
|
||||||
val locations by viewModel.locationResults
|
val locations by viewModel.locationResults
|
||||||
|
|
||||||
BottomSheetDialog(
|
BottomSheetDialog(onDismissRequest) {
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.preference_location),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
var query by remember { mutableStateOf("") }
|
var query by remember { mutableStateOf("") }
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize().padding(it)
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(it)
|
||||||
) {
|
) {
|
||||||
Row(
|
SearchBar(
|
||||||
Modifier.padding(bottom = 16.dp)
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
) {
|
windowInsets = WindowInsets(0.dp),
|
||||||
OutlinedTextField(
|
expanded = false,
|
||||||
singleLine = true,
|
onExpandedChange = {},
|
||||||
value = query,
|
inputField = {
|
||||||
textStyle = MaterialTheme.typography.bodyLarge,
|
SearchBarDefaults.InputField(
|
||||||
onValueChange = {
|
leadingIcon = {
|
||||||
query = it
|
Icon(
|
||||||
scope.launch {
|
imageVector = Icons.Rounded.Search,
|
||||||
viewModel.searchLocation(it)
|
contentDescription = null
|
||||||
}
|
)
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
onSearch = {},
|
||||||
Icon(imageVector = Icons.Rounded.Search, contentDescription = null)
|
expanded = false,
|
||||||
},
|
onExpandedChange = {},
|
||||||
modifier = Modifier
|
query = query,
|
||||||
.weight(1f)
|
onQueryChange = {
|
||||||
)
|
query = it
|
||||||
}
|
scope.launch {
|
||||||
|
viewModel.searchLocation(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {}
|
||||||
if (isSearching) {
|
if (isSearching) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@ -1,374 +1,42 @@
|
|||||||
package de.mm20.launcher2.ui.component
|
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.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.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
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.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.padding
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.material3.SheetState
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
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.runtime.Composable
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.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
|
@Composable
|
||||||
fun BottomSheetDialog(
|
fun BottomSheetDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
title: (@Composable () -> Unit)? = null,
|
footerItems: @Composable (() -> Unit)? = null,
|
||||||
actions: (@Composable RowScope.() -> Unit)? = null,
|
bottomSheetState: SheetState = rememberModalBottomSheetState(),
|
||||||
confirmButton: @Composable (() -> Unit)? = null,
|
|
||||||
dismissButton: @Composable (() -> Unit)? = null,
|
|
||||||
dismissible: () -> Boolean = { true },
|
|
||||||
zIndex: Float = LocalZIndex.current + 1f,
|
|
||||||
content: @Composable (paddingValues: PaddingValues) -> Unit,
|
content: @Composable (paddingValues: PaddingValues) -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
ModalBottomSheet(
|
||||||
|
sheetState = bottomSheetState,
|
||||||
val focusManager = LocalFocusManager.current
|
onDismissRequest = onDismissRequest,
|
||||||
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,
|
|
||||||
) {
|
) {
|
||||||
Overlay(zIndex = zIndex) {
|
content(PaddingValues(horizontal = 24.dp, vertical = 8.dp))
|
||||||
BoxWithConstraints(
|
if (footerItems != null) {
|
||||||
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxWidth()
|
||||||
.systemBarsPadding()
|
.padding(start = 24.dp, end = 24.dp, bottom = 8.dp),
|
||||||
.imePadding(),
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
propagateMinConstraints = true,
|
space = 16.dp,
|
||||||
contentAlignment = Alignment.BottomCenter
|
alignment = Alignment.End,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
val maxHeight = maxHeight
|
footerItems()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
onWidgetUpdated: (Widget) -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
BottomSheetDialog(onDismissRequest = onDismiss,
|
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))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
@ -1,44 +1,27 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.sheets
|
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.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
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.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
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.Icons
|
||||||
import androidx.compose.material.icons.automirrored.rounded.Label
|
import androidx.compose.material.icons.automirrored.rounded.Label
|
||||||
import androidx.compose.material.icons.outlined.Visibility
|
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.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.Tag
|
||||||
import androidx.compose.material.icons.rounded.Visibility
|
import androidx.compose.material.icons.rounded.Visibility
|
||||||
import androidx.compose.material.icons.rounded.VisibilityOff
|
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.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.MenuAnchorType
|
import androidx.compose.material3.MenuAnchorType
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -50,16 +33,13 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.Badge
|
||||||
import de.mm20.launcher2.badges.BadgeIcon
|
import de.mm20.launcher2.badges.BadgeIcon
|
||||||
import de.mm20.launcher2.icons.CustomIconWithPreview
|
import de.mm20.launcher2.icons.CustomIconWithPreview
|
||||||
import de.mm20.launcher2.icons.IconPack
|
|
||||||
import de.mm20.launcher2.search.Application
|
import de.mm20.launcher2.search.Application
|
||||||
import de.mm20.launcher2.search.CalendarEvent
|
import de.mm20.launcher2.search.CalendarEvent
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
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.OutlinedTagsInputField
|
||||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||||
import de.mm20.launcher2.ui.ktx.toPixels
|
import de.mm20.launcher2.ui.ktx.toPixels
|
||||||
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@ -79,246 +58,231 @@ fun CustomizeSearchableSheet(
|
|||||||
searchable: SavableSearchable,
|
searchable: SavableSearchable,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
val viewModel: CustomizeSearchableSheetVM =
|
val viewModel: CustomizeSearchableSheetVM =
|
||||||
remember(searchable.key) { CustomizeSearchableSheetVM(searchable) }
|
remember(searchable.key) { CustomizeSearchableSheetVM(searchable) }
|
||||||
|
|
||||||
val pickIcon by viewModel.isIconPickerOpen
|
val pickIcon by viewModel.isIconPickerOpen
|
||||||
|
|
||||||
if (pickIcon) {
|
BottomSheetDialog(onDismissRequest = { if (!pickIcon) onDismiss() }) {
|
||||||
BackHandler {
|
Column(
|
||||||
viewModel.closeIconPicker()
|
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(
|
ShapedLauncherIcon(
|
||||||
onDismissRequest = onDismiss,
|
size = iconSize,
|
||||||
title = if (pickIcon) {
|
icon = { icon },
|
||||||
{
|
badge = {
|
||||||
Text(stringResource(R.string.icon_picker_title))
|
Badge(
|
||||||
}
|
icon = BadgeIcon(Icons.Rounded.Edit)
|
||||||
} else null,
|
)
|
||||||
dismissible = { !pickIcon },
|
},
|
||||||
confirmButton = if (pickIcon) {
|
modifier = Modifier.clickable {
|
||||||
{
|
viewModel.openIconPicker()
|
||||||
OutlinedButton(onClick = { viewModel.closeIconPicker() }) {
|
|
||||||
Text(stringResource(id = android.R.string.cancel))
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var customLabelValue by remember {
|
||||||
|
mutableStateOf(searchable.labelOverride ?: "")
|
||||||
}
|
}
|
||||||
} else null,
|
OutlinedTextField(
|
||||||
zIndex = 100f,
|
modifier = Modifier
|
||||||
) {
|
.fillMaxWidth()
|
||||||
if (!pickIcon) {
|
.padding(top = 24.dp, bottom = 16.dp),
|
||||||
Column(
|
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
|
modifier = Modifier
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
.padding(it),
|
.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
tags = tags, onTagsChange = { tags = it.distinct() },
|
||||||
) {
|
label = {
|
||||||
|
Text(stringResource(R.string.customize_item_tags))
|
||||||
val iconSize = 64.dp
|
},
|
||||||
val iconSizePx = iconSize.toPixels()
|
onAutocomplete = {
|
||||||
val icon by remember { viewModel.getIcon(iconSizePx.toInt()) }.collectAsState(null)
|
viewModel.autocompleteTags(it).minus(tags.toSet())
|
||||||
|
},
|
||||||
ShapedLauncherIcon(
|
leadingIcon = {
|
||||||
size = iconSize,
|
Icon(Icons.Rounded.Tag, null)
|
||||||
icon = { icon },
|
|
||||||
badge = {
|
|
||||||
Badge(
|
|
||||||
icon = BadgeIcon(Icons.Rounded.Edit)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
viewModel.openIconPicker()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var customLabelValue by remember {
|
|
||||||
mutableStateOf(searchable.labelOverride ?: "")
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var showDropdown by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExposedDropdownMenuBox(
|
||||||
|
expanded = showDropdown,
|
||||||
|
onExpandedChange = { showDropdown = it },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 24.dp, bottom = 16.dp),
|
.menuAnchor(MenuAnchorType.PrimaryNotEditable),
|
||||||
value = customLabelValue,
|
value = when (visibility) {
|
||||||
onValueChange = {
|
VisibilityLevel.Default -> {
|
||||||
customLabelValue = it
|
when (searchable) {
|
||||||
},
|
is Application -> stringResource(R.string.item_visibility_app_default)
|
||||||
singleLine = true,
|
is CalendarEvent -> stringResource(R.string.item_visibility_calendar_default)
|
||||||
label = {
|
else -> stringResource(R.string.item_visibility_search_only)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VisibilityLevel.SearchOnly -> stringResource(R.string.item_visibility_search_only)
|
VisibilityLevel.SearchOnly -> stringResource(R.string.item_visibility_search_only)
|
||||||
VisibilityLevel.Hidden -> stringResource(R.string.item_visibility_hidden)
|
VisibilityLevel.Hidden -> stringResource(R.string.item_visibility_hidden)
|
||||||
},
|
},
|
||||||
label = {
|
label = {
|
||||||
Text(stringResource(R.string.customize_item_visibility))
|
Text(stringResource(R.string.customize_item_visibility))
|
||||||
},
|
},
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showDropdown) },
|
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showDropdown) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
when (visibility) {
|
when (visibility) {
|
||||||
VisibilityLevel.Default -> Icons.Rounded.Visibility
|
VisibilityLevel.Default -> Icons.Rounded.Visibility
|
||||||
VisibilityLevel.SearchOnly -> Icons.Outlined.Visibility
|
VisibilityLevel.SearchOnly -> Icons.Outlined.Visibility
|
||||||
VisibilityLevel.Hidden -> Icons.Rounded.VisibilityOff
|
VisibilityLevel.Hidden -> Icons.Rounded.VisibilityOff
|
||||||
},
|
},
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
|
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
|
||||||
)
|
)
|
||||||
ExposedDropdownMenu(
|
ExposedDropdownMenu(
|
||||||
expanded = showDropdown,
|
expanded = showDropdown,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showDropdown = false
|
showDropdown = false
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (searchable is Application) {
|
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
onClick = {
|
onClick = {
|
||||||
visibility = VisibilityLevel.Hidden
|
visibility = VisibilityLevel.Default
|
||||||
showDropdown = false
|
showDropdown = false
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(stringResource(R.string.item_visibility_hidden))
|
Text(stringResource(R.string.item_visibility_app_default))
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
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)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
if (searchable is Application || searchable is CalendarEvent) {
|
||||||
|
DropdownMenuItem(
|
||||||
DisposableEffect(searchable.key) {
|
onClick = {
|
||||||
onDispose {
|
visibility = VisibilityLevel.SearchOnly
|
||||||
viewModel.setCustomLabel(customLabelValue)
|
showDropdown = false
|
||||||
viewModel.setTags(tags)
|
},
|
||||||
viewModel.setVisibility(visibility)
|
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(
|
DisposableEffect(searchable.key) {
|
||||||
searchable = searchable,
|
onDispose {
|
||||||
onSelect = {
|
viewModel.setCustomLabel(customLabelValue)
|
||||||
viewModel.pickIcon(it)
|
viewModel.setTags(tags)
|
||||||
},
|
viewModel.setVisibility(visibility)
|
||||||
contentPadding = it,
|
}
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
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?) {
|
fun pickIcon(icon: CustomIcon?) {
|
||||||
iconService.setCustomIcon(searchable, icon)
|
iconService.setCustomIcon(searchable, icon)
|
||||||
closeIconPicker()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCustomLabel(label: String) {
|
fun setCustomLabel(label: String) {
|
||||||
|
|||||||
@ -39,7 +39,6 @@ import androidx.compose.material3.FilterChipDefaults
|
|||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
|
||||||
import androidx.compose.material3.OutlinedCard
|
import androidx.compose.material3.OutlinedCard
|
||||||
import androidx.compose.material3.Slider
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.SliderDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
@ -106,28 +105,7 @@ fun EditFavoritesSheet(
|
|||||||
val loading by viewModel.loading
|
val loading by viewModel.loading
|
||||||
val createShortcutTarget by viewModel.createShortcutTarget
|
val createShortcutTarget by viewModel.createShortcutTarget
|
||||||
|
|
||||||
BottomSheetDialog(
|
BottomSheetDialog(onDismiss) {
|
||||||
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
|
|
||||||
) {
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@ -86,6 +86,8 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
.map { Tag(it) }
|
.map { Tag(it) }
|
||||||
this.pinnedTags.value = pinnedTags
|
this.pinnedTags.value = pinnedTags
|
||||||
|
|
||||||
|
createShortcutTarget.value = null
|
||||||
|
|
||||||
buildItemList()
|
buildItemList()
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.offset
|
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.Button
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SegmentedButton
|
import androidx.compose.material3.SegmentedButton
|
||||||
import androidx.compose.material3.SegmentedButtonDefaults
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@ -49,6 +51,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.PathEffect
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
@ -77,8 +80,6 @@ fun EditTagSheet(
|
|||||||
) {
|
) {
|
||||||
val viewModel: EditTagSheetVM = viewModel()
|
val viewModel: EditTagSheetVM = viewModel()
|
||||||
|
|
||||||
val isCreatingNewTag = tag == null
|
|
||||||
|
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
LaunchedEffect(tag) {
|
LaunchedEffect(tag) {
|
||||||
@ -88,48 +89,14 @@ fun EditTagSheet(
|
|||||||
if (viewModel.loading) return
|
if (viewModel.loading) return
|
||||||
|
|
||||||
BottomSheetDialog(
|
BottomSheetDialog(
|
||||||
title = {
|
bottomSheetState = rememberModalBottomSheetState(true),
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
if (viewModel.page == EditTagSheetPage.CustomizeTag) {
|
if (viewModel.page == EditTagSheetPage.CustomizeTag) {
|
||||||
viewModel.save()
|
viewModel.save()
|
||||||
onTagSaved(viewModel.tagName)
|
onTagSaved(viewModel.tagName)
|
||||||
}
|
}
|
||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
}
|
||||||
dismissible = {
|
|
||||||
!(!isCreatingNewTag && viewModel.page == EditTagSheetPage.PickItems)
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
when (viewModel.page) {
|
when (viewModel.page) {
|
||||||
EditTagSheetPage.CreateTag -> CreateNewTagPage(viewModel, it)
|
EditTagSheetPage.CreateTag -> CreateNewTagPage(viewModel, it)
|
||||||
@ -161,55 +128,91 @@ fun CreateNewTagPage(viewModel: EditTagSheetVM, paddingValues: PaddingValues) {
|
|||||||
value = viewModel.tagName,
|
value = viewModel.tagName,
|
||||||
onValueChange = { viewModel.tagName = it }
|
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
|
@Composable
|
||||||
fun PickItems(viewModel: EditTagSheetVM, paddingValues: PaddingValues) {
|
fun PickItems(viewModel: EditTagSheetVM, paddingValues: PaddingValues) {
|
||||||
val columns = LocalGridSettings.current.columnCount - 1
|
val columns = LocalGridSettings.current.columnCount - 1
|
||||||
LazyVerticalGrid(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
Scaffold (
|
||||||
columns = GridCells.Fixed(columns),
|
contentWindowInsets = WindowInsets(0.dp),
|
||||||
contentPadding = paddingValues,
|
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) }) {
|
LazyVerticalGrid(
|
||||||
Text(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
stringResource(id = R.string.tag_select_items),
|
columns = GridCells.Fixed(columns),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
contentPadding = it
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
) {
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
item(span = { GridItemSpan(columns) }) {
|
||||||
)
|
Text(
|
||||||
}
|
stringResource(id = R.string.tag_select_items),
|
||||||
items(viewModel.taggableApps) {
|
style = MaterialTheme.typography.labelMedium,
|
||||||
val iconSize = 32.dp.toPixels()
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
val icon by remember(it.item.key) {
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
viewModel.getIcon(it.item, iconSize.toInt())
|
)
|
||||||
}.collectAsState(null)
|
}
|
||||||
ListItem(item = it, icon = icon, onTagChanged = { tagged ->
|
items(viewModel.taggableApps) {
|
||||||
if (tagged) viewModel.tagItem(it.item)
|
val iconSize = 32.dp.toPixels()
|
||||||
else viewModel.untagItem(it.item)
|
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) }) {
|
if (viewModel.taggableOther.isNotEmpty()) {
|
||||||
Box(
|
item(span = { GridItemSpan(columns) }) {
|
||||||
modifier = Modifier
|
Box(
|
||||||
.padding(vertical = 8.dp)
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.outlineVariant)
|
.padding(vertical = 8.dp)
|
||||||
.fillMaxWidth()
|
.background(MaterialTheme.colorScheme.outlineVariant)
|
||||||
.height(1.dp)
|
.fillMaxWidth()
|
||||||
)
|
.height(1.dp)
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
items(viewModel.taggableOther) {
|
items(viewModel.taggableOther) {
|
||||||
val iconSize = 32.dp.toPixels()
|
val iconSize = 32.dp.toPixels()
|
||||||
val icon by remember(it.item.key) {
|
val icon by remember(it.item.key) {
|
||||||
viewModel.getIcon(it.item, iconSize.toInt())
|
viewModel.getIcon(it.item, iconSize.toInt())
|
||||||
}.collectAsState(null)
|
}.collectAsState(null)
|
||||||
ListItem(item = it, icon = icon, onTagChanged = { tagged ->
|
ListItem(item = it, icon = icon, onTagChanged = { tagged ->
|
||||||
if (tagged) viewModel.tagItem(it.item)
|
if (tagged) viewModel.tagItem(it.item)
|
||||||
else viewModel.untagItem(it.item)
|
else viewModel.untagItem(it.item)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,7 +287,7 @@ fun CustomizeTag(viewModel: EditTagSheetVM, paddingValues: PaddingValues) {
|
|||||||
.clickable {
|
.clickable {
|
||||||
viewModel.openIconPicker()
|
viewModel.openIconPicker()
|
||||||
}
|
}
|
||||||
.size(56.dp)
|
.size(72.dp)
|
||||||
then (
|
then (
|
||||||
if (tagIcon != null) {
|
if (tagIcon != null) {
|
||||||
Modifier
|
Modifier
|
||||||
@ -327,7 +330,7 @@ fun CustomizeTag(viewModel: EditTagSheetVM, paddingValues: PaddingValues) {
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
placeholder = { Text(stringResource(R.string.tag_name)) },
|
label = { Text(stringResource(R.string.tag_name)) },
|
||||||
value = viewModel.tagName,
|
value = viewModel.tagName,
|
||||||
onValueChange = { viewModel.tagName = it },
|
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(
|
SmallMessage(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
icon = Icons.Rounded.Warning,
|
icon = Icons.Rounded.Warning,
|
||||||
text = stringResource(
|
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.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
@ -83,7 +81,7 @@ class EditTagSheetVM : ViewModel(), KoinComponent {
|
|||||||
val oldName = oldTagName
|
val oldName = oldTagName
|
||||||
val newName = tagName
|
val newName = tagName
|
||||||
val tagIcon = tagCustomIcon
|
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 if (oldName != null) tagService.updateTag(oldName, newName = newName, items = taggedItems)
|
||||||
else tagService.createTag(tagName, taggedItems)
|
else tagService.createTag(tagName, taggedItems)
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,6 @@ fun FailedGestureSheet(
|
|||||||
})
|
})
|
||||||
|
|
||||||
BottomSheetDialog(
|
BottomSheetDialog(
|
||||||
title = { Text(actionName) },
|
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
@ -3,21 +3,9 @@ package de.mm20.launcher2.ui.launcher.sheets
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
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.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
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.search.SavableSearchable
|
||||||
import de.mm20.launcher2.ui.R
|
|
||||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||||
|
|
||||||
@ -26,27 +14,7 @@ fun HiddenItemsSheet(
|
|||||||
items: List<SavableSearchable>,
|
items: List<SavableSearchable>,
|
||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
val viewModel: HiddenItemsSheetVM = viewModel()
|
BottomSheetDialog(onDismiss) {
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
|
|
||||||
SearchResultGrid(
|
SearchResultGrid(
|
||||||
items,
|
items,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -64,9 +65,9 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
import de.mm20.launcher2.widgets.CalendarWidget
|
|
||||||
import de.mm20.launcher2.widgets.AppWidget
|
import de.mm20.launcher2.widgets.AppWidget
|
||||||
import de.mm20.launcher2.widgets.AppWidgetConfig
|
import de.mm20.launcher2.widgets.AppWidgetConfig
|
||||||
|
import de.mm20.launcher2.widgets.CalendarWidget
|
||||||
import de.mm20.launcher2.widgets.FavoritesWidget
|
import de.mm20.launcher2.widgets.FavoritesWidget
|
||||||
import de.mm20.launcher2.widgets.MusicWidget
|
import de.mm20.launcher2.widgets.MusicWidget
|
||||||
import de.mm20.launcher2.widgets.NotesWidget
|
import de.mm20.launcher2.widgets.NotesWidget
|
||||||
@ -277,10 +278,8 @@ fun WidgetPickerSheet(
|
|||||||
val query by viewModel.searchQuery.collectAsState("")
|
val query by viewModel.searchQuery.collectAsState("")
|
||||||
|
|
||||||
BottomSheetDialog(
|
BottomSheetDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss
|
||||||
title = {
|
) {
|
||||||
Text(title)
|
|
||||||
}) {
|
|
||||||
val builtIn by viewModel.builtInWidgets.collectAsState(emptyList())
|
val builtIn by viewModel.builtInWidgets.collectAsState(emptyList())
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -299,6 +298,7 @@ fun WidgetPickerSheet(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
|
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
|
||||||
|
windowInsets = WindowInsets(0.dp),
|
||||||
query = query,
|
query = query,
|
||||||
onQueryChange = { viewModel.search(it) },
|
onQueryChange = { viewModel.search(it) },
|
||||||
onSearch = {},
|
onSearch = {},
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.width
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
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.AccessTime
|
||||||
import androidx.compose.material.icons.rounded.Alarm
|
import androidx.compose.material.icons.rounded.Alarm
|
||||||
import androidx.compose.material.icons.rounded.AlignVerticalBottom
|
import androidx.compose.material.icons.rounded.AlignVerticalBottom
|
||||||
@ -370,11 +371,16 @@ fun ConfigureClockWidgetSheet(
|
|||||||
icon = {
|
icon = {
|
||||||
SegmentedButtonDefaults.Icon(
|
SegmentedButtonDefaults.Icon(
|
||||||
active = compact == false,
|
active = compact == false,
|
||||||
|
activeContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.HorizontalSplit,
|
imageVector = Icons.Rounded.HorizontalSplit,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(SegmentedButtonDefaults.IconSize)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -390,11 +396,16 @@ fun ConfigureClockWidgetSheet(
|
|||||||
icon = {
|
icon = {
|
||||||
SegmentedButtonDefaults.Icon(
|
SegmentedButtonDefaults.Icon(
|
||||||
active = compact == true,
|
active = compact == true,
|
||||||
|
activeContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.VerticalSplit,
|
imageVector = Icons.Rounded.VerticalSplit,
|
||||||
contentDescription = null,
|
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.AnimatedVisibility
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.Canvas
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -43,11 +42,13 @@ import androidx.compose.material3.DropdownMenuItem
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
import androidx.compose.material3.SnackbarResult
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@ -57,7 +58,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@ -398,39 +398,11 @@ fun NoteWidgetConflictResolveSheet(
|
|||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
var selectedStrategy by remember { mutableStateOf<LinkedFileConflictStrategy?>(null) }
|
var selectedStrategy by remember { mutableStateOf<LinkedFileConflictStrategy?>(null) }
|
||||||
|
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
BottomSheetDialog(
|
BottomSheetDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
confirmButton = {
|
bottomSheetState = bottomSheetState
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -463,6 +435,40 @@ fun NoteWidgetConflictResolveSheet(
|
|||||||
selected = selectedStrategy == LinkedFileConflictStrategy.KeepFile,
|
selected = selectedStrategy == LinkedFileConflictStrategy.KeepFile,
|
||||||
onSelect = { 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),
|
.padding(16.dp),
|
||||||
text = content, onTextChange = {}
|
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(
|
IconButton(
|
||||||
modifier = Modifier.align(Alignment.TopEnd),
|
modifier = Modifier.align(Alignment.TopEnd),
|
||||||
onClick = onSelect
|
onClick = onSelect
|
||||||
|
|||||||
@ -2,26 +2,24 @@ package de.mm20.launcher2.ui.settings.backup
|
|||||||
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.*
|
import androidx.compose.material.icons.rounded.CheckCircleOutline
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
import de.mm20.launcher2.ui.component.LargeMessage
|
import de.mm20.launcher2.ui.component.LargeMessage
|
||||||
import de.mm20.launcher2.ui.component.SmallMessage
|
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@ -50,68 +48,29 @@ fun CreateBackupSheet(
|
|||||||
|
|
||||||
val state by viewModel.state
|
val state by viewModel.state
|
||||||
|
|
||||||
BottomSheetDialog(
|
if (state == CreateBackupState.BackingUp || state == CreateBackupState.BackedUp) {
|
||||||
onDismissRequest = onDismissRequest,
|
BottomSheetDialog(onDismissRequest) {
|
||||||
title = {
|
if (state == CreateBackupState.BackingUp) {
|
||||||
Text(
|
Box(
|
||||||
stringResource(id = R.string.preference_backup),
|
modifier = Modifier
|
||||||
)
|
.fillMaxWidth()
|
||||||
},
|
.aspectRatio(1f),
|
||||||
confirmButton = {
|
contentAlignment = Alignment.Center
|
||||||
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
|
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.close))
|
CircularProgressIndicator(
|
||||||
}
|
modifier = Modifier.size(48.dp)
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
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
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)) {
|
if (!isOpenSearch && value is GestureAction.Launch && (showAppPicker || app == null)) {
|
||||||
SearchablePicker(
|
SearchablePicker(
|
||||||
title = { Text(title) },
|
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showAppPicker = false
|
showAppPicker = false
|
||||||
if (app == null) onValueChanged(GestureAction.NoAction)
|
if (app == null) onValueChanged(GestureAction.NoAction)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import androidx.compose.foundation.border
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
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.Add
|
||||||
import androidx.compose.material.icons.rounded.Android
|
import androidx.compose.material.icons.rounded.Android
|
||||||
import androidx.compose.material.icons.rounded.ArrowDropDown
|
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.ManageSearch
|
||||||
import androidx.compose.material.icons.rounded.MoreVert
|
|
||||||
import androidx.compose.material.icons.rounded.RemoveCircleOutline
|
import androidx.compose.material.icons.rounded.RemoveCircleOutline
|
||||||
import androidx.compose.material.icons.rounded.ToggleOn
|
import androidx.compose.material.icons.rounded.ToggleOn
|
||||||
import androidx.compose.material.icons.rounded.TravelExplore
|
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.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
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.actions.SearchActionIcon
|
||||||
import de.mm20.launcher2.searchactions.builders.AppSearchActionBuilder
|
import de.mm20.launcher2.searchactions.builders.AppSearchActionBuilder
|
||||||
import de.mm20.launcher2.searchactions.builders.CustomIntentActionBuilder
|
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.CustomWebsearchActionBuilder
|
||||||
|
import de.mm20.launcher2.searchactions.builders.CustomizableSearchActionBuilder
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
import de.mm20.launcher2.ui.component.ExperimentalBadge
|
import de.mm20.launcher2.ui.component.ExperimentalBadge
|
||||||
@ -99,13 +97,11 @@ fun EditSearchActionSheet(
|
|||||||
initialSearchAction: CustomizableSearchActionBuilder?,
|
initialSearchAction: CustomizableSearchActionBuilder?,
|
||||||
onSave: (CustomizableSearchActionBuilder) -> Unit,
|
onSave: (CustomizableSearchActionBuilder) -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onDelete: () -> Unit = {},
|
|
||||||
) {
|
) {
|
||||||
val viewModel: EditSearchActionSheetVM = viewModel()
|
val viewModel: EditSearchActionSheetVM = viewModel()
|
||||||
LaunchedEffect(initialSearchAction) {
|
LaunchedEffect(initialSearchAction) {
|
||||||
viewModel.init(initialSearchAction)
|
viewModel.init(initialSearchAction)
|
||||||
}
|
}
|
||||||
val createNew by viewModel.createNew
|
|
||||||
val page by viewModel.currentPage
|
val page by viewModel.currentPage
|
||||||
|
|
||||||
val searchAction by viewModel.searchAction
|
val searchAction by viewModel.searchAction
|
||||||
@ -114,10 +110,7 @@ fun EditSearchActionSheet(
|
|||||||
viewModel.onDismiss()
|
viewModel.onDismiss()
|
||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
},
|
||||||
dismissible = {
|
footerItems = when (page) {
|
||||||
page != EditSearchActionPage.PickIcon
|
|
||||||
},
|
|
||||||
confirmButton = when (page) {
|
|
||||||
EditSearchActionPage.CustomizeAppSearch,
|
EditSearchActionPage.CustomizeAppSearch,
|
||||||
EditSearchActionPage.CustomizeWebSearch,
|
EditSearchActionPage.CustomizeWebSearch,
|
||||||
EditSearchActionPage.CustomizeCustomIntent -> {
|
EditSearchActionPage.CustomizeCustomIntent -> {
|
||||||
@ -170,37 +163,6 @@ fun EditSearchActionSheet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> null
|
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) {
|
when (page) {
|
||||||
EditSearchActionPage.SelectType -> SelectTypePage(viewModel, it)
|
EditSearchActionPage.SelectType -> SelectTypePage(viewModel, it)
|
||||||
@ -741,7 +703,7 @@ fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) {
|
|||||||
if (action?.customIcon == null) {
|
if (action?.customIcon == null) {
|
||||||
|
|
||||||
val availableIcons =
|
val availableIcons =
|
||||||
remember { SearchActionIcon.values().filter { it != SearchActionIcon.Custom } }
|
remember { SearchActionIcon.entries.filter { it != SearchActionIcon.Custom } }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(paddingValues)
|
modifier = Modifier.padding(paddingValues)
|
||||||
@ -787,7 +749,7 @@ fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.padding(top = 16.dp, bottom = 24.dp),
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
onClick = { pickIconLauncher.launch("image/*") }) {
|
onClick = { pickIconLauncher.launch("image/*") }) {
|
||||||
Text(stringResource(R.string.websearch_dialog_custom_icon))
|
Text(stringResource(R.string.websearch_dialog_custom_icon))
|
||||||
}
|
}
|
||||||
@ -795,7 +757,8 @@ fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) {
|
|||||||
} else {
|
} else {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier.padding(paddingValues)
|
modifier = Modifier.padding(paddingValues).fillMaxWidth()
|
||||||
|
|
||||||
) {
|
) {
|
||||||
SearchActionIconTile {
|
SearchActionIconTile {
|
||||||
SearchActionIcon(builder = action!!, size = 24.dp)
|
SearchActionIcon(builder = action!!, size = 24.dp)
|
||||||
@ -817,15 +780,17 @@ fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) {
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 24.dp)
|
.padding(top = 24.dp)
|
||||||
.horizontalScroll(rememberScrollState())
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
|
space = 16.dp,
|
||||||
|
alignment = Alignment.End
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
modifier = Modifier.padding(start = 16.dp),
|
|
||||||
onClick = { pickIconLauncher.launch("image/*") }) {
|
onClick = { pickIconLauncher.launch("image/*") }) {
|
||||||
Text(stringResource(R.string.websearch_dialog_replace_icon))
|
Text(stringResource(R.string.websearch_dialog_replace_icon))
|
||||||
}
|
}
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
modifier = Modifier.padding(start = 16.dp),
|
|
||||||
onClick = { viewModel.setIcon(SearchActionIcon.Search) },
|
onClick = { viewModel.setIcon(SearchActionIcon.Search) },
|
||||||
colors = ButtonDefaults.outlinedButtonColors(
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
contentColor = MaterialTheme.colorScheme.error
|
contentColor = MaterialTheme.colorScheme.error
|
||||||
|
|||||||
@ -289,9 +289,6 @@ fun SearchActionsSettingsScreen() {
|
|||||||
},
|
},
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
viewModel.dismissDialogs()
|
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_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_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_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_select_items">Select items:</string>
|
||||||
<string name="tag_name">Tag name</string>
|
<string name="tag_name">Tag name</string>
|
||||||
<plurals name="tag_selected_items">
|
<plurals name="tag_selected_items">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user