Use frequently used items settings in favorites
This commit is contained in:
parent
93f1f41d65
commit
c8bf1354b8
80
ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt
Normal file
80
ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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.calculator.CalculatorItem
|
||||||
import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem
|
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.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.hidden.HiddenResults
|
||||||
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterItem
|
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterItem
|
||||||
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItem
|
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItem
|
||||||
@ -51,12 +52,14 @@ fun SearchColumn(
|
|||||||
|
|
||||||
val viewModel: SearchVM = viewModel()
|
val viewModel: SearchVM = viewModel()
|
||||||
|
|
||||||
|
val favoritesVM: SearchFavoritesVM = viewModel()
|
||||||
|
val favorites by remember { favoritesVM.favorites }.collectAsState(emptyList())
|
||||||
|
|
||||||
val showLabels by viewModel.showLabels.observeAsState(true)
|
val showLabels by viewModel.showLabels.observeAsState(true)
|
||||||
|
|
||||||
var showWorkProfileApps by remember { mutableStateOf(false) }
|
var showWorkProfileApps by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val hideFavs by viewModel.hideFavorites.observeAsState(true)
|
val hideFavs by viewModel.hideFavorites.observeAsState(true)
|
||||||
val favorites by viewModel.favorites.observeAsState(emptyList())
|
|
||||||
val apps by viewModel.appResults.observeAsState(emptyList())
|
val apps by viewModel.appResults.observeAsState(emptyList())
|
||||||
val workApps by viewModel.workAppResults.observeAsState(emptyList())
|
val workApps by viewModel.workAppResults.observeAsState(emptyList())
|
||||||
val appShortcuts by viewModel.appShortcutResults.observeAsState(emptyList())
|
val appShortcuts by viewModel.appShortcutResults.observeAsState(emptyList())
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import de.mm20.launcher2.permissions.PermissionsManager
|
|||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.search.WebsearchRepository
|
import de.mm20.launcher2.search.WebsearchRepository
|
||||||
import de.mm20.launcher2.search.data.*
|
import de.mm20.launcher2.search.data.*
|
||||||
|
import de.mm20.launcher2.ui.utils.withCustomLabels
|
||||||
import de.mm20.launcher2.unitconverter.UnitConverterRepository
|
import de.mm20.launcher2.unitconverter.UnitConverterRepository
|
||||||
import de.mm20.launcher2.websites.WebsiteRepository
|
import de.mm20.launcher2.websites.WebsiteRepository
|
||||||
import de.mm20.launcher2.widgets.WidgetRepository
|
import de.mm20.launcher2.widgets.WidgetRepository
|
||||||
@ -30,7 +31,6 @@ import org.koin.core.component.inject
|
|||||||
class SearchVM : ViewModel(), KoinComponent {
|
class SearchVM : ViewModel(), KoinComponent {
|
||||||
|
|
||||||
private val favoritesRepository: FavoritesRepository by inject()
|
private val favoritesRepository: FavoritesRepository by inject()
|
||||||
private val widgetRepository: WidgetRepository by inject()
|
|
||||||
private val permissionsManager: PermissionsManager by inject()
|
private val permissionsManager: PermissionsManager by inject()
|
||||||
private val customAttributesRepository: CustomAttributesRepository by inject()
|
private val customAttributesRepository: CustomAttributesRepository by inject()
|
||||||
private val dataStore: LauncherDataStore 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 showLabels = dataStore.data.map { it.grid.showLabels }.asLiveData()
|
||||||
|
|
||||||
val favorites = MutableLiveData<List<Searchable>>(emptyList())
|
|
||||||
|
|
||||||
val appResults = MutableLiveData<List<Application>>(emptyList())
|
val appResults = MutableLiveData<List<Application>>(emptyList())
|
||||||
val workAppResults = MutableLiveData<List<Application>>(emptyList())
|
val workAppResults = MutableLiveData<List<Application>>(emptyList())
|
||||||
val appShortcutResults = MutableLiveData<List<AppShortcut>>(emptyList())
|
val appShortcutResults = MutableLiveData<List<AppShortcut>>(emptyList())
|
||||||
@ -76,27 +74,6 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
search("")
|
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
|
var searchJob: Job? = null
|
||||||
@ -131,7 +108,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
appRepository
|
appRepository
|
||||||
.search(query)
|
.search(query)
|
||||||
.withCustomAttributeResults(customAttrResults)
|
.withCustomAttributeResults(customAttrResults)
|
||||||
.withCustomLabels()
|
.withCustomLabels(customAttributesRepository)
|
||||||
.sorted()
|
.sorted()
|
||||||
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
||||||
val (work, personal) = results.partition { it is LauncherApp && !it.isMainProfile }
|
val (work, personal) = results.partition { it is LauncherApp && !it.isMainProfile }
|
||||||
@ -146,7 +123,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
contactRepository
|
contactRepository
|
||||||
.search(query)
|
.search(query)
|
||||||
.withCustomAttributeResults(customAttrResults)
|
.withCustomAttributeResults(customAttrResults)
|
||||||
.withCustomLabels()
|
.withCustomLabels(customAttributesRepository)
|
||||||
.sorted()
|
.sorted()
|
||||||
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
||||||
contactResults.postValue(results)
|
contactResults.postValue(results)
|
||||||
@ -159,7 +136,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
calendarRepository
|
calendarRepository
|
||||||
.search(query)
|
.search(query)
|
||||||
.withCustomAttributeResults(customAttrResults)
|
.withCustomAttributeResults(customAttrResults)
|
||||||
.withCustomLabels()
|
.withCustomLabels(customAttributesRepository)
|
||||||
.sorted()
|
.sorted()
|
||||||
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
||||||
calendarResults.postValue(results)
|
calendarResults.postValue(results)
|
||||||
@ -192,7 +169,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
fileRepository
|
fileRepository
|
||||||
.search(query)
|
.search(query)
|
||||||
.withCustomAttributeResults(customAttrResults)
|
.withCustomAttributeResults(customAttrResults)
|
||||||
.withCustomLabels()
|
.withCustomLabels(customAttributesRepository)
|
||||||
.sorted()
|
.sorted()
|
||||||
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
||||||
fileResults.postValue(results)
|
fileResults.postValue(results)
|
||||||
@ -210,7 +187,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
appShortcutRepository
|
appShortcutRepository
|
||||||
.search(query)
|
.search(query)
|
||||||
.withCustomAttributeResults(customAttrResults)
|
.withCustomAttributeResults(customAttrResults)
|
||||||
.withCustomLabels()
|
.withCustomLabels(customAttributesRepository)
|
||||||
.sorted()
|
.sorted()
|
||||||
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
||||||
appShortcutResults.postValue(results)
|
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(
|
private inline fun <reified T : Searchable> Flow<List<T>>.withCustomAttributeResults(
|
||||||
customAttributeResults: Flow<List<Searchable>>
|
customAttributeResults: Flow<List<Searchable>>
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search.favorites
|
||||||
|
|
||||||
|
import de.mm20.launcher2.ui.common.FavoritesVM
|
||||||
|
|
||||||
|
class SearchFavoritesVM: FavoritesVM()
|
||||||
@ -1,15 +1,17 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.widgets.favorites
|
package de.mm20.launcher2.ui.launcher.widgets.favorites
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FavoritesWidget() {
|
fun FavoritesWidget() {
|
||||||
val viewModel: FavoritesWidgetVM = viewModel()
|
val viewModel: FavoritesWidgetVM = viewModel()
|
||||||
val favorites by viewModel.favorites.observeAsState(emptyList())
|
val favorites by remember { viewModel.favorites }.collectAsState(emptyList())
|
||||||
|
|
||||||
SearchResultGrid(favorites)
|
SearchResultGrid(favorites)
|
||||||
}
|
}
|
||||||
@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.search.data.Searchable
|
import de.mm20.launcher2.search.data.Searchable
|
||||||
|
import de.mm20.launcher2.ui.common.FavoritesVM
|
||||||
import de.mm20.launcher2.widgets.WidgetRepository
|
import de.mm20.launcher2.widgets.WidgetRepository
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@ -13,24 +14,4 @@ import kotlinx.coroutines.launch
|
|||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
class FavoritesWidgetVM: ViewModel(), KoinComponent {
|
class FavoritesWidgetVM: FavoritesVM()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
ui/src/main/java/de/mm20/launcher2/ui/utils/CustomLabels.kt
Normal file
23
ui/src/main/java/de/mm20/launcher2/ui/utils/CustomLabels.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user