Implement basic icon picker
This commit is contained in:
@@ -28,6 +28,7 @@ import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.*
|
||||
import de.mm20.launcher2.ui.ktx.toDp
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.launcher.search.common.customattrs.CustomizeSearchableSheet
|
||||
import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled
|
||||
import de.mm20.launcher2.ui.locals.LocalGridIconSize
|
||||
import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
|
||||
@@ -48,6 +49,8 @@ fun AppItem(
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val snackbarHostState = LocalSnackbarHostState.current
|
||||
|
||||
var edit by remember { mutableStateOf(false) }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
Column(
|
||||
modifier = modifier
|
||||
@@ -217,6 +220,12 @@ fun AppItem(
|
||||
)
|
||||
)
|
||||
|
||||
toolbarActions.add(DefaultToolbarAction(
|
||||
label = stringResource(R.string.menu_customize),
|
||||
icon = Icons.Rounded.Edit,
|
||||
action = { edit = true }
|
||||
))
|
||||
|
||||
val storeDetails = remember(app) { app.getStoreDetails(context) }
|
||||
val shareAction = if (storeDetails == null) {
|
||||
DefaultToolbarAction(
|
||||
@@ -308,6 +317,13 @@ fun AppItem(
|
||||
rightActions = toolbarActions
|
||||
)
|
||||
}
|
||||
|
||||
if (edit) {
|
||||
CustomizeSearchableSheet(
|
||||
searchable = app,
|
||||
onDismiss = { edit = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package de.mm20.launcher2.ui.launcher.search.common.customattrs
|
||||
|
||||
import android.graphics.drawable.InsetDrawable
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.badges.Badge
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.locals.LocalGridColumns
|
||||
|
||||
@Composable
|
||||
fun CustomizeSearchableSheet(
|
||||
searchable: Searchable,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val viewModel: CustomizeSearchableSheetVM =
|
||||
remember(searchable) { CustomizeSearchableSheetVM(searchable) }
|
||||
val context = LocalContext.current
|
||||
|
||||
val pickIcon by viewModel.isIconPickerOpen.observeAsState(false)
|
||||
|
||||
BottomSheetDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(stringResource(if (pickIcon) R.string.icon_picker_title else R.string.menu_customize))
|
||||
},
|
||||
confirmButton = {
|
||||
if (pickIcon) {
|
||||
OutlinedButton(onClick = { viewModel.closeIconPicker() }) {
|
||||
Text(stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
} else {
|
||||
OutlinedButton(onClick = onDismiss) {
|
||||
Text(stringResource(id = R.string.close))
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
if (!pickIcon) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
|
||||
val iconSize = 64.dp
|
||||
val iconSizePx = iconSize.toPixels()
|
||||
val icon by remember { viewModel.getIcon(iconSizePx.toInt()) }.collectAsState(null)
|
||||
val primaryColor = MaterialTheme.colorScheme.onSecondary
|
||||
val badgeDrawable = remember {
|
||||
InsetDrawable(
|
||||
AppCompatResources.getDrawable(context, R.drawable.ic_edit),
|
||||
8
|
||||
).also {
|
||||
it.setTint(primaryColor.toArgb())
|
||||
}
|
||||
}
|
||||
|
||||
ShapedLauncherIcon(
|
||||
size = iconSize,
|
||||
icon = icon,
|
||||
badge = Badge(
|
||||
icon = badgeDrawable
|
||||
),
|
||||
onClick = {
|
||||
viewModel.openIconPicker()
|
||||
}
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 24.dp),
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
placeholder = {
|
||||
Text(searchable.label)
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val iconSize = 48.dp
|
||||
val iconSizePx = iconSize.toPixels()
|
||||
val suggestions by
|
||||
remember { viewModel.getIconSuggestions(iconSizePx.toInt()) }
|
||||
.observeAsState(emptyList())
|
||||
LazyVerticalGrid(columns = GridCells.Fixed(LocalGridColumns.current)) {
|
||||
items(suggestions) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
) {
|
||||
ShapedLauncherIcon(
|
||||
size = iconSize,
|
||||
icon = it.icon,
|
||||
onClick = {
|
||||
viewModel.pickIcon(it.data)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package de.mm20.launcher2.ui.launcher.search.common.customattrs
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.liveData
|
||||
import de.mm20.launcher2.customattrs.CustomIcon
|
||||
import de.mm20.launcher2.icons.CustomIconSuggestion
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koin.androidx.compose.inject
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class CustomizeSearchableSheetVM(
|
||||
private val searchable: Searchable
|
||||
) : KoinComponent {
|
||||
private val iconRepository: IconRepository by inject()
|
||||
|
||||
val isIconPickerOpen = MutableLiveData(false)
|
||||
|
||||
fun getIcon(size: Int): Flow<LauncherIcon> {
|
||||
return iconRepository.getIcon(searchable, size)
|
||||
}
|
||||
|
||||
fun getIconSuggestions(size: Int) = liveData {
|
||||
emit(iconRepository.getCustomIconSuggestions(searchable, size))
|
||||
}
|
||||
|
||||
fun openIconPicker() {
|
||||
isIconPickerOpen.value = true
|
||||
}
|
||||
|
||||
fun closeIconPicker() {
|
||||
isIconPickerOpen.value = false
|
||||
}
|
||||
|
||||
fun pickIcon(icon: CustomIcon?) {
|
||||
iconRepository.setCustomIcon(searchable, icon)
|
||||
closeIconPicker()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user