Feature: Lock Single Tag for Favorites Widget (#1355)

* single tag for favorites

* fixing

* don't need this one

* cleanup

* new features - select for favorites/tags customization

* bug fix for tags

* bug fix to show pinned tags

* make sure pinned tag works on load

* better way to work with viewmodel (learning as i go)

* back to working state.. can be improved

* adding some comments

* cleanup

---------

Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
rac146
2025-12-21 12:01:06 -05:00
committed by GitHub
parent 97922805aa
commit d6214ef9fa
9 changed files with 437 additions and 120 deletions

View File

@@ -46,6 +46,7 @@ fun FavoritesTagSelector(
compact: Boolean,
expanded: Boolean,
onExpand: (Boolean) -> Unit,
showFavorites: Boolean
) {
val sheetManager = LocalBottomSheetManager.current
@@ -70,40 +71,42 @@ fun FavoritesTagSelector(
.horizontalScroll(scrollState)
.padding(end = 12.dp),
) {
FilterChip(
modifier = Modifier
.padding(start = 16.dp),
selected = selectedTag == null,
onClick = { onSelectTag(null) },
leadingIcon = if (compact) null else {
{
Icon(
painter = painterResource(R.drawable.star_20px_filled),
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize),
)
if (showFavorites) {
FilterChip(
modifier = Modifier
.padding(start = 16.dp),
selected = selectedTag == null,
onClick = { onSelectTag(null) },
leadingIcon = if (compact) null else {
{
Icon(
painter = painterResource(R.drawable.star_20px_filled),
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize),
)
}
},
label = {
if (compact) {
Icon(
painter = painterResource(R.drawable.star_20px_filled),
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize),
)
} else {
Text(stringResource(R.string.favorites))
}
}
},
label = {
if (compact) {
Icon(
painter = painterResource(R.drawable.star_20px_filled),
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize),
)
} else {
Text(stringResource(R.string.favorites))
}
}
)
for (tag in tags) {
)
}
for ((i, tag) in tags.withIndex()) {
TagChip(
modifier = Modifier
.padding(start = 8.dp),
.padding(start = if (!showFavorites && i == 0) 16.dp else 8.dp),
tag = tag,
selected = selectedTag == tag.tag,
onClick = {
if (selectedTag == tag.tag) {
if (selectedTag == tag.tag && showFavorites) {
onSelectTag(null)
} else {
onSelectTag(tag.tag)
@@ -184,7 +187,7 @@ fun FavoritesTagSelector(
compact = compact,
selected = selectedTag == tag.tag,
onClick = {
if (selectedTag == tag.tag) {
if (selectedTag == tag.tag && showFavorites) {
onSelectTag(null)
} else {
onSelectTag(tag.tag)

View File

@@ -44,7 +44,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
minPinnedLevel = PinnedLevel.AutomaticallySorted,
).map {
it.filterIsInstance<Tag>()
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
open val favorites: Flow<List<SavableSearchable>> = selectedTag.flatMapLatest { tag ->
if (tag == null) {

View File

@@ -73,6 +73,7 @@ fun LazyListScope.SearchFavorites(
expanded = tagsExpanded,
compact = compactTags,
onExpand = onExpandTags,
showFavorites = true
)
}
}

View File

@@ -12,33 +12,56 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ButtonGroupDefaults
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.DropdownMenuGroup
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.DropdownMenuPopup
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuDefaults
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedCard
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.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -52,20 +75,24 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.zIndex
import androidx.core.net.toUri
import androidx.lifecycle.compose.LocalLifecycleOwner
import de.mm20.launcher2.calendar.CalendarRepository
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.plugin.PluginRepository
import de.mm20.launcher2.plugin.PluginType
import de.mm20.launcher2.search.Tag
import de.mm20.launcher2.search.calendar.CalendarListType
import de.mm20.launcher2.themes.colors.atTone
import de.mm20.launcher2.ui.R
@@ -74,6 +101,9 @@ import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.DragResizeHandle
import de.mm20.launcher2.ui.component.LargeMessage
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.dragndrop.DraggableItem
import de.mm20.launcher2.ui.component.dragndrop.LazyDragAndDropColumn
import de.mm20.launcher2.ui.component.dragndrop.rememberLazyDragAndDropListState
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@@ -89,8 +119,10 @@ import de.mm20.launcher2.widgets.MusicWidget
import de.mm20.launcher2.widgets.NotesWidget
import de.mm20.launcher2.widgets.WeatherWidget
import de.mm20.launcher2.widgets.Widget
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.map
import org.koin.compose.koinInject
import java.text.Collator
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import kotlin.math.roundToInt
@@ -157,15 +189,16 @@ fun ColumnScope.ConfigureWeatherWidget(
bottom = 8.dp,
),
onClick = {
context.startActivity(Intent(
context,
SettingsActivity::class.java
).apply {
putExtra(
SettingsActivity.EXTRA_ROUTE,
SettingsActivity.ROUTE_WEATHER_INTEGRATION
)
})
context.startActivity(
Intent(
context,
SettingsActivity::class.java
).apply {
putExtra(
SettingsActivity.EXTRA_ROUTE,
SettingsActivity.ROUTE_WEATHER_INTEGRATION
)
})
}) {
Text(stringResource(R.string.widget_config_weather_integration_settings))
Icon(
@@ -182,49 +215,342 @@ fun ColumnScope.ConfigureFavoritesWidget(
widget: FavoritesWidget,
onWidgetUpdated: (FavoritesWidget) -> Unit,
) {
val customAttrRepository = koinInject<CustomAttributesRepository>()
val allTags by remember {
customAttrRepository
.getAllTags()
.map {
val collator = Collator.getInstance().apply { strength = Collator.SECONDARY }
it
.sortedWith { el1, el2 ->
collator.compare(el1, el2)
}
}
}.collectAsState(emptyList())
val tagsListState = rememberLazyListState()
val bottomSheetManager = LocalBottomSheetManager.current
OutlinedCard {
Column(
modifier = Modifier.fillMaxWidth()
var createTag by remember { mutableStateOf(false) }
Row(
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
) {
ToggleButton(
modifier = Modifier.weight(1f),
checked = !widget.config.customTags,
onCheckedChange = {
if (it) {
onWidgetUpdated(widget.copy(config = widget.config.copy(customTags = false)))
}
},
shapes = ButtonGroupDefaults.connectedLeadingButtonShapes(),
) {
SwitchPreference(
title = stringResource(R.string.preference_edit_button),
iconPadding = false,
value = widget.config.editButton,
onValueChanged = {
onWidgetUpdated(widget.copy(config = widget.config.copy(editButton = it)))
}
Icon(
painterResource(
if (!widget.config.customTags) R.drawable.check_20px else R.drawable.star_20px
),
contentDescription = null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
SwitchPreference(
title = stringResource(R.string.preference_compact_tags),
iconPadding = false,
value = widget.config.compactTags,
onValueChanged = {
onWidgetUpdated(widget.copy(config = widget.config.copy(compactTags = it)))
Text(stringResource(R.string.favorites))
}
ToggleButton(
modifier = Modifier.weight(1f),
checked = widget.config.customTags,
onCheckedChange = {
if (it) {
onWidgetUpdated(
widget.copy(
config = widget.config.copy(customTags = true)
)
)
}
},
shapes = ButtonGroupDefaults.connectedTrailingButtonShapes(),
) {
Icon(
painterResource(
if (widget.config.customTags) R.drawable.check_20px else R.drawable.tag_20px
),
contentDescription = null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_screen_tags))
}
}
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = PaddingValues(
end = 16.dp,
top = 8.dp,
start = 24.dp,
bottom = 8.dp,
),
onClick = {
bottomSheetManager.showEditFavoritesSheet()
}) {
Text(stringResource(R.string.menu_item_edit_favs))
Icon(
AnimatedContent(widget.config.customTags) { showTags ->
Column(
modifier = Modifier
.padding(start = ButtonDefaults.IconSpacing)
.requiredSize(ButtonDefaults.IconSize),
painter = painterResource(R.drawable.open_in_new_20px), contentDescription = null
)
.fillMaxWidth(),
) {
if (showTags) {
var tagList by remember {
mutableStateOf(widget.config.tagList.toImmutableList())
}
val availableTags by remember {
derivedStateOf { allTags - tagList }
}
LaunchedEffect(tagList) {
onWidgetUpdated(
widget.copy(
config = widget.config.copy(
tagList = tagList
)
)
)
}
OutlinedCard(
modifier = Modifier.padding(top = 16.dp)
) {
val rowState = rememberLazyDragAndDropListState(
listState = tagsListState,
onItemMove = { from, to ->
val newTagList = tagList.toMutableList()
val tag = newTagList.removeAt(from.index)
newTagList.add(to.index, tag)
tagList = newTagList.toImmutableList()
},
)
LazyDragAndDropColumn(
state = rowState,
modifier = Modifier
.heightIn(max = 9999.dp)
.fillMaxWidth()
.animateContentSize(MaterialTheme.motionScheme.defaultSpatialSpec()),
bidirectionalDrag = false,
) {
items(
tagList,
key = { it }
) { tag ->
DraggableItem(state = rowState, key = tag) {
val elevation by animateDpAsState(if (it) 4.dp else 0.dp)
Surface(
shadowElevation = elevation,
tonalElevation = elevation,
modifier = Modifier.zIndex(if (it) 1f else 0f)
) {
Preference(
title = tag,
icon = R.drawable.tag_24px,
controls = {
IconButton(
modifier = Modifier.offset(x = 8.dp),
onClick = {
tagList = (tagList - tag).toImmutableList()
}
) {
Icon(
painterResource(R.drawable.close_24px),
stringResource(R.string.menu_remove),
)
}
}
)
}
}
}
}
}
var showAddMenu by remember { mutableStateOf(false) }
Box {
FilledTonalButton(
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp),
onClick = {
showAddMenu = true
},
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
shapes = ButtonDefaults.shapes()
) {
Icon(
painterResource(R.drawable.add_20px),
null,
modifier = Modifier
.padding(end = ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text(stringResource(R.string.select_tag))
}
DropdownMenuPopup(
expanded = showAddMenu,
onDismissRequest = { showAddMenu = false }) {
if (availableTags.isNotEmpty()) {
DropdownMenuGroup(
shapes = MenuDefaults.groupShape(0, 2),
) {
for ((i, tag) in availableTags.withIndex()) {
DropdownMenuItem(
shape = if (availableTags.size == 1) MenuDefaults.standaloneItemShape
else when (i) {
0 -> MenuDefaults.leadingItemShape
availableTags.lastIndex -> MenuDefaults.trailingItemShape
else -> MenuDefaults.middleItemShape
},
leadingIcon = {
Icon(
painterResource(R.drawable.tag_24px),
null
)
},
text = { Text(tag) },
onClick = {
tagList = (tagList + tag).toImmutableList()
showAddMenu = false
})
}
}
Spacer(
modifier = Modifier.height(MenuDefaults.GroupSpacing)
)
}
DropdownMenuGroup(
shapes = MenuDefaults.groupShape(
if (availableTags.isEmpty()) 0 else 1,
if (availableTags.isEmpty()) 1 else 2,
)
) {
DropdownMenuItem(
shape = MenuDefaults.standaloneItemShape,
leadingIcon = {
Icon(painterResource(R.drawable.add_24px), null)
},
text = {
Text(
stringResource(R.string.edit_favorites_dialog_new_tag),
)
},
onClick = {
createTag = true
showAddMenu = false
}
)
}
}
}
if (createTag) {
EditTagSheet(
tag = null,
onTagSaved = { tag ->
val newTag = Tag(tag)
tagList = (tagList + newTag.tag).toImmutableList()
},
onDismiss = {
createTag = false
}
)
}
OutlinedCard(
modifier = Modifier.padding(top = 16.dp)
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
SwitchPreference(
title = stringResource(R.string.preference_compact_tags),
iconPadding = false,
value = widget.config.compactTags,
onValueChanged = {
onWidgetUpdated(
widget.copy(
config = widget.config.copy(
compactTags = it
)
)
)
},
enabled = widget.config.tagList.size > 1,
)
}
}
} else {
Column(
modifier = Modifier.fillMaxWidth()
) {
OutlinedCard(
modifier = Modifier.padding(top = 16.dp)
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
SwitchPreference(
title = stringResource(R.string.preference_edit_button),
iconPadding = false,
value = widget.config.editButton,
onValueChanged = {
onWidgetUpdated(
widget.copy(
config = widget.config.copy(
editButton = it
)
)
)
}
)
SwitchPreference(
title = stringResource(R.string.customize_item_tags),
iconPadding = false,
value = widget.config.compactTags,
onValueChanged = {
onWidgetUpdated(
widget.copy(
config = widget.config.copy(
compactTags = it
)
)
)
}
)
}
}
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = PaddingValues(
end = 16.dp,
top = 8.dp,
start = 24.dp,
bottom = 8.dp,
),
onClick = {
bottomSheetManager.showEditFavoritesSheet()
}) {
Text(stringResource(R.string.menu_item_edit_favs))
Icon(
modifier = Modifier
.padding(start = ButtonDefaults.IconSpacing)
.requiredSize(ButtonDefaults.IconSize),
painter = painterResource(R.drawable.open_in_new_20px),
contentDescription = null
)
}
}
}
}
}
}
@@ -232,7 +558,6 @@ fun ColumnScope.ConfigureFavoritesWidget(
fun ColumnScope.ConfigureMusicWidget(
widget: MusicWidget,
onWidgetUpdated: (MusicWidget) -> Unit,
) {
val context = LocalContext.current
@@ -261,15 +586,16 @@ fun ColumnScope.ConfigureMusicWidget(
bottom = 8.dp,
),
onClick = {
context.startActivity(Intent(
context,
SettingsActivity::class.java
).apply {
putExtra(
SettingsActivity.EXTRA_ROUTE,
SettingsActivity.ROUTE_MEDIA_INTEGRATION,
)
})
context.startActivity(
Intent(
context,
SettingsActivity::class.java
).apply {
putExtra(
SettingsActivity.EXTRA_ROUTE,
SettingsActivity.ROUTE_MEDIA_INTEGRATION,
)
})
}) {
Text(stringResource(R.string.widget_config_music_integration_settings))
Icon(
@@ -504,7 +830,8 @@ fun ColumnScope.ConfigureAppWidget(
modifier = Modifier
.padding(start = ButtonDefaults.IconSpacing)
.requiredSize(ButtonDefaults.IconSize),
painter = painterResource(R.drawable.open_in_new_20px), contentDescription = null
painter = painterResource(R.drawable.open_in_new_20px),
contentDescription = null
)
}
}
@@ -670,6 +997,7 @@ fun ConfigureNotesWidget(
onWidgetUpdated: (NotesWidget) -> Unit
) {
val context = LocalContext.current
val resources = LocalResources.current
val linkFileLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("text/markdown")
) {
@@ -733,7 +1061,7 @@ fun ConfigureNotesWidget(
icon = R.drawable.link_24px,
onClick = {
linkFileLauncher.launch(
context.getString(
resources.getString(
R.string.notes_widget_export_filename,
ZonedDateTime.now().format(
DateTimeFormatter.ISO_INSTANT

View File

@@ -45,7 +45,7 @@ fun AppWidget(
) {
val context = LocalContext.current
val lightBackground = (!LocalDarkTheme.current && widget.config.background) || LocalPreferDarkContentOverWallpaper.current
val lightBackground = if (widget.config.background) !LocalDarkTheme.current else LocalPreferDarkContentOverWallpaper.current
val widgetInfo = remember(widget.config.widgetId) {
AppWidgetManager.getInstance(context)

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.Tag
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.FavoritesTagSelector
import de.mm20.launcher2.ui.component.Banner
@@ -24,10 +25,12 @@ fun FavoritesWidget(widget: FavoritesWidget) {
val favorites by remember { viewModel.favorites }.collectAsState(emptyList())
val pinnedTags by viewModel.pinnedTags.collectAsState(emptyList())
val selectedTag by viewModel.selectedTag.collectAsState(null)
val compactTags by viewModel.compactTags.collectAsState(false)
val favoritesEditButton = widget.config.editButton
val tagsExpanded by viewModel.tagsExpanded.collectAsState(false)
val customTags = widget.config.customTags && widget.config.tagList.isNotEmpty()
val favoritesEditButton = !customTags && widget.config.editButton
val compactTags = widget.config.compactTags
val tagsExpanded = widget.config.tagsMultiline
LaunchedEffect(widget) {
viewModel.updateWidget(widget)
@@ -47,9 +50,9 @@ fun FavoritesWidget(widget: FavoritesWidget) {
icon = if (selectedTag == null) R.drawable.star_24px else R.drawable.tag_24px,
)
}
if (pinnedTags.isNotEmpty() || favoritesEditButton) {
if (favoritesEditButton || (customTags && widget.config.tagList.size > 1)) {
FavoritesTagSelector(
tags = pinnedTags,
tags = if (customTags) widget.config.tagList.map { Tag(it) } else pinnedTags,
selectedTag = selectedTag,
editButton = favoritesEditButton,
reverse = false,
@@ -57,7 +60,8 @@ fun FavoritesWidget(widget: FavoritesWidget) {
scrollState = rememberScrollState(),
expanded = tagsExpanded,
compact = compactTags,
onExpand = { viewModel.setTagsExpanded(it) }
onExpand = { viewModel.setTagsExpanded(it) },
showFavorites = !customTags
)
}
}

View File

@@ -1,6 +1,5 @@
package de.mm20.launcher2.ui.launcher.widgets.favorites
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.preferences.ui.GridSettings
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.services.widgets.WidgetsService
@@ -10,40 +9,16 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.koin.core.component.inject
class FavoritesWidgetVM : FavoritesVM() {
private val widgetsService: WidgetsService by inject()
private val uiSettings: UiSettings by inject()
private val widget = MutableStateFlow<FavoritesWidget?>(null)
override val tagsExpanded = widget.map { it?.config?.tagsMultiline == true }
override val compactTags: Flow<Boolean> = widget.map { it?.config?.compactTags == true }
private val isTopWidget = widgetsService.isFavoritesWidgetFirst()
private val clockWidgetFavSlots =
combine(uiSettings.dock, isTopWidget, uiSettings.gridSettings) { (dock, isTop, grid) ->
dock as Boolean
isTop as Boolean
grid as GridSettings
if (!isTop || !dock) 0
else {
grid.columnCount
}
}
override val favorites = super.favorites.combine(clockWidgetFavSlots) { favs, slots ->
if (selectedTag.value == null) {
if (favs.lastIndex < slots) emptyList()
else favs.subList(slots, favs.size)
} else {
favs
}
}
override fun setTagsExpanded(expanded: Boolean) {
val widget = this.widget.value ?: return
widgetsService.updateWidget(
@@ -54,7 +29,11 @@ class FavoritesWidgetVM : FavoritesVM() {
}
fun updateWidget(widget: FavoritesWidget) {
selectTag(null)
if (widget.config.customTags) {
selectTag(widget.config.tagList.firstOrNull())
}
this.widget.value = widget
}
}

View File

@@ -1070,4 +1070,5 @@
<string name="preference_transliteration">Preferred transliteration</string>
<string name="preference_transliteration_auto">Automatic</string>
<string name="preference_transliteration_disabled">Disabled</string>
<string name="select_tag">Select tag</string>
</resources>

View File

@@ -2,15 +2,16 @@ package de.mm20.launcher2.widgets
import de.mm20.launcher2.database.entities.PartialWidgetEntity
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.UUID
@Serializable
data class FavoritesWidgetConfig(
val customTags: Boolean = false,
val editButton: Boolean = true,
val tagsMultiline: Boolean = false,
val compactTags: Boolean = false,
val tagList: List<String> = emptyList()
)
data class FavoritesWidget(