@@ -2,15 +2,22 @@ package de.mm20.launcher2.ui.common
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.FlexibleBottomAppBar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -19,138 +26,160 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.backup.BackupCompatibility
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.BottomSheet
|
||||
import de.mm20.launcher2.ui.component.LargeMessage
|
||||
import de.mm20.launcher2.ui.component.SmallMessage
|
||||
|
||||
@Composable
|
||||
fun RestoreBackupSheet(
|
||||
uri: Uri,
|
||||
uri: Uri?,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
val viewModel: RestoreBackupSheetVM = viewModel()
|
||||
BottomSheet(
|
||||
expanded = uri != null,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
|
||||
LaunchedEffect(uri) {
|
||||
viewModel.setInputUri(uri)
|
||||
}
|
||||
val viewModel: RestoreBackupSheetVM = viewModel()
|
||||
|
||||
val state by viewModel.state
|
||||
val compatibility by viewModel.compatibility
|
||||
if (uri != null) {
|
||||
LaunchedEffect(uri) {
|
||||
viewModel.setInputUri(uri)
|
||||
}
|
||||
}
|
||||
|
||||
val state by viewModel.state
|
||||
val compatibility by viewModel.compatibility
|
||||
|
||||
BottomSheetDialog(onDismissRequest) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(it),
|
||||
horizontalAlignment = Alignment.End,
|
||||
.wrapContentHeight(),
|
||||
) {
|
||||
when (state) {
|
||||
RestoreBackupState.Parsing -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f),
|
||||
contentAlignment = Alignment.Center
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.preference_restore))
|
||||
},
|
||||
actions = {
|
||||
FilledTonalIconButton(
|
||||
onClick = onDismissRequest
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
Icon(painterResource(R.drawable.close_24px), stringResource(R.string.close))
|
||||
}
|
||||
}
|
||||
|
||||
RestoreBackupState.InvalidFile -> {
|
||||
LargeMessage(
|
||||
modifier = Modifier.aspectRatio(1f),
|
||||
icon = R.drawable.error_48px,
|
||||
text = stringResource(id = R.string.restore_invalid_file)
|
||||
)
|
||||
}
|
||||
|
||||
RestoreBackupState.Ready -> {
|
||||
val metadata by viewModel.metadata
|
||||
|
||||
if (metadata != null) {
|
||||
Column {
|
||||
SmallMessage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(bottom = 16.dp),
|
||||
icon = R.drawable.info_24px,
|
||||
text = stringResource(
|
||||
R.string.restore_meta,
|
||||
DateUtils.formatDateTime(
|
||||
LocalContext.current,
|
||||
metadata!!.timestamp,
|
||||
DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR
|
||||
),
|
||||
metadata!!.deviceName,
|
||||
stringResource(R.string.app_name) + " " + metadata!!.appVersionName,
|
||||
)
|
||||
)
|
||||
AnimatedContent(
|
||||
state,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
when (it) {
|
||||
RestoreBackupState.Parsing, RestoreBackupState.Restoring -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
if (compatibility == BackupCompatibility.Incompatible) {
|
||||
LargeMessage(
|
||||
modifier = Modifier.aspectRatio(1f),
|
||||
icon = R.drawable.error_48px,
|
||||
}
|
||||
}
|
||||
|
||||
RestoreBackupState.InvalidFile -> {
|
||||
LargeMessage(
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
icon = R.drawable.error_48px,
|
||||
text = stringResource(id = R.string.restore_invalid_file)
|
||||
)
|
||||
}
|
||||
|
||||
RestoreBackupState.Ready -> {
|
||||
val metadata by viewModel.metadata
|
||||
|
||||
if (metadata != null) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
SmallMessage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding()
|
||||
.wrapContentHeight(),
|
||||
icon = R.drawable.info_24px,
|
||||
text = stringResource(
|
||||
id = R.string.restore_incompatible_file,
|
||||
stringResource(R.string.app_name)
|
||||
R.string.restore_meta,
|
||||
DateUtils.formatDateTime(
|
||||
LocalContext.current,
|
||||
metadata!!.timestamp,
|
||||
DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR
|
||||
),
|
||||
metadata!!.deviceName,
|
||||
stringResource(R.string.app_name) + " " + metadata!!.appVersionName,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
if (compatibility == BackupCompatibility.PartiallyCompatible) {
|
||||
SmallMessage(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
if (compatibility == BackupCompatibility.Incompatible) {
|
||||
LargeMessage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
icon = R.drawable.warning_24px,
|
||||
text =
|
||||
stringResource(
|
||||
R.string.restore_different_minor_version,
|
||||
stringResource(R.string.app_name)
|
||||
)
|
||||
.navigationBarsPadding(),
|
||||
icon = R.drawable.error_48px,
|
||||
text = stringResource(
|
||||
id = R.string.restore_incompatible_file,
|
||||
stringResource(R.string.app_name)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
if (compatibility == BackupCompatibility.PartiallyCompatible) {
|
||||
SmallMessage(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding(),
|
||||
icon = R.drawable.warning_24px,
|
||||
text =
|
||||
stringResource(
|
||||
R.string.restore_different_minor_version,
|
||||
stringResource(R.string.app_name)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RestoreBackupState.Restoring -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(48.dp)
|
||||
RestoreBackupState.Restored -> {
|
||||
LargeMessage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding(),
|
||||
icon = R.drawable.check_circle_48px,
|
||||
text = stringResource(
|
||||
id = R.string.restore_complete
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
RestoreBackupState.Restored -> {
|
||||
LargeMessage(
|
||||
modifier = Modifier.aspectRatio(1f),
|
||||
icon = R.drawable.check_circle_48px,
|
||||
text = stringResource(
|
||||
id = R.string.restore_complete
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (state == RestoreBackupState.Ready && compatibility != BackupCompatibility.Incompatible) {
|
||||
Button(
|
||||
onClick = { viewModel.restore() }
|
||||
AnimatedVisibility(state == RestoreBackupState.Ready && compatibility != BackupCompatibility.Incompatible) {
|
||||
FlexibleBottomAppBar(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
Text(stringResource(R.string.preference_restore))
|
||||
Button(
|
||||
onClick = { viewModel.restore() },
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
) {
|
||||
Text(stringResource(R.string.preference_restore))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
|
||||
@@ -38,7 +38,7 @@ fun SearchablePicker(
|
||||
) {
|
||||
val viewModel: SearchablePickerVM = viewModel()
|
||||
|
||||
BottomSheetDialog(onDismissRequest = onDismissRequest) {
|
||||
DismissableBottomSheet(onDismissRequest = onDismissRequest) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
@@ -2,13 +2,20 @@ package de.mm20.launcher2.ui.common
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DockedSearchBar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SearchBar
|
||||
import androidx.compose.material3.SearchBarDefaults
|
||||
@@ -21,32 +28,43 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.SmallMessage
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun WeatherLocationSearchDialog(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewModel: WeatherLocationSearchDialogVM = viewModel()
|
||||
val isSearching by viewModel.isSearchingLocation
|
||||
val locations by viewModel.locationResults
|
||||
|
||||
BottomSheetDialog(onDismissRequest) {
|
||||
DismissableBottomSheet(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewModel: WeatherLocationSearchDialogVM = viewModel()
|
||||
val isSearching by viewModel.isSearchingLocation
|
||||
val locations by viewModel.locationResults
|
||||
|
||||
var query by remember { mutableStateOf("") }
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
SearchBar(
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
windowInsets = WindowInsets(0.dp),
|
||||
DockedSearchBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
bottom = 8.dp,
|
||||
start = 16.dp,
|
||||
end = 16.dp
|
||||
),
|
||||
expanded = false,
|
||||
onExpandedChange = {},
|
||||
inputField = {
|
||||
@@ -74,11 +92,13 @@ fun WeatherLocationSearchDialog(
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
)
|
||||
} else if (locations.isNotEmpty()) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentPadding = it
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues()
|
||||
) {
|
||||
items(locations) {
|
||||
Text(
|
||||
@@ -89,16 +109,15 @@ fun WeatherLocationSearchDialog(
|
||||
viewModel.setLocation(it)
|
||||
onDismissRequest()
|
||||
}
|
||||
.padding(
|
||||
vertical = 16.dp
|
||||
)
|
||||
.padding(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
} else if (query.isNotEmpty()) {
|
||||
SmallMessage(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
icon = R.drawable.error_24px,
|
||||
text = stringResource(R.string.weather_location_search_no_result)
|
||||
)
|
||||
|
||||
@@ -20,16 +20,16 @@ class WeatherLocationSearchDialogVM: ViewModel(), KoinComponent {
|
||||
|
||||
private var debounceSearchJob: Job? = null
|
||||
suspend fun searchLocation(query: String) {
|
||||
debounceSearchJob?.cancelAndJoin()
|
||||
if (query.isBlank()) {
|
||||
locationResults.value = emptyList()
|
||||
isSearchingLocation.value = false
|
||||
return
|
||||
}
|
||||
debounceSearchJob?.cancelAndJoin()
|
||||
withContext(coroutineContext) {
|
||||
debounceSearchJob = launch {
|
||||
delay(1000)
|
||||
isSearchingLocation.value = true
|
||||
delay(1000)
|
||||
locationResults.value = repository.searchLocations(query).first()
|
||||
isSearchingLocation.value = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import androidx.activity.compose.PredictiveBackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColor
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.core.rememberTransition
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.overlays.Overlay
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
@Composable
|
||||
fun BottomSheet(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit = {},
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
BottomSheet(
|
||||
state = expanded,
|
||||
expanded = { it },
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-dismissable bottom sheet.
|
||||
*/
|
||||
@Composable
|
||||
fun <T>BottomSheet(
|
||||
state: T,
|
||||
expanded: (T) -> Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
content: @Composable (state: T) -> Unit,
|
||||
) {
|
||||
val expandedState = remember { MutableTransitionState(state) }
|
||||
expandedState.targetState = state
|
||||
|
||||
val expandedCurrent = expanded(expandedState.currentState)
|
||||
val expandedTarget = expanded(expandedState.targetState)
|
||||
|
||||
|
||||
if (expandedCurrent || expandedTarget) {
|
||||
|
||||
Overlay {
|
||||
val backProgress = remember { Animatable(0f) }
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
LaunchedEffect(Unit) {
|
||||
focusManager.clearFocus(true)
|
||||
}
|
||||
|
||||
PredictiveBackHandler {
|
||||
try {
|
||||
it.collect {
|
||||
backProgress.snapTo(it.progress)
|
||||
}
|
||||
onDismissRequest()
|
||||
backProgress.animateTo(0f)
|
||||
} catch (_: CancellationException) {
|
||||
backProgress.animateTo(0f)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val transition = rememberTransition(expandedState)
|
||||
|
||||
val scrimColor by transition.animateColor(
|
||||
transitionSpec = { MaterialTheme.motionScheme.slowEffectsSpec() },
|
||||
) { if (expanded(it)) BottomSheetDefaults.ScrimColor else Color.Transparent }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(scrimColor)
|
||||
.graphicsLayer {
|
||||
scaleX = 1f - backProgress.value * 0.1f
|
||||
scaleY = 1f - backProgress.value * 0.1f
|
||||
transformOrigin = TransformOrigin(0.5f, 1f)
|
||||
}
|
||||
.imePadding()
|
||||
) {
|
||||
|
||||
Box(Modifier
|
||||
.fillMaxSize()
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures {
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
if (expanded(transition.currentState)) {
|
||||
DisposableEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
onDispose {
|
||||
focusRequester.freeFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
transition.AnimatedVisibility(
|
||||
visible = { expanded(it) },
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
enter = slideInVertically { it },
|
||||
exit = slideOutVertically { it }
|
||||
) {
|
||||
Surface(
|
||||
shadowElevation = 1.dp,
|
||||
shape = BottomSheetDefaults.ExpandedShape,
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.imePadding()
|
||||
.fillMaxWidth()
|
||||
.widthIn(max = BottomSheetDefaults.SheetMaxWidth)
|
||||
.align(Alignment.BottomCenter)
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
content(
|
||||
if (expandedTarget) expandedState.targetState else expandedState.currentState
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.ktx.toDp
|
||||
|
||||
@Composable
|
||||
fun BottomSheetDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
bottomSheetState: SheetState = rememberModalBottomSheetState(),
|
||||
windowInsets: WindowInsets = WindowInsets(left = 24.dp, right = 24.dp),
|
||||
content: @Composable (paddingValues: PaddingValues) -> Unit,
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.padding(top = 8.dp),
|
||||
sheetState = bottomSheetState,
|
||||
onDismissRequest = onDismissRequest,
|
||||
contentWindowInsets = { windowInsets },
|
||||
containerColor = BottomSheetDefaults.ContainerColor
|
||||
) {
|
||||
content(PaddingValues(
|
||||
bottom = WindowInsets.navigationBars.getBottom(LocalDensity.current).toDp() + 8.dp,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.PredictiveBackHandler
|
||||
import androidx.compose.animation.animateColor
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.core.rememberTransition
|
||||
import androidx.compose.foundation.LocalOverscrollFactory
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.AnchoredDraggableDefaults
|
||||
import androidx.compose.foundation.gestures.AnchoredDraggableState
|
||||
import androidx.compose.foundation.gestures.DraggableAnchors
|
||||
import androidx.compose.foundation.gestures.FlingBehavior
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.ScrollScope
|
||||
import androidx.compose.foundation.gestures.anchoredDraggable
|
||||
import androidx.compose.foundation.gestures.animateTo
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
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.WindowInsets
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
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.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.overlays.Overlay
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
@Composable
|
||||
fun DismissableBottomSheet(
|
||||
expanded: Boolean = true,
|
||||
onDismissRequest: () -> Unit,
|
||||
content: @Composable (contentPadding: PaddingValues) -> Unit,
|
||||
) {
|
||||
DismissableBottomSheet(
|
||||
state = expanded,
|
||||
expanded = { it },
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
content(PaddingValues())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A bottom sheet that can be dismissed by swiping down.
|
||||
*
|
||||
* Use `BottomSheet` for a sheet that can't be dismissed.
|
||||
*
|
||||
* @param expanded Whether the bottom sheet is visible.
|
||||
* @param onDismissRequest Called when the bottom sheet is dismissed.
|
||||
* @param content The content of the bottom sheet.
|
||||
*/
|
||||
@Composable
|
||||
fun <T> DismissableBottomSheet(
|
||||
state: T,
|
||||
expanded: (T) -> Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
content: @Composable (state: T) -> Unit,
|
||||
) {
|
||||
|
||||
val expandedState = remember { MutableTransitionState(state) }
|
||||
expandedState.targetState = state
|
||||
|
||||
val expandedCurrent = expanded(expandedState.currentState)
|
||||
val expandedTarget = expanded(expandedState.targetState)
|
||||
|
||||
if (expandedCurrent || expandedTarget) {
|
||||
|
||||
Overlay {
|
||||
val focusManager = LocalFocusManager.current
|
||||
LaunchedEffect(Unit) {
|
||||
focusManager.clearFocus(true)
|
||||
}
|
||||
|
||||
val backProgress = remember { Animatable(0f) }
|
||||
|
||||
val draggableState = remember {
|
||||
AnchoredDraggableState(SheetValue.Hidden, DraggableAnchors {
|
||||
SheetValue.Hidden at 0f
|
||||
})
|
||||
}
|
||||
|
||||
PredictiveBackHandler {
|
||||
try {
|
||||
it.collect {
|
||||
backProgress.snapTo(it.progress)
|
||||
}
|
||||
onDismissRequest()
|
||||
} catch (_: CancellationException) {
|
||||
backProgress.animateTo(1f)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val motionSpec = MaterialTheme.motionScheme.defaultEffectsSpec<Float>()
|
||||
|
||||
val transition = rememberTransition(expandedState)
|
||||
|
||||
val scrimColor by transition.animateColor(
|
||||
transitionSpec = { MaterialTheme.motionScheme.slowEffectsSpec() },
|
||||
) { if (expanded(it)) BottomSheetDefaults.ScrimColor else Color.Transparent }
|
||||
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(scrimColor)
|
||||
.graphicsLayer {
|
||||
scaleX = 1f - backProgress.value * 0.1f
|
||||
scaleY = 1f - backProgress.value * 0.1f
|
||||
transformOrigin = TransformOrigin(0.5f, 1f)
|
||||
}
|
||||
.imePadding()
|
||||
) {
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures {
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
if (draggableState.anchors.size > 1) {
|
||||
LaunchedEffect(Unit) {
|
||||
draggableState.animateTo(SheetValue.PartiallyExpanded, motionSpec)
|
||||
}
|
||||
|
||||
if (draggableState.settledValue != SheetValue.Hidden) {
|
||||
DisposableEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
onDispose {
|
||||
focusRequester.freeFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(expandedTarget) {
|
||||
if (expandedTarget != expandedCurrent && draggableState.settledValue == draggableState.targetValue) {
|
||||
if (!expandedTarget && draggableState.settledValue != SheetValue.Hidden) {
|
||||
draggableState.animateTo(SheetValue.Hidden, motionSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(draggableState.settledValue) {
|
||||
if (draggableState.settledValue == draggableState.targetValue) {
|
||||
if (draggableState.settledValue == SheetValue.Hidden) {
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val containerHeight = maxHeight.toPixels()
|
||||
var sheetHeight by remember { mutableIntStateOf(0) }
|
||||
|
||||
val flingBehavior =
|
||||
AnchoredDraggableDefaults.flingBehavior(
|
||||
state = draggableState,
|
||||
animationSpec = motionSpec,
|
||||
)
|
||||
|
||||
Surface(
|
||||
shadowElevation = 1.dp,
|
||||
shape = BottomSheetDefaults.ExpandedShape,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.focusRequester(focusRequester)
|
||||
.nestedScroll(
|
||||
remember(draggableState) {
|
||||
BottomSheetNestedScrollConnection(
|
||||
anchoredDraggableState = draggableState,
|
||||
flingBehavior = flingBehavior,
|
||||
)
|
||||
}
|
||||
)
|
||||
.onSizeChanged {
|
||||
sheetHeight = it.height
|
||||
draggableState.updateAnchors(
|
||||
DraggableAnchors {
|
||||
SheetValue.Hidden at 0f
|
||||
if (it.height > containerHeight * 0.5f) {
|
||||
SheetValue.PartiallyExpanded at containerHeight * -0.5f
|
||||
SheetValue.Expanded at it.height * -1f
|
||||
} else {
|
||||
SheetValue.PartiallyExpanded at it.height * -1f
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.widthIn(max = BottomSheetDefaults.SheetMaxWidth)
|
||||
.align(Alignment.BottomCenter)
|
||||
.wrapContentHeight()
|
||||
.heightIn(max = maxHeight)
|
||||
.graphicsLayer {
|
||||
translationY = draggableState.offset.coerceAtMost(0f) + sheetHeight
|
||||
}
|
||||
.anchoredDraggable(
|
||||
state = draggableState,
|
||||
orientation = Orientation.Vertical,
|
||||
flingBehavior = flingBehavior,
|
||||
)
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalOverscrollFactory provides null
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.consumeWindowInsets(WindowInsets.ime),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
BottomSheetDefaults.DragHandle()
|
||||
Box {
|
||||
content(
|
||||
if (expandedTarget) expandedState.targetState else expandedState.currentState
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BottomSheetNestedScrollConnection(
|
||||
anchoredDraggableState: AnchoredDraggableState<SheetValue>,
|
||||
flingBehavior: FlingBehavior,
|
||||
): NestedScrollConnection =
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
val delta = available.y
|
||||
return if (delta < 0 && source == NestedScrollSource.UserInput) {
|
||||
anchoredDraggableState.dispatchRawDelta(delta).toOffset()
|
||||
} else {
|
||||
Offset.Zero
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
source: NestedScrollSource,
|
||||
): Offset {
|
||||
return if (source == NestedScrollSource.UserInput) {
|
||||
anchoredDraggableState.dispatchRawDelta(available.y).toOffset()
|
||||
} else {
|
||||
Offset.Zero
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
val toFling = available.y
|
||||
val currentOffset = anchoredDraggableState.requireOffset()
|
||||
val minAnchor = anchoredDraggableState.anchors.minPosition()
|
||||
return if (toFling < 0 && currentOffset > minAnchor) {
|
||||
anchoredDraggableState.anchoredDrag(flingBehavior, 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 {
|
||||
val toFling = available.y
|
||||
val consumedByAnchoredDraggableFling =
|
||||
anchoredDraggableState.anchoredDrag(flingBehavior, toFling)
|
||||
return Velocity(consumed.x, consumedByAnchoredDraggableFling)
|
||||
}
|
||||
|
||||
private fun Float.toOffset(): Offset =
|
||||
Offset(x = 0f, y = this)
|
||||
|
||||
private fun AnchoredDraggableState<SheetValue>.newOffsetForDelta(delta: Float) =
|
||||
((if (offset.isNaN()) 0f else offset) + delta).coerceIn(
|
||||
anchoredDraggableState.anchors.minPosition(),
|
||||
anchoredDraggableState.anchors.maxPosition(),
|
||||
)
|
||||
|
||||
private suspend fun AnchoredDraggableState<SheetValue>.anchoredDrag(
|
||||
flingBehavior: FlingBehavior,
|
||||
initialVelocity: Float
|
||||
): Float {
|
||||
var consumedVelocity = 0f
|
||||
anchoredDraggableState.anchoredDrag {
|
||||
val scrollScope =
|
||||
object : ScrollScope {
|
||||
override fun scrollBy(pixels: Float): Float {
|
||||
val newOffset = newOffsetForDelta(pixels)
|
||||
val consumed = newOffset - offset
|
||||
dragTo(newOffset)
|
||||
return consumed
|
||||
}
|
||||
}
|
||||
consumedVelocity = with(flingBehavior) { scrollScope.performFling(initialVelocity) }
|
||||
}
|
||||
return consumedVelocity
|
||||
}
|
||||
}
|
||||
@@ -355,11 +355,10 @@ fun SearchColumn(
|
||||
|
||||
|
||||
val sheetManager = LocalBottomSheetManager.current
|
||||
if (sheetManager.hiddenItemsSheetShown.value) {
|
||||
HiddenItemsSheet(
|
||||
items = hiddenResults,
|
||||
onDismiss = { sheetManager.dismissHiddenItemsSheet() })
|
||||
}
|
||||
HiddenItemsSheet(
|
||||
expanded = sheetManager.hiddenItemsSheetShown.value,
|
||||
items = hiddenResults,
|
||||
onDismiss = { sheetManager.dismissHiddenItemsSheet() })
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,10 +26,11 @@ import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
@@ -97,7 +98,7 @@ import de.mm20.launcher2.search.calendar.CalendarListType
|
||||
import de.mm20.launcher2.themes.colors.atTone
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.base.LocalAppWidgetHost
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.DragResizeHandle
|
||||
import de.mm20.launcher2.ui.component.LargeMessage
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
@@ -113,8 +114,8 @@ import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
||||
import de.mm20.launcher2.ui.settings.SettingsActivity
|
||||
import de.mm20.launcher2.widgets.AppWidget
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.AppsWidget
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.MusicWidget
|
||||
import de.mm20.launcher2.widgets.NotesWidget
|
||||
import de.mm20.launcher2.widgets.WeatherWidget
|
||||
@@ -129,19 +130,20 @@ import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun ConfigureWidgetSheet(
|
||||
expanded: Boolean,
|
||||
widget: Widget,
|
||||
onWidgetUpdated: (Widget) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
BottomSheetDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
windowInsets = WindowInsets()
|
||||
DismissableBottomSheet(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismiss
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = if (widget is AppWidget) 8.dp else 16.dp)
|
||||
.padding(it)
|
||||
.padding(horizontal = if (widget is AppWidget) 8.dp else 16.dp, vertical = 16.dp)
|
||||
.navigationBarsPadding()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
when (widget) {
|
||||
@@ -648,28 +650,28 @@ fun ColumnScope.ConfigureAppWidget(
|
||||
)
|
||||
Text(stringResource(R.string.widget_action_replace))
|
||||
}
|
||||
if (replaceWidget) {
|
||||
WidgetPickerSheet(
|
||||
onDismiss = { replaceWidget = false },
|
||||
onWidgetSelected = {
|
||||
val updatedWidget = when (it) {
|
||||
is AppWidget -> widget.copy(
|
||||
config = widget.config.copy(
|
||||
widgetId = it.config.widgetId
|
||||
)
|
||||
WidgetPickerSheet(
|
||||
expanded = replaceWidget,
|
||||
onDismiss = { replaceWidget = false },
|
||||
onWidgetSelected = {
|
||||
val updatedWidget = when (it) {
|
||||
is AppWidget -> widget.copy(
|
||||
config = widget.config.copy(
|
||||
widgetId = it.config.widgetId
|
||||
)
|
||||
)
|
||||
|
||||
is WeatherWidget -> it.copy(id = widget.id)
|
||||
is MusicWidget -> it.copy(id = widget.id)
|
||||
is CalendarWidget -> it.copy(id = widget.id)
|
||||
is AppsWidget -> it.copy(id = widget.id)
|
||||
is NotesWidget -> it.copy(id = widget.id)
|
||||
}
|
||||
onWidgetUpdated(updatedWidget)
|
||||
replaceWidget = false
|
||||
is WeatherWidget -> it.copy(id = widget.id)
|
||||
is MusicWidget -> it.copy(id = widget.id)
|
||||
is CalendarWidget -> it.copy(id = widget.id)
|
||||
is AppsWidget -> it.copy(id = widget.id)
|
||||
is NotesWidget -> it.copy(id = widget.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
onWidgetUpdated(updatedWidget)
|
||||
replaceWidget = false
|
||||
}
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package de.mm20.launcher2.ui.launcher.sheets
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
@@ -13,7 +18,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MenuAnchorType
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -21,10 +25,10 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -39,27 +43,35 @@ import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.searchable.VisibilityLevel
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.common.IconPicker
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.OutlinedTagsInputField
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun CustomizeSearchableSheet(
|
||||
searchable: SavableSearchable,
|
||||
searchable: SavableSearchable?,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewModel: CustomizeSearchableSheetVM =
|
||||
remember(searchable.key) { CustomizeSearchableSheetVM(searchable) }
|
||||
|
||||
val pickIcon by viewModel.isIconPickerOpen
|
||||
DismissableBottomSheet(
|
||||
state = searchable,
|
||||
expanded = { it != null },
|
||||
onDismissRequest = { onDismiss() }) { searchable ->
|
||||
|
||||
searchable ?: return@DismissableBottomSheet
|
||||
|
||||
val viewModel: CustomizeSearchableSheetVM =
|
||||
remember(searchable.key) { CustomizeSearchableSheetVM(searchable) }
|
||||
|
||||
val pickIcon by viewModel.isIconPickerOpen
|
||||
|
||||
BottomSheetDialog(onDismissRequest = { if (!pickIcon) onDismiss() }) {
|
||||
Column(
|
||||
modifier = Modifier.padding(it),
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
val iconSize = 64.dp
|
||||
@@ -261,19 +273,14 @@ fun CustomizeSearchableSheet(
|
||||
}
|
||||
}
|
||||
if (pickIcon) {
|
||||
val bottomSheetState = rememberModalBottomSheetState()
|
||||
BottomSheetDialog(
|
||||
DismissableBottomSheet(
|
||||
onDismissRequest = { viewModel.closeIconPicker() },
|
||||
bottomSheetState = bottomSheetState
|
||||
) {
|
||||
IconPicker(
|
||||
searchable = searchable,
|
||||
onSelect = {
|
||||
scope.launch {
|
||||
viewModel.pickIcon(it)
|
||||
bottomSheetState.hide()
|
||||
viewModel.closeIconPicker()
|
||||
}
|
||||
},
|
||||
contentPadding = it,
|
||||
)
|
||||
|
||||
@@ -12,9 +12,12 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -56,6 +59,7 @@ import androidx.compose.ui.graphics.drawOutline
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@@ -75,7 +79,7 @@ import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.Tag
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.common.TagChip
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.component.dragndrop.DraggableItem
|
||||
@@ -83,24 +87,27 @@ import de.mm20.launcher2.ui.component.dragndrop.LazyDragAndDropRow
|
||||
import de.mm20.launcher2.ui.component.dragndrop.LazyVerticalDragAndDropGrid
|
||||
import de.mm20.launcher2.ui.component.dragndrop.rememberLazyDragAndDropGridState
|
||||
import de.mm20.launcher2.ui.component.dragndrop.rememberLazyDragAndDropListState
|
||||
import de.mm20.launcher2.ui.ktx.toDp
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun EditFavoritesSheet(
|
||||
expanded: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val viewModel: EditFavoritesSheetVM = viewModel()
|
||||
DismissableBottomSheet(expanded, onDismissRequest = onDismiss) {
|
||||
|
||||
LaunchedEffect(null) {
|
||||
viewModel.reload()
|
||||
}
|
||||
val viewModel: EditFavoritesSheetVM = viewModel()
|
||||
|
||||
val loading by viewModel.loading
|
||||
val createShortcutTarget by viewModel.createShortcutTarget
|
||||
LaunchedEffect(null) {
|
||||
viewModel.reload()
|
||||
}
|
||||
|
||||
val loading by viewModel.loading
|
||||
val createShortcutTarget by viewModel.createShortcutTarget
|
||||
|
||||
BottomSheetDialog(onDismiss) {
|
||||
if (loading) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -115,11 +122,18 @@ fun EditFavoritesSheet(
|
||||
)
|
||||
}
|
||||
} else {
|
||||
ReorderFavoritesGrid(viewModel, it)
|
||||
if (createShortcutTarget != null) {
|
||||
BottomSheetDialog({ viewModel.cancelPickShortcut() }) {
|
||||
ShortcutPicker(viewModel, it)
|
||||
}
|
||||
ReorderFavoritesGrid(
|
||||
viewModel,
|
||||
PaddingValues(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||
)
|
||||
)
|
||||
DismissableBottomSheet(
|
||||
expanded = createShortcutTarget != null,
|
||||
onDismissRequest = { viewModel.cancelPickShortcut() }) {
|
||||
ShortcutPicker(viewModel, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,15 +31,11 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.ToggleButton
|
||||
import androidx.compose.material3.ToggleButtonDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -65,7 +61,7 @@ import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.search.Tag
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.common.IconPicker
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.component.SmallMessage
|
||||
import de.mm20.launcher2.ui.component.emojipicker.EmojiPicker
|
||||
@@ -88,8 +84,7 @@ fun EditTagSheet(
|
||||
|
||||
if (viewModel.loading) return
|
||||
|
||||
BottomSheetDialog(
|
||||
bottomSheetState = rememberModalBottomSheetState(true),
|
||||
DismissableBottomSheet(
|
||||
onDismissRequest = {
|
||||
if (viewModel.page == EditTagSheetPage.CustomizeTag) {
|
||||
viewModel.save()
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.launcher.sheets
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -10,13 +11,13 @@ import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.preferences.GestureAction
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.launcher.scaffold.Gesture
|
||||
|
||||
@@ -24,38 +25,43 @@ data class FailedGesture(val gesture: Gesture, val action: GestureAction)
|
||||
|
||||
@Composable
|
||||
fun FailedGestureSheet(
|
||||
failedGesture: FailedGesture,
|
||||
failedGesture: FailedGesture?,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val viewModel: FailedGestureSheetVM = viewModel()
|
||||
|
||||
val actionName = stringResource(when(failedGesture.action) {
|
||||
is GestureAction.Search -> R.string.gesture_action_open_search
|
||||
is GestureAction.Notifications -> R.string.gesture_action_notifications
|
||||
is GestureAction.ScreenLock -> R.string.gesture_action_lock_screen
|
||||
is GestureAction.QuickSettings -> R.string.gesture_action_quick_settings
|
||||
is GestureAction.Recents -> R.string.gesture_action_recents
|
||||
is GestureAction.PowerMenu -> R.string.gesture_action_power_menu
|
||||
else -> R.string.gesture_action_none
|
||||
})
|
||||
val gestureName = stringResource(when(failedGesture.gesture) {
|
||||
Gesture.DoubleTap -> R.string.preference_gesture_double_tap
|
||||
Gesture.LongPress -> R.string.preference_gesture_long_press
|
||||
Gesture.SwipeDown -> R.string.preference_gesture_swipe_down
|
||||
Gesture.SwipeLeft -> R.string.preference_gesture_swipe_left
|
||||
Gesture.SwipeRight -> R.string.preference_gesture_swipe_right
|
||||
Gesture.SwipeUp -> R.string.preference_gesture_swipe_up
|
||||
Gesture.HomeButton -> R.string.preference_gesture_home_button
|
||||
else -> throw IllegalArgumentException("Unknown gesture: ${failedGesture.gesture}")
|
||||
})
|
||||
|
||||
BottomSheetDialog(
|
||||
DismissableBottomSheet(
|
||||
state = failedGesture,
|
||||
expanded = { it != null },
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
it ?: return@DismissableBottomSheet
|
||||
val viewModel: FailedGestureSheetVM = viewModel()
|
||||
|
||||
val actionName = stringResource(when(it.action) {
|
||||
is GestureAction.Search -> R.string.gesture_action_open_search
|
||||
is GestureAction.Notifications -> R.string.gesture_action_notifications
|
||||
is GestureAction.ScreenLock -> R.string.gesture_action_lock_screen
|
||||
is GestureAction.QuickSettings -> R.string.gesture_action_quick_settings
|
||||
is GestureAction.Recents -> R.string.gesture_action_recents
|
||||
is GestureAction.PowerMenu -> R.string.gesture_action_power_menu
|
||||
else -> R.string.gesture_action_none
|
||||
})
|
||||
val gestureName = stringResource(when(it.gesture) {
|
||||
Gesture.DoubleTap -> R.string.preference_gesture_double_tap
|
||||
Gesture.LongPress -> R.string.preference_gesture_long_press
|
||||
Gesture.SwipeDown -> R.string.preference_gesture_swipe_down
|
||||
Gesture.SwipeLeft -> R.string.preference_gesture_swipe_left
|
||||
Gesture.SwipeRight -> R.string.preference_gesture_swipe_right
|
||||
Gesture.SwipeUp -> R.string.preference_gesture_swipe_up
|
||||
Gesture.HomeButton -> R.string.preference_gesture_home_button
|
||||
else -> throw IllegalArgumentException("Unknown gesture: ${it.gesture}")
|
||||
})
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(it)
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.gesture_failed_message, gestureName, actionName),
|
||||
@@ -67,7 +73,7 @@ fun FailedGestureSheet(
|
||||
text = stringResource(id = R.string.missing_permission_accessibility_gesture_failed),
|
||||
secondaryAction = {
|
||||
OutlinedButton(onClick = {
|
||||
viewModel.disableGesture(failedGesture.gesture)
|
||||
viewModel.disableGesture(it.gesture)
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.turn_off))
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
package de.mm20.launcher2.ui.launcher.sheets
|
||||
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||
|
||||
@Composable
|
||||
fun HiddenItemsSheet(
|
||||
expanded: Boolean,
|
||||
items: List<SavableSearchable>,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
BottomSheetDialog(onDismiss) {
|
||||
DismissableBottomSheet(expanded = expanded, onDismissRequest = onDismiss) {
|
||||
SearchResultGrid(
|
||||
items,
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(it)
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,17 @@ import androidx.compose.runtime.Composable
|
||||
@Composable
|
||||
fun LauncherBottomSheets() {
|
||||
val bottomSheetManager = LocalBottomSheetManager.current
|
||||
bottomSheetManager.customizeSearchableSheetShown.value?.let {
|
||||
CustomizeSearchableSheet(
|
||||
searchable = it,
|
||||
onDismiss = { bottomSheetManager.dismissCustomizeSearchableModal() })
|
||||
}
|
||||
if (bottomSheetManager.editFavoritesSheetShown.value) {
|
||||
EditFavoritesSheet(onDismiss = { bottomSheetManager.dismissEditFavoritesSheet() })
|
||||
}
|
||||
CustomizeSearchableSheet(
|
||||
searchable = bottomSheetManager.customizeSearchableSheetShown.value,
|
||||
onDismiss = { bottomSheetManager.dismissCustomizeSearchableModal() })
|
||||
EditFavoritesSheet(
|
||||
expanded = bottomSheetManager.editFavoritesSheetShown.value,
|
||||
onDismiss = { bottomSheetManager.dismissEditFavoritesSheet() })
|
||||
bottomSheetManager.editTagSheetShown.value?.let {
|
||||
EditTagSheet(tag = it, onDismiss = { bottomSheetManager.dismissEditTagSheet() })
|
||||
}
|
||||
bottomSheetManager.failedGestureSheetShown.value?.let {
|
||||
FailedGestureSheet(it, onDismiss = { bottomSheetManager.dismissFailedGestureSheet() })
|
||||
}
|
||||
FailedGestureSheet(
|
||||
bottomSheetManager.failedGestureSheetShown.value,
|
||||
onDismiss = { bottomSheetManager.dismissFailedGestureSheet() }
|
||||
)
|
||||
}
|
||||
@@ -18,20 +18,25 @@ import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.DockedSearchBar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.SearchBar
|
||||
import androidx.compose.material3.SearchBarDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -54,11 +59,11 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.widgets.AppWidget
|
||||
import de.mm20.launcher2.widgets.AppWidgetConfig
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.AppsWidget
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.MusicWidget
|
||||
import de.mm20.launcher2.widgets.NotesWidget
|
||||
import de.mm20.launcher2.widgets.WeatherWidget
|
||||
@@ -249,41 +254,48 @@ private class BindAndConfigureAppWidgetContract(
|
||||
|
||||
@Composable
|
||||
fun WidgetPickerSheet(
|
||||
expanded: Boolean,
|
||||
includeBuiltinWidgets: Boolean = true,
|
||||
title: String = stringResource(R.string.widget_pick_widget),
|
||||
onWidgetSelected: (Widget) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val density = LocalDensity.current
|
||||
val viewModel: WidgetPickerSheetVM = viewModel(factory = WidgetPickerSheetVM.Factory)
|
||||
|
||||
val bindAppWidgetStarter =
|
||||
rememberLauncherForActivityResult(BindAndConfigureAppWidgetContract(density)) {
|
||||
if (it != null) {
|
||||
onWidgetSelected(it)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val appWidgetGroups by viewModel.appWidgetGroups.collectAsState(emptyList())
|
||||
val expandAllGroups by viewModel.expandAllGroups.collectAsState(false)
|
||||
|
||||
val colorSurface = MaterialTheme.colorScheme.surfaceContainerLow
|
||||
|
||||
val query by viewModel.searchQuery.collectAsState("")
|
||||
|
||||
BottomSheetDialog(
|
||||
DismissableBottomSheet(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismiss
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val density = LocalDensity.current
|
||||
val viewModel: WidgetPickerSheetVM = viewModel(factory = WidgetPickerSheetVM.Factory)
|
||||
|
||||
val bindAppWidgetStarter =
|
||||
rememberLauncherForActivityResult(BindAndConfigureAppWidgetContract(density)) {
|
||||
if (it != null) {
|
||||
onWidgetSelected(it)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val appWidgetGroups by viewModel.appWidgetGroups.collectAsState(emptyList())
|
||||
val expandAllGroups by viewModel.expandAllGroups.collectAsState(false)
|
||||
|
||||
val colorSurface = MaterialTheme.colorScheme.surfaceContainerLow
|
||||
|
||||
val query by viewModel.searchQuery.collectAsState("")
|
||||
|
||||
val builtIn by viewModel.builtInWidgets.collectAsState(emptyList())
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentPadding = it
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 16.dp
|
||||
),
|
||||
) {
|
||||
stickyHeader {
|
||||
SearchBar(
|
||||
DockedSearchBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.drawBehind {
|
||||
@@ -295,24 +307,34 @@ fun WidgetPickerSheet(
|
||||
)
|
||||
}
|
||||
.padding(bottom = 16.dp),
|
||||
windowInsets = WindowInsets(0.dp),
|
||||
query = query,
|
||||
onQueryChange = { viewModel.search(it) },
|
||||
onSearch = {},
|
||||
active = false,
|
||||
onActiveChange = {},
|
||||
placeholder = {
|
||||
Text(stringResource(R.string.search_bar_placeholder))
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(painterResource(R.drawable.search_24px), null)
|
||||
},
|
||||
trailingIcon = {
|
||||
if (query.isNotEmpty()) {
|
||||
IconButton(onClick = { viewModel.search("") }) {
|
||||
Icon(painterResource(R.drawable.close_24px), null)
|
||||
}
|
||||
}
|
||||
expanded = false,
|
||||
onExpandedChange = {},
|
||||
inputField = {
|
||||
SearchBarDefaults.InputField(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.search_24px),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
if (query.isNotEmpty()) {
|
||||
IconButton(onClick = { viewModel.search("") }) {
|
||||
Icon(painterResource(R.drawable.close_24px), null)
|
||||
}
|
||||
}
|
||||
},
|
||||
onSearch = {},
|
||||
expanded = false,
|
||||
onExpandedChange = {},
|
||||
query = query,
|
||||
onQueryChange = {
|
||||
viewModel.search(it)
|
||||
},
|
||||
placeholder = {
|
||||
Text(stringResource(R.string.search_bar_placeholder))
|
||||
},
|
||||
)
|
||||
}
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets
|
||||
|
||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -11,8 +8,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
@@ -34,10 +29,6 @@ import androidx.compose.ui.layout.positionInParent
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
@@ -161,7 +152,9 @@ fun WidgetColumn(
|
||||
)
|
||||
|
||||
Button(
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally).padding(vertical = 8.dp),
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(vertical = 8.dp),
|
||||
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
|
||||
onClick = {
|
||||
if (!editMode) {
|
||||
@@ -172,7 +165,8 @@ fun WidgetColumn(
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = ButtonDefaults.IconSpacing)
|
||||
modifier = Modifier
|
||||
.padding(end = ButtonDefaults.IconSpacing)
|
||||
.size(ButtonDefaults.IconSize),
|
||||
painter = painterResource(
|
||||
if (editMode) R.drawable.add_20px else R.drawable.edit_20px
|
||||
@@ -184,13 +178,12 @@ fun WidgetColumn(
|
||||
}
|
||||
}
|
||||
|
||||
if (addNewWidget) {
|
||||
WidgetPickerSheet(
|
||||
onDismiss = { addNewWidget = false },
|
||||
onWidgetSelected = {
|
||||
viewModel.addWidget(it)
|
||||
addNewWidget = false
|
||||
}
|
||||
)
|
||||
}
|
||||
WidgetPickerSheet(
|
||||
expanded = addNewWidget,
|
||||
onDismiss = { addNewWidget = false },
|
||||
onWidgetSelected = {
|
||||
viewModel.addWidget(it)
|
||||
addNewWidget = false
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -174,11 +174,10 @@ fun WidgetItem(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (configure) {
|
||||
ConfigureWidgetSheet(
|
||||
widget = widget,
|
||||
onWidgetUpdated = onWidgetUpdate,
|
||||
onDismiss = { configure = false },
|
||||
)
|
||||
}
|
||||
ConfigureWidgetSheet(
|
||||
expanded = configure,
|
||||
widget = widget,
|
||||
onWidgetUpdated = onWidgetUpdate,
|
||||
onDismiss = { configure = false },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
@@ -57,7 +58,7 @@ import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.base.LocalTime
|
||||
import de.mm20.launcher2.ui.component.Banner
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.AnalogClock
|
||||
@@ -144,9 +145,7 @@ fun ClockWidget(
|
||||
}
|
||||
}
|
||||
HorizontalDivider()
|
||||
if (configure) {
|
||||
ConfigureClockWidgetSheet(onDismiss = { configure = false })
|
||||
}
|
||||
ConfigureClockWidgetSheet(expanded = configure, onDismiss = { configure = false })
|
||||
}
|
||||
} else {
|
||||
Column(modifier = modifier) {
|
||||
@@ -345,27 +344,29 @@ fun DynamicZone(
|
||||
|
||||
@Composable
|
||||
fun ConfigureClockWidgetSheet(
|
||||
expanded: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val viewModel: ClockWidgetSettingsScreenVM = viewModel()
|
||||
val compact by viewModel.compact.collectAsState()
|
||||
val color by viewModel.color.collectAsState()
|
||||
val style by viewModel.clockStyle.collectAsState()
|
||||
val fillHeight by viewModel.fillHeight.collectAsState()
|
||||
val widgetsOnHome by viewModel.widgetsOnHome.collectAsState()
|
||||
val alignment by viewModel.alignment.collectAsState()
|
||||
val showSeconds by viewModel.showSeconds.collectAsState()
|
||||
val monospaced by viewModel.monospaced.collectAsState()
|
||||
val useAccentColor by viewModel.useThemeColor.collectAsState()
|
||||
val parts by viewModel.parts.collectAsState()
|
||||
val smartspacer by viewModel.useSmartspacer.collectAsState()
|
||||
DismissableBottomSheet(expanded = expanded, onDismissRequest = onDismiss) {
|
||||
val viewModel: ClockWidgetSettingsScreenVM = viewModel()
|
||||
val compact by viewModel.compact.collectAsState()
|
||||
val color by viewModel.color.collectAsState()
|
||||
val style by viewModel.clockStyle.collectAsState()
|
||||
val fillHeight by viewModel.fillHeight.collectAsState()
|
||||
val widgetsOnHome by viewModel.widgetsOnHome.collectAsState()
|
||||
val alignment by viewModel.alignment.collectAsState()
|
||||
val showSeconds by viewModel.showSeconds.collectAsState()
|
||||
val monospaced by viewModel.monospaced.collectAsState()
|
||||
val useAccentColor by viewModel.useThemeColor.collectAsState()
|
||||
val parts by viewModel.parts.collectAsState()
|
||||
val smartspacer by viewModel.useSmartspacer.collectAsState()
|
||||
|
||||
BottomSheetDialog(onDismissRequest = onDismiss) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth()
|
||||
.padding(it)
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
@@ -475,10 +475,11 @@ fun WatchFaceSelector(
|
||||
}
|
||||
}
|
||||
|
||||
if (showWidgetPicker && selected is ClockWidgetStyle.Custom) {
|
||||
if (selected is ClockWidgetStyle.Custom) {
|
||||
val previousWidgetId = selected.widgetId
|
||||
val appWidgetHost = LocalAppWidgetHost.current
|
||||
WidgetPickerSheet(
|
||||
expanded = showWidgetPicker,
|
||||
includeBuiltinWidgets = false,
|
||||
onWidgetSelected = {
|
||||
if (previousWidgetId != null) {
|
||||
|
||||
@@ -26,8 +26,8 @@ import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet
|
||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
||||
import de.mm20.launcher2.widgets.AppWidget
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.AppsWidget
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.MusicWidget
|
||||
import de.mm20.launcher2.widgets.NotesWidget
|
||||
import de.mm20.launcher2.widgets.WeatherWidget
|
||||
@@ -41,7 +41,8 @@ fun AppWidget(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val lightBackground = if (widget.config.background) !LocalDarkTheme.current else LocalPreferDarkContentOverWallpaper.current
|
||||
val lightBackground =
|
||||
if (widget.config.background) !LocalDarkTheme.current else LocalPreferDarkContentOverWallpaper.current
|
||||
|
||||
val widgetInfo = remember(widget.config.widgetId) {
|
||||
AppWidgetManager.getInstance(context)
|
||||
@@ -66,28 +67,27 @@ fun AppWidget(
|
||||
}
|
||||
}
|
||||
)
|
||||
if (replaceWidget) {
|
||||
WidgetPickerSheet(
|
||||
onDismiss = { replaceWidget = false },
|
||||
onWidgetSelected = {
|
||||
val updatedWidget = when (it) {
|
||||
is AppWidget -> widget.copy(
|
||||
config = widget.config.copy(
|
||||
widgetId = it.config.widgetId
|
||||
)
|
||||
WidgetPickerSheet(
|
||||
expanded = replaceWidget,
|
||||
onDismiss = { replaceWidget = false },
|
||||
onWidgetSelected = {
|
||||
val updatedWidget = when (it) {
|
||||
is AppWidget -> widget.copy(
|
||||
config = widget.config.copy(
|
||||
widgetId = it.config.widgetId
|
||||
)
|
||||
)
|
||||
|
||||
is WeatherWidget -> it.copy(id = widget.id)
|
||||
is MusicWidget -> it.copy(id = widget.id)
|
||||
is CalendarWidget -> it.copy(id = widget.id)
|
||||
is AppsWidget -> it.copy(id = widget.id)
|
||||
is NotesWidget -> it.copy(id = widget.id)
|
||||
}
|
||||
onWidgetUpdate(updatedWidget)
|
||||
replaceWidget = false
|
||||
is WeatherWidget -> it.copy(id = widget.id)
|
||||
is MusicWidget -> it.copy(id = widget.id)
|
||||
is CalendarWidget -> it.copy(id = widget.id)
|
||||
is AppsWidget -> it.copy(id = widget.id)
|
||||
is NotesWidget -> it.copy(id = widget.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
onWidgetUpdate(updatedWidget)
|
||||
replaceWidget = false
|
||||
}
|
||||
)
|
||||
} else {
|
||||
val width = widget.config.width
|
||||
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
|
||||
@@ -36,7 +36,6 @@ import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -57,7 +56,7 @@ import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.Banner
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.Tooltip
|
||||
import de.mm20.launcher2.ui.component.markdown.MarkdownEditor
|
||||
import de.mm20.launcher2.ui.component.markdown.MarkdownText
|
||||
@@ -404,10 +403,8 @@ fun NoteWidgetConflictResolveSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
var selectedStrategy by remember { mutableStateOf<LinkedFileConflictStrategy?>(null) }
|
||||
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
BottomSheetDialog(
|
||||
DismissableBottomSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
bottomSheetState = bottomSheetState
|
||||
|
||||
) {
|
||||
Column(
|
||||
@@ -546,7 +543,7 @@ fun NoteReadWriteErrorSheet(
|
||||
onRelink: () -> Unit,
|
||||
onUnlink: () -> Unit,
|
||||
) {
|
||||
BottomSheetDialog(
|
||||
DismissableBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
Column(
|
||||
|
||||
@@ -115,9 +115,10 @@ fun WeatherWidget(widget: WeatherWidget) {
|
||||
|
||||
var showLocationDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showLocationDialog) {
|
||||
WeatherLocationSearchDialog(onDismissRequest = { showLocationDialog = false })
|
||||
}
|
||||
WeatherLocationSearchDialog(
|
||||
expanded = showLocationDialog,
|
||||
onDismissRequest = { showLocationDialog = false }
|
||||
)
|
||||
|
||||
|
||||
Column {
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -343,7 +344,8 @@ class SettingsActivity : BaseActivity() {
|
||||
)
|
||||
}
|
||||
OverlayHost(
|
||||
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
) {
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package de.mm20.launcher2.ui.settings.backup
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.common.RestoreBackupSheet
|
||||
@@ -13,24 +16,21 @@ import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Serializable
|
||||
data object BackupSettingsRoute: NavKey
|
||||
data object BackupSettingsRoute : NavKey
|
||||
|
||||
@Composable
|
||||
fun BackupSettingsScreen() {
|
||||
val viewModel: BackupSettingsScreenVM = viewModel()
|
||||
|
||||
val restoreUri by viewModel.restoreUri
|
||||
var restoreUri by remember { mutableStateOf<Uri?>(null) }
|
||||
|
||||
val showBackupSheet by viewModel.showBackupSheet
|
||||
var showBackupSheet by remember { mutableStateOf(false) }
|
||||
|
||||
val restoreLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument(),
|
||||
onResult = {
|
||||
viewModel.setRestoreUri(it)
|
||||
restoreUri = it
|
||||
}
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ fun BackupSettingsScreen() {
|
||||
title = stringResource(id = R.string.preference_backup),
|
||||
summary = stringResource(id = R.string.preference_backup_summary),
|
||||
onClick = {
|
||||
viewModel.setShowBackupSheet(true)
|
||||
showBackupSheet = true
|
||||
})
|
||||
Preference(
|
||||
title = stringResource(id = R.string.preference_restore),
|
||||
@@ -55,13 +55,11 @@ fun BackupSettingsScreen() {
|
||||
|
||||
val uri = restoreUri
|
||||
|
||||
if (uri != null) {
|
||||
RestoreBackupSheet(uri = uri, onDismissRequest = { viewModel.setRestoreUri(null) })
|
||||
}
|
||||
RestoreBackupSheet(uri = uri, onDismissRequest = { restoreUri = null })
|
||||
|
||||
if(showBackupSheet) {
|
||||
CreateBackupSheet(onDismissRequest = {
|
||||
viewModel.setShowBackupSheet(false)
|
||||
CreateBackupSheet(
|
||||
expanded = showBackupSheet,
|
||||
onDismissRequest = {
|
||||
showBackupSheet = false
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package de.mm20.launcher2.ui.settings.backup
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
||||
class BackupSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
val showBackupSheet = mutableStateOf(false)
|
||||
|
||||
val restoreUri = mutableStateOf<Uri?>(null)
|
||||
|
||||
fun setShowBackupSheet(show: Boolean) {
|
||||
showBackupSheet.value = show
|
||||
}
|
||||
|
||||
fun setRestoreUri(uri: Uri?) {
|
||||
restoreUri.value = uri
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@ package de.mm20.launcher2.ui.settings.backup
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -16,39 +18,47 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.LargeMessage
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Composable
|
||||
fun CreateBackupSheet(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
DismissableBottomSheet(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
val viewModel: CreateBackupSheetVM = viewModel()
|
||||
|
||||
val viewModel: CreateBackupSheetVM = viewModel()
|
||||
|
||||
val backupLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("application/vnd.de.mm20.launcher2.backup"),
|
||||
onResult = {
|
||||
if (it != null) viewModel.createBackup(it)
|
||||
val backupLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("application/vnd.de.mm20.launcher2.backup"),
|
||||
onResult = {
|
||||
if (it != null) viewModel.createBackup(it)
|
||||
else onDismissRequest()
|
||||
}
|
||||
)
|
||||
LaunchedEffect(null) {
|
||||
viewModel.reset()
|
||||
val fileName = "${
|
||||
ZonedDateTime.now().format(
|
||||
DateTimeFormatter.ISO_INSTANT
|
||||
).replace(":", "_")
|
||||
}.kvaesitso"
|
||||
backupLauncher.launch(fileName)
|
||||
}
|
||||
)
|
||||
LaunchedEffect(null) {
|
||||
viewModel.reset()
|
||||
val fileName = "${
|
||||
ZonedDateTime.now().format(
|
||||
DateTimeFormatter.ISO_INSTANT
|
||||
).replace(":", "_")
|
||||
}.kvaesitso"
|
||||
backupLauncher.launch(fileName)
|
||||
}
|
||||
|
||||
val state by viewModel.state
|
||||
|
||||
if (state == CreateBackupState.BackingUp || state == CreateBackupState.BackedUp) {
|
||||
BottomSheetDialog(onDismissRequest) {
|
||||
if (state == CreateBackupState.BackingUp) {
|
||||
val state by viewModel.state
|
||||
AnimatedContent(
|
||||
state,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
if (it == CreateBackupState.BackingUp) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -59,8 +69,7 @@ fun CreateBackupSheet(
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else if (it == CreateBackupState.BackedUp) {
|
||||
LargeMessage(
|
||||
modifier = Modifier.aspectRatio(1f),
|
||||
icon = R.drawable.check_circle_48px,
|
||||
|
||||
@@ -34,7 +34,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker
|
||||
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
@@ -76,7 +76,7 @@ fun CorePaletteColorPreference(
|
||||
|
||||
if (showDialog) {
|
||||
var currentValue by remember { mutableStateOf(value) }
|
||||
BottomSheetDialog(onDismissRequest = {
|
||||
DismissableBottomSheet(onDismissRequest = {
|
||||
onValueChange(currentValue)
|
||||
showDialog = false
|
||||
}) {
|
||||
|
||||
@@ -22,9 +22,6 @@ import androidx.compose.material3.ButtonGroupDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
@@ -57,7 +54,7 @@ import de.mm20.launcher2.themes.colors.StaticColor
|
||||
import de.mm20.launcher2.themes.colors.atTone
|
||||
import de.mm20.launcher2.themes.colors.get
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker
|
||||
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
|
||||
import de.mm20.launcher2.ui.ktx.hct
|
||||
@@ -104,7 +101,7 @@ fun ThemeColorPreference(
|
||||
var currentValue by remember { mutableStateOf(value) }
|
||||
|
||||
val actualValue = currentValue ?: defaultValue
|
||||
BottomSheetDialog(onDismissRequest = {
|
||||
DismissableBottomSheet(onDismissRequest = {
|
||||
onValueChange(currentValue)
|
||||
showDialog = false
|
||||
}) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import de.mm20.launcher2.ui.launcher.sheets.EditFavoritesSheet
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data object FavoritesSettingsRoute: NavKey
|
||||
data object FavoritesSettingsRoute : NavKey
|
||||
|
||||
@Composable
|
||||
fun FavoritesSettingsScreen() {
|
||||
@@ -105,9 +105,8 @@ fun FavoritesSettingsScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
if (showEditSheet) {
|
||||
EditFavoritesSheet(
|
||||
onDismiss = { showEditSheet = false }
|
||||
)
|
||||
}
|
||||
EditFavoritesSheet(
|
||||
expanded = showEditSheet,
|
||||
onDismiss = { showEditSheet = false }
|
||||
)
|
||||
}
|
||||
@@ -14,16 +14,12 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ButtonGroupDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ToggleButton
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -46,7 +42,7 @@ import de.mm20.launcher2.preferences.SearchBarColors
|
||||
import de.mm20.launcher2.preferences.SearchBarStyle
|
||||
import de.mm20.launcher2.preferences.SystemBarColors
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.SearchBar
|
||||
import de.mm20.launcher2.ui.component.SearchBarLevel
|
||||
import de.mm20.launcher2.ui.component.preferences.ListPreference
|
||||
@@ -57,12 +53,11 @@ import de.mm20.launcher2.ui.component.preferences.SliderPreference
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
import de.mm20.launcher2.ui.launcher.widgets.clock.ConfigureClockWidgetSheet
|
||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import de.mm20.launcher2.ui.locals.LocalBackStack
|
||||
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data object HomescreenSettingsRoute: NavKey
|
||||
data object HomescreenSettingsRoute : NavKey
|
||||
|
||||
@Composable
|
||||
fun HomescreenSettingsScreen() {
|
||||
@@ -286,9 +281,10 @@ fun HomescreenSettingsScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
if (viewModel.showClockWidgetSheet) {
|
||||
ConfigureClockWidgetSheet(onDismiss = { viewModel.showClockWidgetSheet = false })
|
||||
}
|
||||
ConfigureClockWidgetSheet(
|
||||
expanded = viewModel.showClockWidgetSheet,
|
||||
onDismiss = { viewModel.showClockWidgetSheet = false }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -310,7 +306,7 @@ fun SearchBarStylePreference(
|
||||
val darkColors =
|
||||
LocalPreferDarkContentOverWallpaper.current && colors == SearchBarColors.Auto || colors == SearchBarColors.Dark
|
||||
|
||||
BottomSheetDialog(
|
||||
DismissableBottomSheet(
|
||||
onDismissRequest = {
|
||||
showDialog = false
|
||||
}
|
||||
|
||||
@@ -6,9 +6,13 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -45,8 +49,7 @@ import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.preferences.IconShape
|
||||
import de.mm20.launcher2.preferences.ui.GridSettings
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.component.getShape
|
||||
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
|
||||
@@ -59,7 +62,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data object IconsSettingsRoute: NavKey
|
||||
data object IconsSettingsRoute : NavKey
|
||||
|
||||
@Composable
|
||||
fun IconsSettingsScreen() {
|
||||
@@ -276,44 +279,49 @@ fun IconsSettingsScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
if (showIconPackSheet) {
|
||||
val iconPackPreviewIcons = remember(installedIconPacks, iconSize) {
|
||||
|
||||
installedIconPacks.associate {
|
||||
it.packageName to viewModel.getIconPackPreviewIcons(
|
||||
context,
|
||||
it,
|
||||
grid.columnCount,
|
||||
iconSize,
|
||||
icons?.themedIcons == true
|
||||
)
|
||||
}
|
||||
val iconPackPreviewIcons = remember(installedIconPacks, iconSize) {
|
||||
installedIconPacks.associate {
|
||||
it.packageName to viewModel.getIconPackPreviewIcons(
|
||||
context,
|
||||
it,
|
||||
grid.columnCount,
|
||||
iconSize,
|
||||
icons?.themedIcons == true
|
||||
)
|
||||
}
|
||||
|
||||
IconPackSelectorSheet(
|
||||
installedIconPacks,
|
||||
iconPackPreviewIcons = iconPackPreviewIcons,
|
||||
columns = grid.columnCount,
|
||||
onSelect = {
|
||||
viewModel.setIconPack(it.packageName)
|
||||
showIconPackSheet = false
|
||||
},
|
||||
onDismiss = { showIconPackSheet = false },
|
||||
)
|
||||
}
|
||||
|
||||
IconPackSelectorSheet(
|
||||
expanded = showIconPackSheet,
|
||||
installedIconPacks,
|
||||
iconPackPreviewIcons = iconPackPreviewIcons,
|
||||
columns = grid.columnCount,
|
||||
onSelect = {
|
||||
viewModel.setIconPack(it.packageName)
|
||||
showIconPackSheet = false
|
||||
},
|
||||
onDismiss = { showIconPackSheet = false },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IconPackSelectorSheet(
|
||||
expanded: Boolean,
|
||||
installedIconPacks: List<IconPack>,
|
||||
iconPackPreviewIcons: Map<String, Flow<List<LauncherIcon>>>,
|
||||
columns: Int,
|
||||
onSelect: (IconPack) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
BottomSheetDialog(onDismissRequest = onDismiss) {
|
||||
DismissableBottomSheet(expanded = expanded, onDismissRequest = onDismiss) {
|
||||
LazyColumn(
|
||||
contentPadding = it,
|
||||
contentPadding = PaddingValues(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
top = 16.dp,
|
||||
bottom = 16.dp + WindowInsets.navigationBars.asPaddingValues()
|
||||
.calculateBottomPadding(),
|
||||
),
|
||||
) {
|
||||
items(installedIconPacks.size) {
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.SmallMessage
|
||||
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
|
||||
import de.mm20.launcher2.ui.component.preferences.ListPreference
|
||||
@@ -399,7 +399,7 @@ fun SearchSettingsScreen() {
|
||||
|
||||
if (showFilterEditor) {
|
||||
val filters by viewModel.searchFilters.collectAsStateWithLifecycle()
|
||||
BottomSheetDialog(onDismissRequest = { showFilterEditor = false }) {
|
||||
DismissableBottomSheet(onDismissRequest = { showFilterEditor = false }) {
|
||||
Column(
|
||||
modifier = Modifier.padding(it)
|
||||
) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
@@ -16,7 +17,11 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -28,15 +33,19 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ButtonGroupDefaults
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenuGroup
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.DropdownMenuPopup
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.FlexibleBottomAppBar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
@@ -53,7 +62,7 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.ToggleButton
|
||||
import androidx.compose.material3.ToggleButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -85,7 +94,7 @@ import de.mm20.launcher2.searchactions.builders.CustomIntentActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.CustomWebsearchActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.CustomizableSearchActionBuilder
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.BottomSheet
|
||||
import de.mm20.launcher2.ui.component.ExperimentalBadge
|
||||
import de.mm20.launcher2.ui.component.SearchActionIcon
|
||||
import de.mm20.launcher2.ui.component.preferences.ListPreference
|
||||
@@ -93,39 +102,125 @@ import de.mm20.launcher2.ui.ktx.toPixels
|
||||
|
||||
@Composable
|
||||
fun EditSearchActionSheet(
|
||||
expanded: Boolean,
|
||||
initialSearchAction: CustomizableSearchActionBuilder?,
|
||||
onSave: (CustomizableSearchActionBuilder) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val viewModel: EditSearchActionSheetVM = viewModel()
|
||||
LaunchedEffect(initialSearchAction) {
|
||||
viewModel.init(initialSearchAction)
|
||||
}
|
||||
val page by viewModel.currentPage
|
||||
|
||||
val searchAction by viewModel.searchAction
|
||||
BottomSheetDialog(
|
||||
onDismissRequest = {
|
||||
viewModel.onDismiss()
|
||||
onDismiss()
|
||||
}
|
||||
BottomSheet(
|
||||
expanded = expanded,
|
||||
) {
|
||||
Column(
|
||||
modifier = when (page) {
|
||||
EditSearchActionPage.InitAppSearch, EditSearchActionPage.PickIcon -> Modifier
|
||||
else -> Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(it)
|
||||
var confirmDismiss by remember { mutableStateOf(false) }
|
||||
|
||||
val viewModel: EditSearchActionSheetVM = viewModel()
|
||||
DisposableEffect(initialSearchAction) {
|
||||
viewModel.init(initialSearchAction)
|
||||
onDispose {
|
||||
viewModel.onDismiss()
|
||||
}
|
||||
}
|
||||
val page by viewModel.currentPage
|
||||
|
||||
if (page == null) return@BottomSheet
|
||||
|
||||
val searchAction by viewModel.searchAction
|
||||
val createNew by viewModel.createNew
|
||||
|
||||
if (confirmDismiss) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { confirmDismiss = false },
|
||||
dismissButton = {
|
||||
OutlinedButton(onClick = {
|
||||
confirmDismiss = false
|
||||
}) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = {
|
||||
confirmDismiss = false
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.action_quit))
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(R.string.dialog_discard_unsaved))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
when (page) {
|
||||
EditSearchActionPage.SelectType -> SelectTypePage(viewModel)
|
||||
EditSearchActionPage.InitWebSearch -> InitWebSearchPage(viewModel)
|
||||
EditSearchActionPage.InitAppSearch -> InitAppSearchPage(viewModel, it)
|
||||
EditSearchActionPage.CustomizeWebSearch -> CustomizeWebSearch(viewModel)
|
||||
EditSearchActionPage.CustomizeCustomIntent -> CustomizeCustomIntent(viewModel)
|
||||
EditSearchActionPage.CustomizeAppSearch -> CustomizeAppSearch(viewModel)
|
||||
EditSearchActionPage.PickIcon -> PickIcon(viewModel, it)
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
if (page != EditSearchActionPage.PickIcon) {
|
||||
Text(
|
||||
stringResource(
|
||||
if (createNew) {
|
||||
R.string.create_search_action_title
|
||||
} else {
|
||||
R.string.edit_search_action_title
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
if (page == EditSearchActionPage.PickIcon) {
|
||||
FilledTonalIconButton(onClick = {
|
||||
viewModel.applyIcon()
|
||||
}) {
|
||||
Icon(
|
||||
painterResource(R.drawable.arrow_back_24px),
|
||||
stringResource(R.string.menu_back)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (page != EditSearchActionPage.PickIcon) {
|
||||
FilledTonalIconButton(
|
||||
onClick = {
|
||||
if (initialSearchAction == searchAction) {
|
||||
onDismiss()
|
||||
return@FilledTonalIconButton
|
||||
}
|
||||
when (page) {
|
||||
EditSearchActionPage.CustomizeAppSearch,
|
||||
EditSearchActionPage.CustomizeWebSearch,
|
||||
EditSearchActionPage.CustomizeCustomIntent -> {
|
||||
confirmDismiss = true
|
||||
}
|
||||
|
||||
else -> onDismiss()
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.close_24px),
|
||||
stringResource(R.string.close)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
AnimatedContent(
|
||||
page!!,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f, false)
|
||||
) {
|
||||
when (it) {
|
||||
EditSearchActionPage.SelectType -> SelectTypePage(viewModel)
|
||||
EditSearchActionPage.InitWebSearch -> InitWebSearchPage(viewModel)
|
||||
EditSearchActionPage.InitAppSearch -> InitAppSearchPage(viewModel)
|
||||
EditSearchActionPage.CustomizeWebSearch -> CustomizeWebSearch(viewModel)
|
||||
EditSearchActionPage.CustomizeCustomIntent -> CustomizeCustomIntent(viewModel)
|
||||
EditSearchActionPage.CustomizeAppSearch -> CustomizeAppSearch(viewModel)
|
||||
EditSearchActionPage.PickIcon -> PickIcon(viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
val button: (@Composable () -> Unit)? = when (page) {
|
||||
@@ -170,27 +265,18 @@ fun EditSearchActionSheet(
|
||||
}
|
||||
}
|
||||
|
||||
EditSearchActionPage.PickIcon -> {
|
||||
{
|
||||
OutlinedButton(onClick = {
|
||||
viewModel.applyIcon()
|
||||
}) {
|
||||
Text(stringResource(R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (button != null) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
AnimatedVisibility(button != null) {
|
||||
FlexibleBottomAppBar(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
button()
|
||||
Box(
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
) {
|
||||
button?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,7 +287,10 @@ fun EditSearchActionSheet(
|
||||
private fun SelectTypePage(viewModel: EditSearchActionSheetVM) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.create_search_action_type),
|
||||
@@ -283,13 +372,19 @@ private fun SelectTypePage(viewModel: EditSearchActionSheetVM) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InitAppSearchPage(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) {
|
||||
private fun InitAppSearchPage(viewModel: EditSearchActionSheetVM) {
|
||||
val context = LocalContext.current
|
||||
val searchableApps by remember { viewModel.getSearchableApps(context) }.collectAsState(null)
|
||||
|
||||
if (searchableApps != null) {
|
||||
LazyColumn(
|
||||
contentPadding = paddingValues
|
||||
contentPadding = PaddingValues(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp + WindowInsets.navigationBars.asPaddingValues()
|
||||
.calculateBottomPadding()
|
||||
)
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
@@ -335,6 +430,7 @@ private fun InitWebSearchPage(viewModel: EditSearchActionSheetVM) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.create_search_action_website_url),
|
||||
@@ -382,6 +478,8 @@ fun CustomizeWebSearch(viewModel: EditSearchActionSheetVM) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
|
||||
if (searchAction != null && searchAction is CustomWebsearchActionBuilder) {
|
||||
@@ -521,6 +619,8 @@ fun CustomizeAppSearch(viewModel: EditSearchActionSheetVM) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
|
||||
if (searchAction != null) {
|
||||
@@ -645,6 +745,8 @@ fun CustomizeCustomIntent(viewModel: EditSearchActionSheetVM) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Bottom
|
||||
@@ -689,7 +791,9 @@ fun CustomizeCustomIntent(viewModel: EditSearchActionSheetVM) {
|
||||
Icon(
|
||||
painterResource(R.drawable.check_20px),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(end = ToggleButtonDefaults.IconSpacing).size(ToggleButtonDefaults.IconSize)
|
||||
modifier = Modifier
|
||||
.padding(end = ToggleButtonDefaults.IconSpacing)
|
||||
.size(ToggleButtonDefaults.IconSize)
|
||||
)
|
||||
}
|
||||
Text("Data")
|
||||
@@ -706,7 +810,9 @@ fun CustomizeCustomIntent(viewModel: EditSearchActionSheetVM) {
|
||||
Icon(
|
||||
painterResource(R.drawable.check_20px),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(end = ToggleButtonDefaults.IconSpacing).size(ToggleButtonDefaults.IconSize)
|
||||
modifier = Modifier
|
||||
.padding(end = ToggleButtonDefaults.IconSpacing)
|
||||
.size(ToggleButtonDefaults.IconSize)
|
||||
)
|
||||
}
|
||||
Text("String extra")
|
||||
@@ -859,7 +965,7 @@ fun CustomizeCustomIntent(viewModel: EditSearchActionSheetVM) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) {
|
||||
fun PickIcon(viewModel: EditSearchActionSheetVM) {
|
||||
val action by viewModel.searchAction
|
||||
|
||||
val iconSizePx = 20.dp.toPixels()
|
||||
@@ -869,105 +975,109 @@ fun PickIcon(viewModel: EditSearchActionSheetVM, paddingValues: PaddingValues) {
|
||||
if (it != null) viewModel.importIcon(it, iconSizePx.toInt())
|
||||
}
|
||||
|
||||
if (action?.customIcon == null) {
|
||||
AnimatedContent(
|
||||
action?.customIcon == null,
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
if (it) {
|
||||
|
||||
val availableIcons =
|
||||
remember { SearchActionIcon.entries.filter { it != SearchActionIcon.Custom } }
|
||||
val availableIcons =
|
||||
remember { SearchActionIcon.entries.filter { it != SearchActionIcon.Custom } }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) {
|
||||
LazyVerticalGrid(columns = GridCells.Adaptive(64.dp)) {
|
||||
if (action is AppSearchActionBuilder) {
|
||||
item {
|
||||
Column {
|
||||
LazyVerticalGrid(columns = GridCells.Adaptive(64.dp)) {
|
||||
if (action is AppSearchActionBuilder) {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val isSelected =
|
||||
action?.icon == SearchActionIcon.Custom && action?.customIcon == null
|
||||
SearchActionIconTile(isSelected, onClick = {
|
||||
viewModel.setCustomIcon(null)
|
||||
}) {
|
||||
SearchActionIcon(
|
||||
icon = SearchActionIcon.Custom,
|
||||
componentName = (action as AppSearchActionBuilder).baseIntent.component,
|
||||
size = 24.dp,
|
||||
color = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
items(availableIcons) {
|
||||
Box(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val isSelected =
|
||||
action?.icon == SearchActionIcon.Custom && action?.customIcon == null
|
||||
val isSelected = action?.icon == it
|
||||
SearchActionIconTile(isSelected, onClick = {
|
||||
viewModel.setCustomIcon(null)
|
||||
viewModel.setIcon(it)
|
||||
}) {
|
||||
SearchActionIcon(
|
||||
icon = SearchActionIcon.Custom,
|
||||
componentName = (action as AppSearchActionBuilder).baseIntent.component,
|
||||
icon = it,
|
||||
size = 24.dp,
|
||||
color = 1,
|
||||
color = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
items(availableIcons) {
|
||||
Box(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val isSelected = action?.icon == it
|
||||
SearchActionIconTile(isSelected, onClick = {
|
||||
viewModel.setIcon(it)
|
||||
}) {
|
||||
SearchActionIcon(
|
||||
icon = it,
|
||||
size = 24.dp,
|
||||
color = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TextButton(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
onClick = { pickIconLauncher.launch("image/*") }) {
|
||||
Text(stringResource(R.string.websearch_dialog_custom_icon))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxWidth()
|
||||
|
||||
) {
|
||||
SearchActionIconTile {
|
||||
SearchActionIcon(builder = action!!, size = 24.dp)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
text = "Monochrome",
|
||||
textAlign = TextAlign.End,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
Switch(
|
||||
checked = action?.iconColor == 0,
|
||||
onCheckedChange = { viewModel.setIconColor(if (it) 0 else 1) })
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp)
|
||||
.horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 16.dp,
|
||||
alignment = Alignment.End
|
||||
)
|
||||
) {
|
||||
OutlinedButton(
|
||||
TextButton(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
onClick = { pickIconLauncher.launch("image/*") }) {
|
||||
Text(stringResource(R.string.websearch_dialog_replace_icon))
|
||||
Text(stringResource(R.string.websearch_dialog_custom_icon))
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = { viewModel.setIcon(SearchActionIcon.Search) },
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
) {
|
||||
SearchActionIconTile {
|
||||
SearchActionIcon(builder = action!!, size = 24.dp)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
text = "Monochrome",
|
||||
textAlign = TextAlign.End,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
Switch(
|
||||
checked = action?.iconColor == 0,
|
||||
onCheckedChange = { viewModel.setIconColor(if (it) 0 else 1) })
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp)
|
||||
.horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 16.dp,
|
||||
alignment = Alignment.End
|
||||
)
|
||||
) {
|
||||
Text(stringResource(R.string.websearch_dialog_delete_icon))
|
||||
OutlinedButton(
|
||||
onClick = { pickIconLauncher.launch("image/*") }) {
|
||||
Text(stringResource(R.string.websearch_dialog_replace_icon))
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = { viewModel.setIcon(SearchActionIcon.Search) },
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
) {
|
||||
Text(stringResource(R.string.websearch_dialog_delete_icon))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
|
||||
|
||||
private var initialCustomIcon: String? = null
|
||||
|
||||
val currentPage = mutableStateOf(EditSearchActionPage.SelectType)
|
||||
val currentPage = mutableStateOf<EditSearchActionPage?>(null)
|
||||
val createNew = mutableStateOf(false)
|
||||
|
||||
val searchAction = mutableStateOf<CustomizableSearchActionBuilder?>(null)
|
||||
@@ -207,6 +207,7 @@ class EditSearchActionSheetVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
|
||||
fun onDismiss() {
|
||||
currentPage.value = null
|
||||
val action = searchAction.value ?: return
|
||||
val newIcon = action.customIcon
|
||||
if (newIcon != initialCustomIcon) {
|
||||
|
||||
@@ -288,26 +288,24 @@ fun SearchActionsSettingsScreen() {
|
||||
val editAction by viewModel.showEditDialogFor
|
||||
val createAction by viewModel.showCreateDialog
|
||||
|
||||
if (createAction) {
|
||||
EditSearchActionSheet(
|
||||
initialSearchAction = null,
|
||||
onSave = {
|
||||
viewModel.addAction(it)
|
||||
},
|
||||
onDismiss = {
|
||||
viewModel.dismissDialogs()
|
||||
}
|
||||
)
|
||||
}
|
||||
if (editAction != null) {
|
||||
EditSearchActionSheet(
|
||||
initialSearchAction = editAction,
|
||||
onSave = {
|
||||
viewModel.updateAction(editAction!!, it)
|
||||
},
|
||||
onDismiss = {
|
||||
viewModel.dismissDialogs()
|
||||
}
|
||||
)
|
||||
}
|
||||
EditSearchActionSheet(
|
||||
expanded = createAction,
|
||||
initialSearchAction = null,
|
||||
onSave = {
|
||||
viewModel.addAction(it)
|
||||
},
|
||||
onDismiss = {
|
||||
viewModel.dismissDialogs()
|
||||
}
|
||||
)
|
||||
EditSearchActionSheet(
|
||||
expanded = editAction != null,
|
||||
initialSearchAction = editAction,
|
||||
onSave = {
|
||||
viewModel.updateAction(editAction!!, it)
|
||||
},
|
||||
onDismiss = {
|
||||
viewModel.dismissDialogs()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -31,10 +31,7 @@ import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.Shapes
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
@@ -66,7 +63,7 @@ import androidx.navigation3.runtime.NavKey
|
||||
import de.mm20.launcher2.serialization.UUIDSerializer
|
||||
import de.mm20.launcher2.themes.shapes.CornerStyle
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.ktx.withCorners
|
||||
@@ -487,7 +484,7 @@ fun ShapePreference(
|
||||
val actualBottomEnd = currentBottomEnd ?: baseBottomEnd
|
||||
val actualBottomStart = currentBottomStart ?: baseBottomStart
|
||||
|
||||
BottomSheetDialog(
|
||||
DismissableBottomSheet(
|
||||
onDismissRequest = {
|
||||
showDialog = false
|
||||
onValueChange(
|
||||
|
||||
@@ -23,7 +23,6 @@ import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.LeadingIconTab
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedIconToggleButton
|
||||
@@ -33,12 +32,10 @@ import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -64,14 +61,13 @@ import de.mm20.launcher2.themes.typography.DefaultEmphasizedTextStyles
|
||||
import de.mm20.launcher2.themes.typography.DefaultTextStyles
|
||||
import de.mm20.launcher2.themes.typography.FontManager
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.DismissableBottomSheet
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.theme.typography.fontFamilyOf
|
||||
import de.mm20.launcher2.ui.theme.typography.typographyOf
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import kotlin.math.ceil
|
||||
@@ -763,12 +759,9 @@ private fun FontPreference(
|
||||
)
|
||||
|
||||
if (showDialog) {
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
val fonts = remember { fontManager.getInstalledFonts() }
|
||||
val scope = rememberCoroutineScope()
|
||||
BottomSheetDialog(
|
||||
DismissableBottomSheet(
|
||||
onDismissRequest = { showDialog = false },
|
||||
bottomSheetState = sheetState
|
||||
) {
|
||||
LazyColumn(contentPadding = it, verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
if (fonts.builtIn.isNotEmpty()) {
|
||||
@@ -778,11 +771,8 @@ private fun FontPreference(
|
||||
null,
|
||||
fonts.builtIn,
|
||||
onFontClick = {
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
onValueChange(it)
|
||||
showDialog = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -793,11 +783,8 @@ private fun FontPreference(
|
||||
stringResource(R.string.font_category_device_default),
|
||||
fonts.deviceDefault,
|
||||
onFontClick = {
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
onValueChange(it)
|
||||
showDialog = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -808,11 +795,8 @@ private fun FontPreference(
|
||||
stringResource(R.string.font_category_generic),
|
||||
fonts.generic,
|
||||
onFontClick = {
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
onValueChange(it)
|
||||
showDialog = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -984,7 +968,7 @@ private fun TextStylePreference(
|
||||
mutableStateOf(value?.letterSpacing)
|
||||
}
|
||||
|
||||
BottomSheetDialog(
|
||||
DismissableBottomSheet(
|
||||
onDismissRequest = {
|
||||
onValueChange(
|
||||
ThemeTextStyle(
|
||||
|
||||
@@ -187,7 +187,8 @@ fun LocationPreference(
|
||||
showDialog = true
|
||||
}
|
||||
)
|
||||
if (showDialog) {
|
||||
WeatherLocationSearchDialog(onDismissRequest = { showDialog = false })
|
||||
}
|
||||
WeatherLocationSearchDialog(
|
||||
expanded = showDialog,
|
||||
onDismissRequest = { showDialog = false }
|
||||
)
|
||||
}
|
||||
@@ -38,6 +38,7 @@
|
||||
<string name="action_more_actions">More actions</string>
|
||||
<string name="action_clear">Clear</string>
|
||||
<string name="action_install">Install</string>
|
||||
<string name="action_quit">Quit</string>
|
||||
<!-- Delete something (a file or a web search shortcut) -->
|
||||
<string name="menu_delete">Delete</string>
|
||||
<string name="menu_remove">Remove</string>
|
||||
@@ -751,6 +752,8 @@
|
||||
<string name="search_action_contact">Add to contacts</string>
|
||||
<string name="search_action_open_url">View website</string>
|
||||
<string name="search_action_event">Schedule event</string>
|
||||
<string name="create_search_action_title">New quick action</string>
|
||||
<string name="edit_search_action_title">Edit quick action</string>
|
||||
<string name="create_search_action_type">What type of action do you want to create?</string>
|
||||
<string name="create_search_action_type_web">Search on a website</string>
|
||||
<string name="create_search_action_type_app">Search in an app</string>
|
||||
@@ -1085,4 +1088,5 @@
|
||||
<string name="preference_transliteration_auto">Automatic</string>
|
||||
<string name="preference_transliteration_disabled">Disabled</string>
|
||||
<string name="select_tag">Select tag</string>
|
||||
<string name="dialog_discard_unsaved">Quit without saving?</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user