Use frequently used items settings in favorites

This commit is contained in:
MM20 2022-09-20 22:57:56 +02:00
parent 93f1f41d65
commit c8bf1354b8
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
8 changed files with 123 additions and 100 deletions

View File

@ -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<String?>(null)
val favorites: Flow<List<Searchable>> = 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<List<Searchable>>()
}
}.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1)
fun selectTag(tag: String?) {
selectedTag.value = tag
}
}

View File

@ -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())

View File

@ -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<List<Searchable>>(emptyList())
val appResults = MutableLiveData<List<Application>>(emptyList())
val workAppResults = MutableLiveData<List<Application>>(emptyList())
val appShortcutResults = MutableLiveData<List<AppShortcut>>(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 <T : Searchable> Flow<List<T>>.withCustomLabels(): Flow<List<T>> = 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 <reified T : Searchable> Flow<List<T>>.withCustomAttributeResults(
customAttributeResults: Flow<List<Searchable>>

View File

@ -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)
}
}
}

View File

@ -0,0 +1,5 @@
package de.mm20.launcher2.ui.launcher.search.favorites
import de.mm20.launcher2.ui.common.FavoritesVM
class SearchFavoritesVM: FavoritesVM()

View File

@ -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)
}

View File

@ -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<List<Searchable>>(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
}
}
}
}
}
}
class FavoritesWidgetVM: FavoritesVM()

View File

@ -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 <T : Searchable> Flow<List<T>>.withCustomLabels(
customAttributesRepository: CustomAttributesRepository,
): Flow<List<T>> = 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)
}
}
}