Improve tag emoji assignment
This commit is contained in:
@@ -100,6 +100,7 @@ dependencies {
|
||||
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.browser)
|
||||
implementation(libs.androidx.emojipicker)
|
||||
|
||||
implementation(libs.androidx.lifecycle.viewmodelcompose)
|
||||
implementation(libs.androidx.lifecycle.runtimecompose)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.mm20.launcher2.ui.common
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColor
|
||||
import androidx.compose.animation.core.animateDp
|
||||
import androidx.compose.animation.core.tween
|
||||
@@ -8,16 +9,20 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material.icons.rounded.Tag
|
||||
import androidx.compose.material.minimumInteractiveComponentSize
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.InputChipDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||
@@ -30,8 +35,13 @@ import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.icons.IconService
|
||||
import de.mm20.launcher2.icons.StaticLauncherIcon
|
||||
import de.mm20.launcher2.icons.TextLayer
|
||||
import de.mm20.launcher2.icons.VectorLayer
|
||||
import de.mm20.launcher2.search.Tag
|
||||
import de.mm20.launcher2.ui.ktx.splitLeadingEmoji
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import org.koin.androidx.compose.inject
|
||||
|
||||
@Composable
|
||||
fun TagChip(
|
||||
@@ -39,15 +49,12 @@ fun TagChip(
|
||||
tag: Tag,
|
||||
selected: Boolean = false,
|
||||
dragged: Boolean = false,
|
||||
compact: Boolean = false,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
clearable: Boolean = false,
|
||||
onClear: (() -> Unit)? = null,
|
||||
) {
|
||||
val (emoji, tagName) = remember(tag.tag) {
|
||||
tag.tag.splitLeadingEmoji()
|
||||
}
|
||||
|
||||
val shape = MaterialTheme.shapes.small
|
||||
|
||||
val transition = updateTransition(
|
||||
@@ -56,10 +63,10 @@ fun TagChip(
|
||||
|
||||
val backgroundColor by transition.animateColor(
|
||||
transitionSpec = {
|
||||
if (targetState == 0) tween(100, 200) else tween(100, 0)
|
||||
if (targetState == 0 && initialState >= 2) tween(100, 200) else tween(100, 0)
|
||||
}
|
||||
) {
|
||||
when(it) {
|
||||
when (it) {
|
||||
0 -> Color.Transparent
|
||||
2 -> MaterialTheme.colorScheme.surfaceContainerLow
|
||||
else -> MaterialTheme.colorScheme.secondaryContainer
|
||||
@@ -79,17 +86,28 @@ fun TagChip(
|
||||
}
|
||||
val elevation by transition.animateDp(
|
||||
transitionSpec = {
|
||||
if (targetState >=2) tween(100, 200) else tween(100, 0)
|
||||
if (targetState >= 2) tween(100, 200) else tween(100, 0)
|
||||
}
|
||||
) {
|
||||
if (it >= 2) 8.dp else 0.dp
|
||||
}
|
||||
|
||||
val iconService by inject<IconService>()
|
||||
val iconSize = InputChipDefaults.AvatarSize.toPixels()
|
||||
|
||||
val icon by remember(tag, iconSize) {
|
||||
iconService.getIcon(
|
||||
tag,
|
||||
iconSize.toInt()
|
||||
)
|
||||
}.collectAsState(null)
|
||||
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(vertical = 8.dp)
|
||||
.height(32.dp)
|
||||
.widthIn(min = 48.dp)
|
||||
.shadow(elevation, shape, true)
|
||||
.border(borderWidth, borderColor, shape)
|
||||
.background(backgroundColor)
|
||||
@@ -99,28 +117,34 @@ fun TagChip(
|
||||
)
|
||||
.padding(horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
if (emoji != null && tagName != null) {
|
||||
val foregroundLayer = (icon as? StaticLauncherIcon)?.foregroundLayer
|
||||
AnimatedVisibility(foregroundLayer != null && (!compact || foregroundLayer is TextLayer)) {
|
||||
if (foregroundLayer is TextLayer) {
|
||||
Text(
|
||||
text = foregroundLayer.text,
|
||||
modifier = Modifier.width(FilterChipDefaults.IconSize),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
} else if (foregroundLayer is VectorLayer && !compact) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
imageVector = foregroundLayer.vector,
|
||||
contentDescription = null,
|
||||
tint = iconColor
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(foregroundLayer != null && (!compact || foregroundLayer is VectorLayer)) {
|
||||
Text(
|
||||
emoji,
|
||||
modifier = Modifier.width(FilterChipDefaults.IconSize),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
imageVector = Icons.Rounded.Tag,
|
||||
contentDescription = null,
|
||||
tint = iconColor
|
||||
tag.tag,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
tagName ?: emoji ?: "",
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp)
|
||||
)
|
||||
if (clearable) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
|
||||
@@ -84,7 +84,7 @@ fun Icons(actions: List<ToolbarAction>, slots: Int) {
|
||||
val action = actions[i]
|
||||
val tooltipState = rememberTooltipState()
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(action.label)
|
||||
|
||||
@@ -186,7 +186,7 @@ fun LazyVerticalDragAndDropGrid(
|
||||
horizontalArrangement,
|
||||
flingBehavior,
|
||||
userScrollEnabled,
|
||||
content,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ fun LazyHorizontalDragAndDropGrid(
|
||||
verticalArrangement,
|
||||
flingBehavior,
|
||||
userScrollEnabled,
|
||||
content,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
package de.mm20.launcher2.ui.component.emojipicker
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconToggleButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RichTooltip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun EmojiPicker(
|
||||
modifier: Modifier = Modifier,
|
||||
onEmojiSelected: (String) -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var selectedCategory = remember {
|
||||
mutableIntStateOf(0)
|
||||
}
|
||||
|
||||
val categories = remember {
|
||||
val typedArray =
|
||||
context.resources.obtainTypedArray(R.array.emoji_by_category_raw_resources_gender_inclusive)
|
||||
IntArray(typedArray.length()) { typedArray.getResourceId(it, 0) }.also {
|
||||
typedArray.recycle()
|
||||
}
|
||||
}
|
||||
val categoryNames = stringArrayResource(R.array.category_names)
|
||||
val categoryIcons = remember {
|
||||
val typedArray = context.resources.obtainTypedArray(R.array.emoji_categories_icons)
|
||||
IntArray(typedArray.length()) { typedArray.getResourceId(it, 0) }.also {
|
||||
typedArray.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
val emojis = remember {
|
||||
mutableStateMapOf<Int, Array<Array<String>>>()
|
||||
}
|
||||
|
||||
LaunchedEffect(selectedCategory.intValue) {
|
||||
if (selectedCategory.intValue in emojis) return@LaunchedEffect
|
||||
val categoryEmojis =
|
||||
context.resources.openRawResource(categories[selectedCategory.intValue])
|
||||
.bufferedReader().use {
|
||||
it.readLines().map { line ->
|
||||
line.split(",").toTypedArray()
|
||||
}.toTypedArray()
|
||||
}
|
||||
emojis[selectedCategory.intValue] = categoryEmojis
|
||||
}
|
||||
|
||||
LazyVerticalGrid(
|
||||
GridCells.Adaptive(48.dp),
|
||||
modifier = modifier,
|
||||
) {
|
||||
stickyHeader {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 8.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
shape = MaterialTheme.shapes.medium
|
||||
)
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.horizontalScroll(rememberScrollState())
|
||||
|
||||
) {
|
||||
for (i in categories.indices) {
|
||||
IconToggleButton(
|
||||
checked = selectedCategory.intValue == i,
|
||||
onCheckedChange = {
|
||||
if (it) selectedCategory.intValue = i
|
||||
},
|
||||
) {
|
||||
if (i < categoryIcons.size && categoryIcons[i] != 0) {
|
||||
Icon(
|
||||
painterResource(id = categoryIcons[i]),
|
||||
contentDescription = categoryNames[i]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
items(
|
||||
emojis[selectedCategory.intValue]?.size ?: 0,
|
||||
key = { selectedCategory.intValue.toString() + "-" + emojis[selectedCategory.intValue]?.get(it)?.get(0) }
|
||||
) { index ->
|
||||
val emoji = emojis[selectedCategory.intValue]?.get(index) ?: return@items
|
||||
if (emoji.size > 1) {
|
||||
val tooltipState = rememberTooltipState(
|
||||
isPersistent = true,
|
||||
)
|
||||
TooltipBox(
|
||||
modifier = Modifier.animateItem(),
|
||||
positionProvider = TooltipDefaults
|
||||
.rememberTooltipPositionProvider(),
|
||||
state = tooltipState,
|
||||
tooltip = {
|
||||
RichTooltip {
|
||||
Row(
|
||||
modifier = Modifier.horizontalScroll(rememberScrollState())
|
||||
) {
|
||||
for (e in emoji) {
|
||||
EmojiButton(
|
||||
emoji = e,
|
||||
onClick = {
|
||||
onEmojiSelected(e)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
EmojiButton(
|
||||
emoji = emoji[0],
|
||||
onClick = {
|
||||
onEmojiSelected(emoji[0])
|
||||
},
|
||||
onLongClick = {
|
||||
scope.launch {
|
||||
tooltipState.show()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
EmojiButton(
|
||||
modifier = Modifier.animateItem(),
|
||||
emoji = emoji[0],
|
||||
onClick = {
|
||||
onEmojiSelected(emoji[0])
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmojiButton(
|
||||
emoji: String,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
.minimumInteractiveComponentSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = emoji,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun EmojiPickerPreview() {
|
||||
EmojiPicker(
|
||||
modifier = Modifier
|
||||
.height(500.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
@@ -398,7 +398,7 @@ fun CustomActions(
|
||||
val action = actions.customActions[i]
|
||||
val tooltipState = rememberTooltipState()
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
|
||||
state = tooltipState,
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
@@ -458,7 +458,7 @@ fun CustomActions(
|
||||
val action = actions.customActions.last()
|
||||
TooltipBox(
|
||||
state = tooltipState,
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(action.name.toString())
|
||||
|
||||
@@ -184,7 +184,7 @@ fun NotesWidget(
|
||||
val tooltipState = rememberTooltipState()
|
||||
TooltipBox(
|
||||
state = tooltipState,
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(
|
||||
@@ -227,7 +227,7 @@ fun NotesWidget(
|
||||
if (isLastWidget == false) {
|
||||
TooltipBox(
|
||||
state = tooltipState,
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(stringResource(R.string.notes_widget_action_dismiss))
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ fun CorePaletteColorPreference(
|
||||
|
||||
TooltipBox(
|
||||
state = tooltipState,
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(title)
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ fun ThemeColorPreference(
|
||||
|
||||
TooltipBox(
|
||||
state = tooltipState,
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
|
||||
tooltip = { PlainTooltip { Text(title) } }
|
||||
) {
|
||||
ColorSwatch(
|
||||
|
||||
@@ -6,6 +6,7 @@ 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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
@@ -22,8 +23,11 @@ import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Check
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Tag
|
||||
import androidx.compose.material.icons.rounded.Warning
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
@@ -38,6 +42,10 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.PathEffect
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -48,6 +56,7 @@ 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.component.SmallMessage
|
||||
import de.mm20.launcher2.ui.component.emojipicker.EmojiPicker
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
||||
|
||||
@@ -87,12 +96,18 @@ fun EditTagSheet(
|
||||
Text(stringResource(R.string.action_next))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (viewModel.page == EditTagSheetPage.PickItems) {
|
||||
{
|
||||
OutlinedButton(onClick = { viewModel.closeItemPicker() }) {
|
||||
Text(stringResource(id = R.string.ok))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
{
|
||||
OutlinedButton(onClick = { viewModel.closeIconPicker() }) {
|
||||
Text(stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismissRequest = {
|
||||
if (viewModel.page == EditTagSheetPage.CustomizeTag) {
|
||||
@@ -109,6 +124,7 @@ fun EditTagSheet(
|
||||
EditTagSheetPage.CreateTag -> CreateNewTagPage(viewModel, it)
|
||||
EditTagSheetPage.PickItems -> PickItems(viewModel, it)
|
||||
EditTagSheetPage.CustomizeTag -> CustomizeTag(viewModel, it)
|
||||
EditTagSheetPage.PickIcon -> PickIcon(viewModel, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,19 +254,72 @@ fun ListItem(
|
||||
@Composable
|
||||
fun CustomizeTag(viewModel: EditTagSheetVM, paddingValues: PaddingValues) {
|
||||
val iconSize = 32.dp.toPixels()
|
||||
val tagEmoji = viewModel.tagEmoji
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
label = { Text(stringResource(R.string.tag_name)) },
|
||||
value = viewModel.tagName,
|
||||
onValueChange = { viewModel.tagName = it }
|
||||
)
|
||||
) {
|
||||
val outlineVariant = MaterialTheme.colorScheme.outlineVariant
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable {
|
||||
viewModel.openIconPicker()
|
||||
}
|
||||
.size(56.dp)
|
||||
then (
|
||||
if (tagEmoji != null) {
|
||||
Modifier.background(
|
||||
MaterialTheme.colorScheme.secondaryContainer,
|
||||
CircleShape
|
||||
)
|
||||
} else {
|
||||
Modifier.drawBehind {
|
||||
val w = with(density) { 2.dp.toPx() }
|
||||
drawCircle(
|
||||
color = outlineVariant,
|
||||
style = Stroke(
|
||||
width = w,
|
||||
pathEffect = PathEffect.dashPathEffect(
|
||||
floatArrayOf(
|
||||
w * 2,
|
||||
w * 2
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (tagEmoji != null) {
|
||||
Text(tagEmoji)
|
||||
} else {
|
||||
Icon(
|
||||
Icons.Rounded.Tag,
|
||||
null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
singleLine = true,
|
||||
placeholder = { Text(stringResource(R.string.tag_name)) },
|
||||
value = viewModel.tagName,
|
||||
onValueChange = { viewModel.tagName = it },
|
||||
)
|
||||
}
|
||||
|
||||
val icon1 = remember(viewModel.taggedItems.getOrNull(0)?.key) {
|
||||
viewModel.taggedItems.getOrNull(0)?.let {
|
||||
viewModel.getIcon(it, iconSize.toInt())
|
||||
@@ -286,21 +355,27 @@ fun CustomizeTag(viewModel: EditTagSheetVM, paddingValues: PaddingValues) {
|
||||
.height(32.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
ShapedLauncherIcon(
|
||||
size = 32.dp,
|
||||
icon = { icon1?.value },
|
||||
modifier = Modifier.offset(x = -0.dp)
|
||||
)
|
||||
ShapedLauncherIcon(
|
||||
size = 32.dp,
|
||||
icon = { icon2?.value },
|
||||
modifier = Modifier.offset(x = -16.dp)
|
||||
)
|
||||
ShapedLauncherIcon(
|
||||
size = 32.dp,
|
||||
icon = { icon3?.value },
|
||||
modifier = Modifier.offset(x = -32.dp)
|
||||
)
|
||||
icon1?.value?.let {
|
||||
ShapedLauncherIcon(
|
||||
size = 32.dp,
|
||||
icon = { it },
|
||||
modifier = Modifier.offset(x = -0.dp)
|
||||
)
|
||||
}
|
||||
icon2?.value?.let {
|
||||
ShapedLauncherIcon(
|
||||
size = 32.dp,
|
||||
icon = { it },
|
||||
modifier = Modifier.offset(x = -16.dp)
|
||||
)
|
||||
}
|
||||
icon3?.value?.let {
|
||||
ShapedLauncherIcon(
|
||||
size = 32.dp,
|
||||
icon = { it },
|
||||
modifier = Modifier.offset(x = -32.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(viewModel.tagNameExists || viewModel.taggedItems.isEmpty()) {
|
||||
@@ -313,4 +388,44 @@ fun CustomizeTag(viewModel: EditTagSheetVM, paddingValues: PaddingValues) {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PickIcon(
|
||||
viewModel: EditTagSheetVM,
|
||||
paddingValues: PaddingValues
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
OutlinedButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
onClick = {
|
||||
viewModel.selectIcon(null)
|
||||
},
|
||||
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
|
||||
) {
|
||||
Icon(
|
||||
Icons.Rounded.Delete,
|
||||
null,
|
||||
modifier = Modifier
|
||||
.padding(end = ButtonDefaults.IconSpacing)
|
||||
.size(ButtonDefaults.IconSize)
|
||||
)
|
||||
Text(stringResource(R.string.reset_icon))
|
||||
}
|
||||
EmojiPicker(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
onEmojiSelected = {
|
||||
viewModel.selectIcon(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,10 +9,14 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.applications.AppRepository
|
||||
import de.mm20.launcher2.data.customattrs.CustomTextIcon
|
||||
import de.mm20.launcher2.icons.IconService
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.icons.StaticLauncherIcon
|
||||
import de.mm20.launcher2.icons.TextLayer
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchService
|
||||
import de.mm20.launcher2.search.Tag
|
||||
import de.mm20.launcher2.services.tags.TagsService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -31,6 +35,7 @@ class EditTagSheetVM : ViewModel(), KoinComponent {
|
||||
private var oldTagName by mutableStateOf<String?>(null)
|
||||
private var allTags by mutableStateOf(emptySet<String>())
|
||||
var tagName by mutableStateOf("")
|
||||
var tagEmoji by mutableStateOf<String?>(null)
|
||||
|
||||
var loading by mutableStateOf(true)
|
||||
|
||||
@@ -46,7 +51,6 @@ class EditTagSheetVM : ViewModel(), KoinComponent {
|
||||
|
||||
|
||||
fun init(tag: String?) {
|
||||
Log.d("MM20", "Init with tag: $tag")
|
||||
loading = true
|
||||
this.oldTagName = tag
|
||||
this.tagName = tag ?: ""
|
||||
@@ -55,6 +59,9 @@ class EditTagSheetVM : ViewModel(), KoinComponent {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
allTags = tagService.getAllTags().first().toSet()
|
||||
val items = if (tag != null) tagService.getTaggedItems(tag).first() else emptyList()
|
||||
val icon = if (tag != null) iconService.getIcon(Tag(tag), 0).first() else null
|
||||
tagEmoji = ((icon as? StaticLauncherIcon)?.foregroundLayer as? TextLayer)?.text
|
||||
|
||||
val apps = appRepository.findMany().first { it.isNotEmpty() }.sorted()
|
||||
taggedItems = items
|
||||
taggableApps = apps.map { app -> TaggableItem(app, items.any { app.key == it.key }) }
|
||||
@@ -69,9 +76,19 @@ class EditTagSheetVM : ViewModel(), KoinComponent {
|
||||
fun save() {
|
||||
val oldName = oldTagName
|
||||
val newName = tagName
|
||||
val tagEmoji = tagEmoji
|
||||
if (taggedItems.isEmpty() && oldName != null) tagService.deleteTag(oldName)
|
||||
else if (oldName != null) tagService.updateTag(oldName, newName = newName, items = taggedItems)
|
||||
else tagService.createTag(tagName, taggedItems)
|
||||
|
||||
if (oldName != null && oldName != newName) {
|
||||
iconService.setCustomIcon(Tag(oldName), null)
|
||||
}
|
||||
if (tagEmoji != null) {
|
||||
iconService.setCustomIcon(Tag(newName), CustomTextIcon(tagEmoji))
|
||||
} else {
|
||||
iconService.setCustomIcon(Tag(newName), null)
|
||||
}
|
||||
loading = true
|
||||
}
|
||||
|
||||
@@ -89,6 +106,19 @@ class EditTagSheetVM : ViewModel(), KoinComponent {
|
||||
page = EditTagSheetPage.PickItems
|
||||
}
|
||||
|
||||
fun openIconPicker() {
|
||||
page = EditTagSheetPage.PickIcon
|
||||
}
|
||||
|
||||
fun closeIconPicker() {
|
||||
page = EditTagSheetPage.CustomizeTag
|
||||
}
|
||||
|
||||
fun selectIcon(emoji: String?) {
|
||||
tagEmoji = emoji
|
||||
closeIconPicker()
|
||||
}
|
||||
|
||||
fun closeItemPicker() {
|
||||
page = EditTagSheetPage.CustomizeTag
|
||||
}
|
||||
@@ -114,6 +144,7 @@ enum class EditTagSheetPage {
|
||||
CreateTag,
|
||||
PickItems,
|
||||
CustomizeTag,
|
||||
PickIcon,
|
||||
}
|
||||
|
||||
@Stable
|
||||
|
||||
Reference in New Issue
Block a user