diff --git a/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt new file mode 100644 index 00000000..79422944 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt @@ -0,0 +1,80 @@ +package de.mm20.launcher2.ui.common + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import de.mm20.launcher2.customattrs.CustomAttributesRepository +import de.mm20.launcher2.favorites.FavoritesRepository +import de.mm20.launcher2.preferences.LauncherDataStore +import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.widgets.WidgetRepository +import kotlinx.coroutines.flow.* +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +open class FavoritesVM : ViewModel(), KoinComponent { + + private val favoritesRepository: FavoritesRepository by inject() + private val widgetRepository: WidgetRepository by inject() + private val customAttributesRepository: CustomAttributesRepository by inject() + private val dataStore: LauncherDataStore by inject() + + val selectedTag = MutableStateFlow(null) + + val favorites: Flow> = selectedTag.flatMapLatest { tag -> + if (tag == null) { + val columns = dataStore.data.map { it.grid.columnCount } + val excludeCalendar = widgetRepository.isCalendarWidgetEnabled() + val favoritesEnabled = dataStore.data.map { it.favorites.enabled } + val includeFrequentlyUsed = dataStore.data.map { it.favorites.frequentlyUsed } + val frequentlyUsedRows = dataStore.data.map { it.favorites.frequentlyUsedRows } + + combine( + listOf( + favoritesEnabled, + columns, + excludeCalendar, + includeFrequentlyUsed, + frequentlyUsedRows + ) + ) { it }.transformLatest { + + val favoritesEnabled = it[0] as Boolean + val columns = it[1] as Int + val excludeCalendar = it[2] as Boolean + val includeFrequentlyUsed = it[3] as Boolean + val frequentlyUsedRows = it[4] as Int + + if (!favoritesEnabled) { + return@transformLatest + } + + val pinned = favoritesRepository.getFavorites( + excludeTypes = if (excludeCalendar) listOf("calendar") else null, + manuallySorted = true, + automaticallySorted = true, + limit = 10 * columns, + ) + if (includeFrequentlyUsed) { + emitAll(pinned.flatMapLatest { pinned -> + favoritesRepository.getFavorites( + excludeTypes = if (excludeCalendar) listOf("calendar") else null, + frequentlyUsed = true, + limit = frequentlyUsedRows * columns - pinned.size % columns, + ).map { + pinned + it + } + }) + } else { + emitAll(pinned) + } + } + } else { + emptyFlow>() + } + }.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1) + + + fun selectTag(tag: String?) { + selectedTag.value = tag + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt index b55acb91..c1dd4f68 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt @@ -30,6 +30,7 @@ import de.mm20.launcher2.ui.launcher.modals.EditFavoritesSheet import de.mm20.launcher2.ui.launcher.search.calculator.CalculatorItem import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem import de.mm20.launcher2.ui.launcher.search.common.list.ListItem +import de.mm20.launcher2.ui.launcher.search.favorites.SearchFavoritesVM import de.mm20.launcher2.ui.launcher.search.hidden.HiddenResults import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterItem import de.mm20.launcher2.ui.launcher.search.website.WebsiteItem @@ -51,12 +52,14 @@ fun SearchColumn( val viewModel: SearchVM = viewModel() + val favoritesVM: SearchFavoritesVM = viewModel() + val favorites by remember { favoritesVM.favorites }.collectAsState(emptyList()) + val showLabels by viewModel.showLabels.observeAsState(true) var showWorkProfileApps by remember { mutableStateOf(false) } val hideFavs by viewModel.hideFavorites.observeAsState(true) - val favorites by viewModel.favorites.observeAsState(emptyList()) val apps by viewModel.appResults.observeAsState(emptyList()) val workApps by viewModel.workAppResults.observeAsState(emptyList()) val appShortcuts by viewModel.appShortcutResults.observeAsState(emptyList()) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt index ab371984..20e1dff6 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt @@ -18,6 +18,7 @@ import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.search.WebsearchRepository import de.mm20.launcher2.search.data.* +import de.mm20.launcher2.ui.utils.withCustomLabels import de.mm20.launcher2.unitconverter.UnitConverterRepository import de.mm20.launcher2.websites.WebsiteRepository import de.mm20.launcher2.widgets.WidgetRepository @@ -30,7 +31,6 @@ import org.koin.core.component.inject class SearchVM : ViewModel(), KoinComponent { private val favoritesRepository: FavoritesRepository by inject() - private val widgetRepository: WidgetRepository by inject() private val permissionsManager: PermissionsManager by inject() private val customAttributesRepository: CustomAttributesRepository by inject() private val dataStore: LauncherDataStore by inject() @@ -52,8 +52,6 @@ class SearchVM : ViewModel(), KoinComponent { val showLabels = dataStore.data.map { it.grid.showLabels }.asLiveData() - val favorites = MutableLiveData>(emptyList()) - val appResults = MutableLiveData>(emptyList()) val workAppResults = MutableLiveData>(emptyList()) val appShortcutResults = MutableLiveData>(emptyList()) @@ -76,27 +74,6 @@ class SearchVM : ViewModel(), KoinComponent { init { search("") - viewModelScope.launch { - dataStore.data.map { it.favorites.enabled }.collectLatest { enabled -> - if (!enabled) { - favorites.value = emptyList() - return@collectLatest - } - widgetRepository.isCalendarWidgetEnabled().collectLatest { excludeCalendar -> - dataStore.data.map { it.grid.columnCount }.collectLatest { columns -> - favoritesRepository - .getFavorites( - columns = columns, - excludeCalendarEvents = excludeCalendar - ) - .withCustomLabels() - .collectLatest { - favorites.value = it - } - } - } - } - } } var searchJob: Job? = null @@ -131,7 +108,7 @@ class SearchVM : ViewModel(), KoinComponent { appRepository .search(query) .withCustomAttributeResults(customAttrResults) - .withCustomLabels() + .withCustomLabels(customAttributesRepository) .sorted() .collectWithHiddenItems(hiddenItemKeys) { results, hidden -> val (work, personal) = results.partition { it is LauncherApp && !it.isMainProfile } @@ -146,7 +123,7 @@ class SearchVM : ViewModel(), KoinComponent { contactRepository .search(query) .withCustomAttributeResults(customAttrResults) - .withCustomLabels() + .withCustomLabels(customAttributesRepository) .sorted() .collectWithHiddenItems(hiddenItemKeys) { results, hidden -> contactResults.postValue(results) @@ -159,7 +136,7 @@ class SearchVM : ViewModel(), KoinComponent { calendarRepository .search(query) .withCustomAttributeResults(customAttrResults) - .withCustomLabels() + .withCustomLabels(customAttributesRepository) .sorted() .collectWithHiddenItems(hiddenItemKeys) { results, hidden -> calendarResults.postValue(results) @@ -192,7 +169,7 @@ class SearchVM : ViewModel(), KoinComponent { fileRepository .search(query) .withCustomAttributeResults(customAttrResults) - .withCustomLabels() + .withCustomLabels(customAttributesRepository) .sorted() .collectWithHiddenItems(hiddenItemKeys) { results, hidden -> fileResults.postValue(results) @@ -210,7 +187,7 @@ class SearchVM : ViewModel(), KoinComponent { appShortcutRepository .search(query) .withCustomAttributeResults(customAttrResults) - .withCustomLabels() + .withCustomLabels(customAttributesRepository) .sorted() .collectWithHiddenItems(hiddenItemKeys) { results, hidden -> appShortcutResults.postValue(results) @@ -306,21 +283,6 @@ class SearchVM : ViewModel(), KoinComponent { } } - /** - * Inject custom labels and sort by the actual label - */ - private fun Flow>.withCustomLabels(): Flow> = channelFlow { - this@withCustomLabels.collectLatest { items -> - val labelsFlow = customAttributesRepository.getCustomLabels(items) - labelsFlow.collectLatest { labels -> - for (item in items) { - val customLabel = labels.find { it.key == item.key } - item.labelOverride = customLabel?.label - } - send(items) - } - } - } private inline fun Flow>.withCustomAttributeResults( customAttributeResults: Flow> diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/favorites/FavoritesResults.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/favorites/FavoritesResults.kt deleted file mode 100644 index ab09257d..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/favorites/FavoritesResults.kt +++ /dev/null @@ -1,33 +0,0 @@ -package de.mm20.launcher2.ui.launcher.search.favorites - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import de.mm20.launcher2.ui.component.LauncherCard -import de.mm20.launcher2.ui.launcher.search.SearchVM -import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid - -@Composable -fun FavoritesResults( - reverse: Boolean = false, -) { - val viewModel: SearchVM = viewModel() - val favorites by viewModel.favorites.observeAsState(emptyList()) - - val hide by viewModel.hideFavorites.observeAsState(true) - - AnimatedVisibility(!hide && favorites.isNotEmpty()) { - LauncherCard( - modifier = Modifier - .padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp) - ) { - SearchResultGrid(items = favorites, reverse = reverse) - } - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/favorites/SearchFavoritesVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/favorites/SearchFavoritesVM.kt new file mode 100644 index 00000000..b49f2cd0 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/favorites/SearchFavoritesVM.kt @@ -0,0 +1,5 @@ +package de.mm20.launcher2.ui.launcher.search.favorites + +import de.mm20.launcher2.ui.common.FavoritesVM + +class SearchFavoritesVM: FavoritesVM() \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidget.kt index 2023f4ec..3d5dde67 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidget.kt @@ -1,15 +1,17 @@ package de.mm20.launcher2.ui.launcher.widgets.favorites 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.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid @Composable fun FavoritesWidget() { val viewModel: FavoritesWidgetVM = viewModel() - val favorites by viewModel.favorites.observeAsState(emptyList()) + val favorites by remember { viewModel.favorites }.collectAsState(emptyList()) SearchResultGrid(favorites) } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidgetVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidgetVM.kt index a7871d66..c8eb6b35 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidgetVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidgetVM.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.ui.common.FavoritesVM import de.mm20.launcher2.widgets.WidgetRepository import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map @@ -13,24 +14,4 @@ import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -class FavoritesWidgetVM: ViewModel(), KoinComponent { - private val favoritesRepository: FavoritesRepository by inject() - private val widgetRepository: WidgetRepository by inject() - private val dataStore: LauncherDataStore by inject() - val favorites = MutableLiveData>(emptyList()) - - init { - viewModelScope.launch { - widgetRepository.isCalendarWidgetEnabled().collectLatest { excludeCalendar -> - dataStore.data.map { it.grid.columnCount }.collectLatest { columns -> - favoritesRepository.getFavorites( - columns = columns, - excludeCalendarEvents = excludeCalendar - ).collectLatest { - favorites.value = it - } - } - } - } - } -} \ No newline at end of file +class FavoritesWidgetVM: FavoritesVM() \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/utils/CustomLabels.kt b/ui/src/main/java/de/mm20/launcher2/ui/utils/CustomLabels.kt new file mode 100644 index 00000000..8edbe2d2 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/utils/CustomLabels.kt @@ -0,0 +1,23 @@ +package de.mm20.launcher2.ui.utils + +import de.mm20.launcher2.customattrs.CustomAttributesRepository +import de.mm20.launcher2.customattrs.CustomLabel +import de.mm20.launcher2.search.data.Searchable +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.collectLatest + +fun Flow>.withCustomLabels( + customAttributesRepository: CustomAttributesRepository, +): Flow> = channelFlow { + this@withCustomLabels.collectLatest { items -> + val customLabels = customAttributesRepository.getCustomLabels(items) + customLabels.collectLatest { labels -> + for (item in items) { + val customLabel = labels.find { it.key == item.key } + item.labelOverride = customLabel?.label + } + send(items) + } + } +} \ No newline at end of file