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:
@@ -46,6 +46,7 @@ fun FavoritesTagSelector(
|
|||||||
compact: Boolean,
|
compact: Boolean,
|
||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
onExpand: (Boolean) -> Unit,
|
onExpand: (Boolean) -> Unit,
|
||||||
|
showFavorites: Boolean
|
||||||
) {
|
) {
|
||||||
val sheetManager = LocalBottomSheetManager.current
|
val sheetManager = LocalBottomSheetManager.current
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ fun FavoritesTagSelector(
|
|||||||
.horizontalScroll(scrollState)
|
.horizontalScroll(scrollState)
|
||||||
.padding(end = 12.dp),
|
.padding(end = 12.dp),
|
||||||
) {
|
) {
|
||||||
|
if (showFavorites) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 16.dp),
|
.padding(start = 16.dp),
|
||||||
@@ -96,14 +98,15 @@ fun FavoritesTagSelector(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
for (tag in tags) {
|
}
|
||||||
|
for ((i, tag) in tags.withIndex()) {
|
||||||
TagChip(
|
TagChip(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 8.dp),
|
.padding(start = if (!showFavorites && i == 0) 16.dp else 8.dp),
|
||||||
tag = tag,
|
tag = tag,
|
||||||
selected = selectedTag == tag.tag,
|
selected = selectedTag == tag.tag,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (selectedTag == tag.tag) {
|
if (selectedTag == tag.tag && showFavorites) {
|
||||||
onSelectTag(null)
|
onSelectTag(null)
|
||||||
} else {
|
} else {
|
||||||
onSelectTag(tag.tag)
|
onSelectTag(tag.tag)
|
||||||
@@ -184,7 +187,7 @@ fun FavoritesTagSelector(
|
|||||||
compact = compact,
|
compact = compact,
|
||||||
selected = selectedTag == tag.tag,
|
selected = selectedTag == tag.tag,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (selectedTag == tag.tag) {
|
if (selectedTag == tag.tag && showFavorites) {
|
||||||
onSelectTag(null)
|
onSelectTag(null)
|
||||||
} else {
|
} else {
|
||||||
onSelectTag(tag.tag)
|
onSelectTag(tag.tag)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
|||||||
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||||
).map {
|
).map {
|
||||||
it.filterIsInstance<Tag>()
|
it.filterIsInstance<Tag>()
|
||||||
}
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||||
|
|
||||||
open val favorites: Flow<List<SavableSearchable>> = selectedTag.flatMapLatest { tag ->
|
open val favorites: Flow<List<SavableSearchable>> = selectedTag.flatMapLatest { tag ->
|
||||||
if (tag == null) {
|
if (tag == null) {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ fun LazyListScope.SearchFavorites(
|
|||||||
expanded = tagsExpanded,
|
expanded = tagsExpanded,
|
||||||
compact = compactTags,
|
compact = compactTags,
|
||||||
onExpand = onExpandTags,
|
onExpand = onExpandTags,
|
||||||
|
showFavorites = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,33 +12,56 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
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.background
|
||||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
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.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredSize
|
import androidx.compose.foundation.layout.requiredSize
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.ButtonGroupDefaults
|
||||||
import androidx.compose.material3.CheckboxDefaults
|
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.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MenuDefaults
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedCard
|
import androidx.compose.material3.OutlinedCard
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.ToggleButton
|
||||||
|
import androidx.compose.material3.ToggleButtonDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
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.PointerEventPass
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalResources
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.isUnspecified
|
import androidx.compose.ui.unit.isUnspecified
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import de.mm20.launcher2.calendar.CalendarRepository
|
import de.mm20.launcher2.calendar.CalendarRepository
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
||||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.plugin.PluginRepository
|
import de.mm20.launcher2.plugin.PluginRepository
|
||||||
import de.mm20.launcher2.plugin.PluginType
|
import de.mm20.launcher2.plugin.PluginType
|
||||||
|
import de.mm20.launcher2.search.Tag
|
||||||
import de.mm20.launcher2.search.calendar.CalendarListType
|
import de.mm20.launcher2.search.calendar.CalendarListType
|
||||||
import de.mm20.launcher2.themes.colors.atTone
|
import de.mm20.launcher2.themes.colors.atTone
|
||||||
import de.mm20.launcher2.ui.R
|
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.DragResizeHandle
|
||||||
import de.mm20.launcher2.ui.component.LargeMessage
|
import de.mm20.launcher2.ui.component.LargeMessage
|
||||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
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.CheckboxPreference
|
||||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
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.NotesWidget
|
||||||
import de.mm20.launcher2.widgets.WeatherWidget
|
import de.mm20.launcher2.widgets.WeatherWidget
|
||||||
import de.mm20.launcher2.widgets.Widget
|
import de.mm20.launcher2.widgets.Widget
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
|
import java.text.Collator
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -157,7 +189,8 @@ fun ColumnScope.ConfigureWeatherWidget(
|
|||||||
bottom = 8.dp,
|
bottom = 8.dp,
|
||||||
),
|
),
|
||||||
onClick = {
|
onClick = {
|
||||||
context.startActivity(Intent(
|
context.startActivity(
|
||||||
|
Intent(
|
||||||
context,
|
context,
|
||||||
SettingsActivity::class.java
|
SettingsActivity::class.java
|
||||||
).apply {
|
).apply {
|
||||||
@@ -182,25 +215,313 @@ fun ColumnScope.ConfigureFavoritesWidget(
|
|||||||
widget: FavoritesWidget,
|
widget: FavoritesWidget,
|
||||||
onWidgetUpdated: (FavoritesWidget) -> Unit,
|
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
|
val bottomSheetManager = LocalBottomSheetManager.current
|
||||||
OutlinedCard {
|
|
||||||
|
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(),
|
||||||
|
) {
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedContent(widget.config.customTags) { showTags ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.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(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
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(
|
SwitchPreference(
|
||||||
title = stringResource(R.string.preference_compact_tags),
|
title = stringResource(R.string.preference_compact_tags),
|
||||||
iconPadding = false,
|
iconPadding = false,
|
||||||
value = widget.config.compactTags,
|
value = widget.config.compactTags,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
onWidgetUpdated(widget.copy(config = widget.config.copy(compactTags = it)))
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -223,16 +544,20 @@ fun ColumnScope.ConfigureFavoritesWidget(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = ButtonDefaults.IconSpacing)
|
.padding(start = ButtonDefaults.IconSpacing)
|
||||||
.requiredSize(ButtonDefaults.IconSize),
|
.requiredSize(ButtonDefaults.IconSize),
|
||||||
painter = painterResource(R.drawable.open_in_new_20px), contentDescription = null
|
painter = painterResource(R.drawable.open_in_new_20px),
|
||||||
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ColumnScope.ConfigureMusicWidget(
|
fun ColumnScope.ConfigureMusicWidget(
|
||||||
widget: MusicWidget,
|
widget: MusicWidget,
|
||||||
onWidgetUpdated: (MusicWidget) -> Unit,
|
onWidgetUpdated: (MusicWidget) -> Unit,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -261,7 +586,8 @@ fun ColumnScope.ConfigureMusicWidget(
|
|||||||
bottom = 8.dp,
|
bottom = 8.dp,
|
||||||
),
|
),
|
||||||
onClick = {
|
onClick = {
|
||||||
context.startActivity(Intent(
|
context.startActivity(
|
||||||
|
Intent(
|
||||||
context,
|
context,
|
||||||
SettingsActivity::class.java
|
SettingsActivity::class.java
|
||||||
).apply {
|
).apply {
|
||||||
@@ -504,7 +830,8 @@ fun ColumnScope.ConfigureAppWidget(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = ButtonDefaults.IconSpacing)
|
.padding(start = ButtonDefaults.IconSpacing)
|
||||||
.requiredSize(ButtonDefaults.IconSize),
|
.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
|
onWidgetUpdated: (NotesWidget) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val resources = LocalResources.current
|
||||||
val linkFileLauncher = rememberLauncherForActivityResult(
|
val linkFileLauncher = rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.CreateDocument("text/markdown")
|
ActivityResultContracts.CreateDocument("text/markdown")
|
||||||
) {
|
) {
|
||||||
@@ -733,7 +1061,7 @@ fun ConfigureNotesWidget(
|
|||||||
icon = R.drawable.link_24px,
|
icon = R.drawable.link_24px,
|
||||||
onClick = {
|
onClick = {
|
||||||
linkFileLauncher.launch(
|
linkFileLauncher.launch(
|
||||||
context.getString(
|
resources.getString(
|
||||||
R.string.notes_widget_export_filename,
|
R.string.notes_widget_export_filename,
|
||||||
ZonedDateTime.now().format(
|
ZonedDateTime.now().format(
|
||||||
DateTimeFormatter.ISO_INSTANT
|
DateTimeFormatter.ISO_INSTANT
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ fun AppWidget(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
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) {
|
val widgetInfo = remember(widget.config.widgetId) {
|
||||||
AppWidgetManager.getInstance(context)
|
AppWidgetManager.getInstance(context)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.search.Tag
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.common.FavoritesTagSelector
|
import de.mm20.launcher2.ui.common.FavoritesTagSelector
|
||||||
import de.mm20.launcher2.ui.component.Banner
|
import de.mm20.launcher2.ui.component.Banner
|
||||||
@@ -24,10 +25,12 @@ fun FavoritesWidget(widget: FavoritesWidget) {
|
|||||||
val favorites by remember { viewModel.favorites }.collectAsState(emptyList())
|
val favorites by remember { viewModel.favorites }.collectAsState(emptyList())
|
||||||
val pinnedTags by viewModel.pinnedTags.collectAsState(emptyList())
|
val pinnedTags by viewModel.pinnedTags.collectAsState(emptyList())
|
||||||
val selectedTag by viewModel.selectedTag.collectAsState(null)
|
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) {
|
LaunchedEffect(widget) {
|
||||||
viewModel.updateWidget(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,
|
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(
|
FavoritesTagSelector(
|
||||||
tags = pinnedTags,
|
tags = if (customTags) widget.config.tagList.map { Tag(it) } else pinnedTags,
|
||||||
selectedTag = selectedTag,
|
selectedTag = selectedTag,
|
||||||
editButton = favoritesEditButton,
|
editButton = favoritesEditButton,
|
||||||
reverse = false,
|
reverse = false,
|
||||||
@@ -57,7 +60,8 @@ fun FavoritesWidget(widget: FavoritesWidget) {
|
|||||||
scrollState = rememberScrollState(),
|
scrollState = rememberScrollState(),
|
||||||
expanded = tagsExpanded,
|
expanded = tagsExpanded,
|
||||||
compact = compactTags,
|
compact = compactTags,
|
||||||
onExpand = { viewModel.setTagsExpanded(it) }
|
onExpand = { viewModel.setTagsExpanded(it) },
|
||||||
|
showFavorites = !customTags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.widgets.favorites
|
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.GridSettings
|
||||||
import de.mm20.launcher2.preferences.ui.UiSettings
|
import de.mm20.launcher2.preferences.ui.UiSettings
|
||||||
import de.mm20.launcher2.services.widgets.WidgetsService
|
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.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
class FavoritesWidgetVM : FavoritesVM() {
|
class FavoritesWidgetVM : FavoritesVM() {
|
||||||
|
|
||||||
private val widgetsService: WidgetsService by inject()
|
private val widgetsService: WidgetsService by inject()
|
||||||
|
|
||||||
private val uiSettings: UiSettings by inject()
|
|
||||||
|
|
||||||
private val widget = MutableStateFlow<FavoritesWidget?>(null)
|
private val widget = MutableStateFlow<FavoritesWidget?>(null)
|
||||||
override val tagsExpanded = widget.map { it?.config?.tagsMultiline == true }
|
override val tagsExpanded = widget.map { it?.config?.tagsMultiline == true }
|
||||||
override val compactTags: Flow<Boolean> = widget.map { it?.config?.compactTags == 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) {
|
override fun setTagsExpanded(expanded: Boolean) {
|
||||||
val widget = this.widget.value ?: return
|
val widget = this.widget.value ?: return
|
||||||
widgetsService.updateWidget(
|
widgetsService.updateWidget(
|
||||||
@@ -54,7 +29,11 @@ class FavoritesWidgetVM : FavoritesVM() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateWidget(widget: FavoritesWidget) {
|
fun updateWidget(widget: FavoritesWidget) {
|
||||||
this.widget.value = widget
|
selectTag(null)
|
||||||
|
if (widget.config.customTags) {
|
||||||
|
selectTag(widget.config.tagList.firstOrNull())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.widget.value = widget
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1070,4 +1070,5 @@
|
|||||||
<string name="preference_transliteration">Preferred transliteration</string>
|
<string name="preference_transliteration">Preferred transliteration</string>
|
||||||
<string name="preference_transliteration_auto">Automatic</string>
|
<string name="preference_transliteration_auto">Automatic</string>
|
||||||
<string name="preference_transliteration_disabled">Disabled</string>
|
<string name="preference_transliteration_disabled">Disabled</string>
|
||||||
|
<string name="select_tag">Select tag</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ package de.mm20.launcher2.widgets
|
|||||||
|
|
||||||
import de.mm20.launcher2.database.entities.PartialWidgetEntity
|
import de.mm20.launcher2.database.entities.PartialWidgetEntity
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FavoritesWidgetConfig(
|
data class FavoritesWidgetConfig(
|
||||||
|
val customTags: Boolean = false,
|
||||||
val editButton: Boolean = true,
|
val editButton: Boolean = true,
|
||||||
val tagsMultiline: Boolean = false,
|
val tagsMultiline: Boolean = false,
|
||||||
val compactTags: Boolean = false,
|
val compactTags: Boolean = false,
|
||||||
|
val tagList: List<String> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
data class FavoritesWidget(
|
data class FavoritesWidget(
|
||||||
|
|||||||
Reference in New Issue
Block a user