Refactor favorites and searchable database
This commit is contained in:
parent
582c94b6af
commit
ed70097c38
@ -132,7 +132,7 @@ dependencies {
|
||||
implementation(project(":core:crashreporter"))
|
||||
implementation(project(":data:currencies"))
|
||||
implementation(project(":data:customattrs"))
|
||||
implementation(project(":data:favorites"))
|
||||
implementation(project(":data:searchable"))
|
||||
implementation(project(":data:files"))
|
||||
implementation(project(":libs:g-services"))
|
||||
implementation(project(":core:i18n"))
|
||||
@ -157,6 +157,7 @@ dependencies {
|
||||
implementation(project(":data:search-actions"))
|
||||
implementation(project(":services:global-actions"))
|
||||
implementation(project(":services:widgets"))
|
||||
implementation(project(":services:favorites"))
|
||||
|
||||
// Uncomment this if you want annoying notifications in your debug builds yelling at you how terrible your code is
|
||||
//debugImplementation(libs.leakcanary)
|
||||
|
||||
@ -13,7 +13,7 @@ import de.mm20.launcher2.calculator.calculatorModule
|
||||
import de.mm20.launcher2.calendar.calendarModule
|
||||
import de.mm20.launcher2.contacts.contactsModule
|
||||
import de.mm20.launcher2.data.customattrs.customAttrsModule
|
||||
import de.mm20.launcher2.favorites.favoritesModule
|
||||
import de.mm20.launcher2.searchable.searchableModule
|
||||
import de.mm20.launcher2.files.filesModule
|
||||
import de.mm20.launcher2.icons.iconsModule
|
||||
import de.mm20.launcher2.music.musicModule
|
||||
@ -29,6 +29,7 @@ import de.mm20.launcher2.notifications.notificationsModule
|
||||
import de.mm20.launcher2.permissions.permissionsModule
|
||||
import de.mm20.launcher2.preferences.preferencesModule
|
||||
import de.mm20.launcher2.searchactions.searchActionsModule
|
||||
import de.mm20.launcher2.services.favorites.favoritesModule
|
||||
import de.mm20.launcher2.services.tags.servicesTagsModule
|
||||
import de.mm20.launcher2.services.widgets.widgetsServiceModule
|
||||
import de.mm20.launcher2.weather.weatherModule
|
||||
@ -67,6 +68,7 @@ class LauncherApplication : Application(), CoroutineScope, ImageLoaderFactory {
|
||||
customAttrsModule,
|
||||
databaseModule,
|
||||
favoritesModule,
|
||||
searchableModule,
|
||||
filesModule,
|
||||
globalActionsModule,
|
||||
iconsModule,
|
||||
|
||||
@ -1,23 +1,21 @@
|
||||
package de.mm20.launcher2.activity
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.LauncherApps
|
||||
import android.os.Bundle
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.search.data.AppShortcut
|
||||
import de.mm20.launcher2.search.data.LauncherShortcut
|
||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AddItemActivity : Activity() {
|
||||
|
||||
val favoritesRepository: FavoritesRepository by inject()
|
||||
private val favoritesService: FavoritesService by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val shortcut = AppShortcut.fromPinRequestIntent(this, intent)
|
||||
if (shortcut != null) {
|
||||
favoritesRepository.pinItem(shortcut)
|
||||
favoritesService.pinItem(shortcut)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@ dependencies {
|
||||
implementation(project(":data:calculator"))
|
||||
implementation(project(":data:files"))
|
||||
implementation(project(":data:widgets"))
|
||||
implementation(project(":data:favorites"))
|
||||
implementation(project(":data:searchable"))
|
||||
implementation(project(":data:wikipedia"))
|
||||
implementation(project(":services:badges"))
|
||||
implementation(project(":core:crashreporter"))
|
||||
@ -151,4 +151,5 @@ dependencies {
|
||||
implementation(project(":data:search-actions"))
|
||||
implementation(project(":services:global-actions"))
|
||||
implementation(project(":services:widgets"))
|
||||
implementation(project(":services:favorites"))
|
||||
}
|
||||
@ -4,10 +4,11 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
||||
import de.mm20.launcher2.data.customattrs.utils.withCustomLabels
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.data.Tag
|
||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.WidgetRepository
|
||||
import kotlinx.coroutines.flow.*
|
||||
@ -16,7 +17,7 @@ import org.koin.core.component.inject
|
||||
|
||||
abstract class FavoritesVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private val favoritesService: FavoritesService by inject()
|
||||
internal val widgetRepository: WidgetRepository by inject()
|
||||
private val customAttributesRepository: CustomAttributesRepository by inject()
|
||||
internal val dataStore: LauncherDataStore by inject()
|
||||
@ -26,7 +27,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
||||
val showEditButton = dataStore.data.map { it.favorites.editButton }
|
||||
abstract val tagsExpanded: Flow<Boolean>
|
||||
|
||||
val pinnedTags = favoritesRepository.getFavorites(
|
||||
val pinnedTags = favoritesService.getFavorites(
|
||||
includeTypes = listOf("tag"),
|
||||
manuallySorted = true,
|
||||
automaticallySorted = true,
|
||||
@ -55,7 +56,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
||||
val includeFrequentlyUsed = it[2] as Boolean
|
||||
val frequentlyUsedRows = it[3] as Int
|
||||
|
||||
val pinned = favoritesRepository.getFavorites(
|
||||
val pinned = favoritesService.getFavorites(
|
||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
||||
manuallySorted = true,
|
||||
automaticallySorted = true,
|
||||
@ -63,7 +64,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
||||
)
|
||||
if (includeFrequentlyUsed) {
|
||||
emitAll(pinned.flatMapLatest { pinned ->
|
||||
favoritesRepository.getFavorites(
|
||||
favoritesService.getFavorites(
|
||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
||||
frequentlyUsed = true,
|
||||
limit = frequentlyUsedRows * columns - pinned.size % columns,
|
||||
|
||||
@ -2,7 +2,6 @@ package de.mm20.launcher2.ui.launcher
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@ -11,7 +10,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.globalactions.GlobalActionsService
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
@ -38,7 +37,7 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val globalActionsService: GlobalActionsService by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private val searchableRepository: SearchableRepository by inject()
|
||||
|
||||
private var isSystemInDarkMode = MutableStateFlow(false)
|
||||
|
||||
@ -137,7 +136,7 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
||||
swipeDownAppKey,
|
||||
longPressAppKey,
|
||||
doubleTapAppKey
|
||||
).let { favoritesRepository.getFromKeys(it) }
|
||||
).let { searchableRepository.getByKeys(it) }
|
||||
|
||||
GestureState(
|
||||
swipeLeftAction = swipeLeftAction,
|
||||
|
||||
@ -5,8 +5,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.favorites.SavedSearchableRankInfo
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
@ -24,6 +23,7 @@ import de.mm20.launcher2.search.data.UnitConverter
|
||||
import de.mm20.launcher2.search.data.Website
|
||||
import de.mm20.launcher2.search.data.Wikipedia
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@ -42,7 +42,8 @@ import org.koin.core.component.inject
|
||||
|
||||
class SearchVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private val favoritesService: FavoritesService by inject()
|
||||
private val searchableRepository: SearchableRepository by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
@ -71,8 +72,10 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
val favoritesEnabled = dataStore.data.map { it.favorites.enabled }
|
||||
val hideFavorites = mutableStateOf(false)
|
||||
|
||||
private val hiddenItemKeys = favoritesRepository
|
||||
.getHiddenItemKeys()
|
||||
private val hiddenItemKeys = searchableRepository
|
||||
.getKeys(
|
||||
hidden = true,
|
||||
)
|
||||
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
val bestMatch = mutableStateOf<Searchable?>(null)
|
||||
@ -85,7 +88,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
val bestMatch = bestMatch.value
|
||||
if (bestMatch is SavableSearchable) {
|
||||
bestMatch.launch(context, null)
|
||||
favoritesRepository.incrementLaunchCounter(bestMatch)
|
||||
favoritesService.reportLaunch(bestMatch)
|
||||
return
|
||||
} else if (bestMatch is SearchAction) {
|
||||
bestMatch.start(context)
|
||||
@ -144,11 +147,11 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
val keys = resultsList.mapNotNull { (it as? SavableSearchable)?.key }
|
||||
when (settings.resultOrdering.ordering) {
|
||||
|
||||
Ordering.LaunchCount -> favoritesRepository.sortByRelevance(
|
||||
Ordering.LaunchCount -> searchableRepository.sortByRelevance(
|
||||
keys
|
||||
).first()
|
||||
|
||||
Ordering.Weighted -> favoritesRepository.sortByWeight(
|
||||
Ordering.Weighted -> searchableRepository.sortByWeight(
|
||||
keys
|
||||
).first()
|
||||
|
||||
|
||||
@ -120,15 +120,15 @@ class AppItemVM(
|
||||
}
|
||||
|
||||
fun isShortcutPinned(shortcut: AppShortcut): Flow<Boolean> {
|
||||
return favoritesRepository.isPinned(shortcut)
|
||||
return searchableRepository.isPinned(shortcut)
|
||||
}
|
||||
|
||||
fun pinShortcut(shortcut: AppShortcut) {
|
||||
favoritesRepository.pinItem(shortcut)
|
||||
favoritesService.pinItem(shortcut)
|
||||
}
|
||||
|
||||
fun unpinShortcut(shortcut: AppShortcut) {
|
||||
favoritesRepository.unpinItem(shortcut)
|
||||
favoritesService.unpinItem(shortcut)
|
||||
}
|
||||
|
||||
fun launchShortcut(context: Context, shortcut: AppShortcut) {
|
||||
|
||||
@ -6,44 +6,42 @@ import androidx.compose.ui.geometry.Rect
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.data.AppShortcut
|
||||
import de.mm20.launcher2.search.data.LauncherApp
|
||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
abstract class SearchableItemVM(
|
||||
private val searchable: SavableSearchable
|
||||
) : KoinComponent {
|
||||
protected val favoritesRepository: FavoritesRepository by inject()
|
||||
protected val favoritesService: FavoritesService by inject()
|
||||
protected val searchableRepository: SearchableRepository by inject()
|
||||
protected val badgeRepository: BadgeRepository by inject()
|
||||
protected val iconRepository: IconRepository by inject()
|
||||
protected val customAttributesRepository: CustomAttributesRepository by inject()
|
||||
|
||||
val isPinned = favoritesRepository.isPinned(searchable)
|
||||
val isPinned = searchableRepository.isPinned(searchable)
|
||||
fun pin() {
|
||||
favoritesRepository.pinItem(searchable)
|
||||
favoritesService.pinItem(searchable)
|
||||
}
|
||||
|
||||
fun unpin() {
|
||||
favoritesRepository.unpinItem(searchable)
|
||||
favoritesService.unpinItem(searchable)
|
||||
}
|
||||
|
||||
val isHidden = favoritesRepository.isHidden(searchable)
|
||||
val isHidden = searchableRepository.isHidden(searchable)
|
||||
fun hide() {
|
||||
favoritesRepository.hideItem(searchable)
|
||||
searchableRepository.upsert(searchable, hidden = true, pinned = false)
|
||||
}
|
||||
|
||||
fun unhide() {
|
||||
favoritesRepository.unhideItem(searchable)
|
||||
searchableRepository.update(searchable, hidden = false)
|
||||
}
|
||||
|
||||
val badge = badgeRepository.getBadge(searchable)
|
||||
@ -71,10 +69,10 @@ abstract class SearchableItemVM(
|
||||
}
|
||||
val bundle = options.toBundle()
|
||||
if (searchable.launch(context, bundle)) {
|
||||
favoritesRepository.incrementLaunchCounter(searchable)
|
||||
favoritesService.reportLaunch(searchable)
|
||||
return true
|
||||
} else if (searchable is LauncherApp || searchable is AppShortcut) {
|
||||
favoritesRepository.remove(searchable)
|
||||
searchableRepository.delete(searchable)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.search.data.AppShortcut
|
||||
@ -37,6 +36,6 @@ class ShortcutItemVM(private val shortcut: AppShortcut) : SearchableItemVM(short
|
||||
fun deleteShortcut() {
|
||||
if (!canDelete) return
|
||||
if (shortcut is LauncherShortcut) shortcutRepository.removePinnedShortcut(shortcut)
|
||||
favoritesRepository.unpinItem(shortcut)
|
||||
searchableRepository.delete(shortcut)
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@ import de.mm20.launcher2.appshortcuts.AppShortcutRepository
|
||||
import de.mm20.launcher2.badges.Badge
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.ktx.normalize
|
||||
@ -24,6 +24,7 @@ import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.data.AppShortcut
|
||||
import de.mm20.launcher2.search.Searchable
|
||||
import de.mm20.launcher2.search.data.Tag
|
||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
@ -34,7 +35,7 @@ import org.koin.core.component.inject
|
||||
|
||||
class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val repository: FavoritesRepository by inject()
|
||||
private val favoritesService: FavoritesService by inject()
|
||||
private val shortcutRepository: AppShortcutRepository by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
@ -58,19 +59,19 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
||||
suspend fun reload(showLoadingIndicator: Boolean = true) {
|
||||
loading.value = showLoadingIndicator
|
||||
manuallySorted = mutableListOf()
|
||||
manuallySorted = repository.getFavorites(
|
||||
manuallySorted = favoritesService.getFavorites(
|
||||
manuallySorted = true,
|
||||
excludeTypes = listOf("tag"),
|
||||
).first().toMutableList()
|
||||
automaticallySorted = repository.getFavorites(
|
||||
automaticallySorted = favoritesService.getFavorites(
|
||||
automaticallySorted = true,
|
||||
excludeTypes = listOf("tag"),
|
||||
).first().toMutableList()
|
||||
frequentlyUsed = repository.getFavorites(
|
||||
frequentlyUsed = favoritesService.getFavorites(
|
||||
frequentlyUsed = true,
|
||||
excludeTypes = listOf("tag"),
|
||||
).first().toMutableList()
|
||||
val pinnedTags = repository.getFavorites(
|
||||
val pinnedTags = favoritesService.getFavorites(
|
||||
includeTypes = listOf("tag"),
|
||||
manuallySorted = true,
|
||||
automaticallySorted = true,
|
||||
@ -169,7 +170,7 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
|
||||
private fun save() {
|
||||
repository.updateFavorites(
|
||||
favoritesService.updateFavorites(
|
||||
manuallySorted = buildList {
|
||||
pinnedTags.value?.let { addAll(it) }
|
||||
addAll(manuallySorted)
|
||||
@ -236,7 +237,7 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
||||
val item =
|
||||
gridItems.find { it is FavoritesSheetGridItem.Favorite && it.item.key == key } as FavoritesSheetGridItem.Favorite?
|
||||
if (item != null) {
|
||||
repository.removeFromFavorites(item.item)
|
||||
favoritesService.reset(item.item)
|
||||
automaticallySorted.removeAll { it.key == item.item.key }
|
||||
|| manuallySorted.removeAll { it.key == item.item.key }
|
||||
|| frequentlyUsed.removeAll { it.key == item.item.key }
|
||||
@ -310,9 +311,9 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
||||
|| frequentlyUsed.removeAll { it.key == item.item.key }
|
||||
buildItemList()
|
||||
customAttributesRepository.addTag(item.item, tag)
|
||||
repository.unpinItem(item.item)
|
||||
favoritesService.unpinItem(item.item)
|
||||
viewModelScope.launch {
|
||||
frequentlyUsed = repository.getFavorites(
|
||||
frequentlyUsed = favoritesService.getFavorites(
|
||||
frequentlyUsed = true,
|
||||
excludeTypes = listOf("tag"),
|
||||
).first().toMutableList()
|
||||
|
||||
@ -10,12 +10,13 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.calendar.CalendarRepository
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -31,11 +32,12 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val calendarRepository: CalendarRepository by inject()
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private val favoritesService: FavoritesService by inject()
|
||||
private val searchableRepository: SearchableRepository by inject()
|
||||
|
||||
val calendarEvents = MutableLiveData<List<CalendarEvent>>(emptyList())
|
||||
val pinnedCalendarEvents =
|
||||
favoritesRepository.getFavorites(
|
||||
favoritesService.getFavorites(
|
||||
includeTypes = listOf(CalendarEvent.Domain),
|
||||
automaticallySorted = true,
|
||||
manuallySorted = true,
|
||||
@ -158,7 +160,10 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
excludeAllDayEvents = settings.hideAlldayEvents,
|
||||
excludeCalendars = settings.excludeCalendarsList
|
||||
).collectLatest { events ->
|
||||
favoritesRepository.getHiddenCalendarEventKeys().collectLatest { hidden ->
|
||||
searchableRepository.getKeys(
|
||||
includeTypes = listOf(CalendarEvent.Domain),
|
||||
hidden = true,
|
||||
).collectLatest { hidden ->
|
||||
upcomingEvents = events.filter { !hidden.contains(it.key) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,16 +5,16 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout
|
||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.WidgetRepository
|
||||
@ -26,7 +26,7 @@ import org.koin.core.component.inject
|
||||
|
||||
class FavoritesPartProvider : PartProvider, KoinComponent {
|
||||
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private val favoritesService: FavoritesService by inject()
|
||||
private val widgetRepository: WidgetRepository by inject()
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
@ -47,7 +47,7 @@ class FavoritesPartProvider : PartProvider, KoinComponent {
|
||||
)
|
||||
|
||||
val favorites by remember(columns, excludeCalendar, layout) {
|
||||
favoritesRepository.getFavorites(
|
||||
favoritesService.getFavorites(
|
||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
||||
manuallySorted = true,
|
||||
automaticallySorted = true,
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
package de.mm20.launcher2.ui.settings.debug
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class DebugSettingsScreenVM: ViewModel(), KoinComponent {
|
||||
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private val searchableRepository: SearchableRepository by inject()
|
||||
private val customAttributesRepository: CustomAttributesRepository by inject()
|
||||
suspend fun cleanUpDatabase(): Int {
|
||||
var removed = favoritesRepository.cleanupDatabase()
|
||||
var removed = searchableRepository.cleanupDatabase()
|
||||
removed += customAttributesRepository.cleanupDatabase()
|
||||
return removed
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
@ -14,10 +14,8 @@ import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -26,7 +24,7 @@ import org.koin.core.component.inject
|
||||
class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private val searchableRepository: SearchableRepository by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
|
||||
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Accessibility).asLiveData()
|
||||
@ -86,13 +84,13 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
val swipeLeftApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeLeftApp }
|
||||
.map {
|
||||
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
|
||||
if (it.isEmpty()) null else searchableRepository.getByKeys(listOf(it)).firstOrNull()
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||
|
||||
fun setSwipeLeftApp(searchable: SavableSearchable?) {
|
||||
viewModelScope.launch {
|
||||
searchable?.let { favoritesRepository.save(it) }
|
||||
searchable?.let { searchableRepository.insert(it) }
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setGestures(it.gestures.toBuilder()
|
||||
@ -106,13 +104,13 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
val swipeRightApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeRightApp }
|
||||
.map {
|
||||
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
|
||||
if (it.isEmpty()) null else searchableRepository.getByKeys(listOf(it)).firstOrNull()
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||
|
||||
fun setSwipeRightApp(searchable: SavableSearchable?) {
|
||||
viewModelScope.launch {
|
||||
searchable?.let { favoritesRepository.save(it) }
|
||||
searchable?.let { searchableRepository.insert(it) }
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setGestures(it.gestures.toBuilder()
|
||||
@ -126,13 +124,13 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
val swipeDownApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeDownApp }
|
||||
.map {
|
||||
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
|
||||
if (it.isEmpty()) null else searchableRepository.getByKeys(listOf(it)).firstOrNull()
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||
|
||||
fun setSwipeDownApp(searchable: SavableSearchable?) {
|
||||
viewModelScope.launch {
|
||||
searchable?.let { favoritesRepository.save(it) }
|
||||
searchable?.let { searchableRepository.insert(it) }
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setGestures(it.gestures.toBuilder()
|
||||
@ -146,13 +144,13 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
val longPressApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.longPressApp }
|
||||
.map {
|
||||
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
|
||||
if (it.isEmpty()) null else searchableRepository.getByKeys(listOf(it)).firstOrNull()
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||
|
||||
fun setLongPressApp(searchable: SavableSearchable?) {
|
||||
viewModelScope.launch {
|
||||
searchable?.let { favoritesRepository.save(it) }
|
||||
searchable?.let { searchableRepository.insert(it) }
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setGestures(it.gestures.toBuilder()
|
||||
@ -166,13 +164,13 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
val doubleTapApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.doubleTapApp }
|
||||
.map {
|
||||
if (it.isEmpty()) null else favoritesRepository.getFromKeys(listOf(it)).firstOrNull()
|
||||
if (it.isEmpty()) null else searchableRepository.getByKeys(listOf(it)).firstOrNull()
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||
|
||||
fun setDoubleTapApp(searchable: SavableSearchable?) {
|
||||
viewModelScope.launch {
|
||||
searchable?.let { favoritesRepository.save(it) }
|
||||
searchable?.let { searchableRepository.insert(it) }
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setGestures(it.gestures.toBuilder()
|
||||
|
||||
@ -10,7 +10,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.liveData
|
||||
import de.mm20.launcher2.applications.AppRepository
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
@ -26,26 +26,26 @@ import org.koin.core.component.inject
|
||||
|
||||
class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val appRepository: AppRepository by inject()
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private val searchableRepository: SearchableRepository by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
|
||||
val allApps = appRepository.getAllInstalledApps().map {
|
||||
withContext(Dispatchers.Default) { it.sorted() }
|
||||
}.asLiveData()
|
||||
val hiddenItems: LiveData<List<SavableSearchable>> = liveData {
|
||||
val hidden = favoritesRepository.getHiddenItems().first().filter { it !is LauncherApp }.sorted()
|
||||
val hidden = searchableRepository.get(hidden = true).first().filter { it !is LauncherApp }.sorted()
|
||||
emit(hidden)
|
||||
}
|
||||
|
||||
fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
||||
return favoritesRepository.isHidden(searchable)
|
||||
return searchableRepository.isHidden(searchable)
|
||||
}
|
||||
|
||||
fun setHidden(searchable: SavableSearchable, hidden: Boolean) {
|
||||
if(hidden) {
|
||||
favoritesRepository.hideItem(searchable)
|
||||
searchableRepository.upsert(searchable, hidden = true, pinned = false)
|
||||
} else {
|
||||
favoritesRepository.unhideItem(searchable)
|
||||
searchableRepository.update(searchable, hidden = false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,496 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 24,
|
||||
"identityHash": "fd75b8c610f9b92eeb42a3ba1474914d",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "forecasts",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `temperature` REAL NOT NULL, `minTemp` REAL NOT NULL, `maxTemp` REAL NOT NULL, `pressure` REAL NOT NULL, `humidity` REAL NOT NULL, `icon` INTEGER NOT NULL, `condition` TEXT NOT NULL, `clouds` INTEGER NOT NULL, `windSpeed` REAL NOT NULL, `windDirection` REAL NOT NULL, `rain` REAL NOT NULL, `snow` REAL NOT NULL, `night` INTEGER NOT NULL, `location` TEXT NOT NULL, `provider` TEXT NOT NULL, `providerUrl` TEXT NOT NULL, `rainProbability` INTEGER NOT NULL, `snowProbability` INTEGER NOT NULL, `updateTime` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "temperature",
|
||||
"columnName": "temperature",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minTemp",
|
||||
"columnName": "minTemp",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "maxTemp",
|
||||
"columnName": "maxTemp",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pressure",
|
||||
"columnName": "pressure",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "humidity",
|
||||
"columnName": "humidity",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "condition",
|
||||
"columnName": "condition",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "clouds",
|
||||
"columnName": "clouds",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "windSpeed",
|
||||
"columnName": "windSpeed",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "windDirection",
|
||||
"columnName": "windDirection",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "precipitation",
|
||||
"columnName": "rain",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "snow",
|
||||
"columnName": "snow",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "night",
|
||||
"columnName": "night",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "location",
|
||||
"columnName": "location",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "provider",
|
||||
"columnName": "provider",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "providerUrl",
|
||||
"columnName": "providerUrl",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "precipProbability",
|
||||
"columnName": "rainProbability",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "snowProbability",
|
||||
"columnName": "snowProbability",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updateTime",
|
||||
"columnName": "updateTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "Searchable",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `type` TEXT NOT NULL, `searchable` TEXT NOT NULL, `launchCount` INTEGER NOT NULL DEFAULT 0, `pinPosition` INTEGER NOT NULL DEFAULT 0, `hidden` INTEGER NOT NULL DEFAULT 0, `weight` REAL NOT NULL DEFAULT 0.0, PRIMARY KEY(`key`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "key",
|
||||
"columnName": "key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "serializedSearchable",
|
||||
"columnName": "searchable",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "launchCount",
|
||||
"columnName": "launchCount",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pinPosition",
|
||||
"columnName": "pinPosition",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hidden",
|
||||
"columnName": "hidden",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "weight",
|
||||
"columnName": "weight",
|
||||
"affinity": "REAL",
|
||||
"notNull": true,
|
||||
"defaultValue": "0.0"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"key"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "Currency",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`symbol` TEXT NOT NULL, `value` REAL NOT NULL, `lastUpdate` INTEGER NOT NULL, PRIMARY KEY(`symbol`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "symbol",
|
||||
"columnName": "symbol",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUpdate",
|
||||
"columnName": "lastUpdate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"symbol"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "Icons",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `packageName` TEXT, `activityName` TEXT, `drawable` TEXT, `extras` TEXT, `iconPack` TEXT NOT NULL, `name` TEXT, `themed` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "activityName",
|
||||
"columnName": "activityName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "drawable",
|
||||
"columnName": "drawable",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "extras",
|
||||
"columnName": "extras",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconPack",
|
||||
"columnName": "iconPack",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "themed",
|
||||
"columnName": "themed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "IconPack",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `packageName` TEXT NOT NULL, `version` TEXT NOT NULL, `scale` REAL NOT NULL, `themed` INTEGER NOT NULL, PRIMARY KEY(`packageName`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "version",
|
||||
"columnName": "version",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "scale",
|
||||
"columnName": "scale",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "themed",
|
||||
"columnName": "themed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "Widget",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `config` TEXT, `position` INTEGER NOT NULL, `id` BLOB NOT NULL, `parentId` BLOB, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "config",
|
||||
"columnName": "config",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "BLOB",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentId",
|
||||
"columnName": "parentId",
|
||||
"affinity": "BLOB",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "CustomAttributes",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `type` TEXT NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "key",
|
||||
"columnName": "key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "SearchAction",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`position` INTEGER NOT NULL, `type` TEXT NOT NULL, `data` TEXT, `label` TEXT, `icon` INTEGER, `color` INTEGER, `customIcon` TEXT, `options` TEXT, PRIMARY KEY(`position`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "label",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "color",
|
||||
"columnName": "color",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "customIcon",
|
||||
"columnName": "customIcon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "options",
|
||||
"columnName": "options",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"position"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fd75b8c610f9b92eeb42a3ba1474914d')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,7 @@ import de.mm20.launcher2.database.migrations.Migration_19_20
|
||||
import de.mm20.launcher2.database.migrations.Migration_20_21
|
||||
import de.mm20.launcher2.database.migrations.Migration_21_22
|
||||
import de.mm20.launcher2.database.migrations.Migration_22_23
|
||||
import de.mm20.launcher2.database.migrations.Migration_23_24
|
||||
import de.mm20.launcher2.database.migrations.Migration_6_7
|
||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
||||
import de.mm20.launcher2.database.migrations.Migration_8_9
|
||||
@ -39,14 +40,15 @@ import java.util.UUID
|
||||
WidgetEntity::class,
|
||||
CustomAttributeEntity::class,
|
||||
SearchActionEntity::class
|
||||
], version = 23, exportSchema = true
|
||||
], version = 24, exportSchema = true
|
||||
)
|
||||
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
|
||||
@TypeConverters(ComponentNameConverter::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun weatherDao(): WeatherDao
|
||||
abstract fun searchDao(): SearchDao
|
||||
abstract fun iconDao(): IconDao
|
||||
|
||||
abstract fun searchableDao(): SearchableDao
|
||||
abstract fun widgetDao(): WidgetDao
|
||||
abstract fun currencyDao(): CurrencyDao
|
||||
abstract fun backupDao(): BackupRestoreDao
|
||||
@ -114,6 +116,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration_20_21(),
|
||||
Migration_21_22(),
|
||||
Migration_22_23(),
|
||||
Migration_23_24(),
|
||||
).build()
|
||||
if (_instance == null) _instance = instance
|
||||
return instance
|
||||
|
||||
@ -1,164 +0,0 @@
|
||||
package de.mm20.launcher2.database
|
||||
|
||||
import androidx.room.*
|
||||
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface SearchDao {
|
||||
|
||||
@Insert
|
||||
fun insertAll(items: List<SavedSearchableEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insertAllSkipExisting(items: List<SavedSearchableEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insertSkipExisting(items: SavedSearchableEntity)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAllReplaceExisting(items: List<SavedSearchableEntity>)
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM Searchable " +
|
||||
"WHERE ((:manuallySorted AND pinned > 1) OR " +
|
||||
"(:automaticallySorted AND pinned = 1) OR" +
|
||||
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
||||
") AND hidden = 0 ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getFavorites(
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
limit: Int,
|
||||
): Flow<List<SavedSearchableEntity>>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM Searchable " +
|
||||
"WHERE `type` IN (:includeTypes) AND (" +
|
||||
"(:manuallySorted AND pinned > 1) OR " +
|
||||
"(:automaticallySorted AND pinned = 1) OR" +
|
||||
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
||||
") AND hidden = 0 ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getFavoritesWithTypes(
|
||||
includeTypes: List<String>,
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
limit: Int,
|
||||
): Flow<List<SavedSearchableEntity>>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM Searchable " +
|
||||
"WHERE `type` NOT IN (:excludeTypes) AND (" +
|
||||
"(:manuallySorted AND pinned > 1) OR " +
|
||||
"(:automaticallySorted AND pinned = 1) OR" +
|
||||
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
|
||||
") AND hidden = 0 ORDER BY pinned DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getFavoritesWithoutTypes(
|
||||
excludeTypes: List<String>,
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
limit: Int,
|
||||
): Flow<List<SavedSearchableEntity>>
|
||||
|
||||
@Query("SELECT `key` FROM Searchable WHERE hidden = 1 AND type = 'calendar'")
|
||||
fun getHiddenCalendarEventKeys(): Flow<List<String>>
|
||||
|
||||
@Query("DELETE FROM Searchable WHERE `key` IN (:keys)")
|
||||
fun deleteAll(keys: List<String>)
|
||||
|
||||
@Query("UPDATE Searchable SET pinned = 1, hidden = 0 WHERE `key` = :key")
|
||||
fun pinExistingItem(key: String)
|
||||
|
||||
@Transaction
|
||||
fun pinToFavorites(item: SavedSearchableEntity) {
|
||||
pinExistingItem(item.key)
|
||||
insertSkipExisting(item)
|
||||
}
|
||||
|
||||
@Query("UPDATE Searchable SET pinned = 0 WHERE `key` = :key")
|
||||
fun unpinFavorite(key: String)
|
||||
|
||||
@Query("DELETE FROM Searchable WHERE `key` = :key")
|
||||
suspend fun deleteByKey(key: String)
|
||||
|
||||
@Query("UPDATE Searchable SET pinned = 0 WHERE `key` = :key")
|
||||
fun unpinApp(key: String)
|
||||
|
||||
|
||||
@Query("SELECT pinned FROM Searchable WHERE `key` = :key UNION SELECT 0 as pinned ORDER BY pinned DESC LIMIT 1")
|
||||
fun isPinned(key: String): Flow<Boolean>
|
||||
|
||||
|
||||
@Query("UPDATE Searchable SET hidden = 1, pinned = 0 WHERE `key` = :key")
|
||||
fun hideExistingItem(key: String)
|
||||
|
||||
@Transaction
|
||||
fun hideItem(item: SavedSearchableEntity) {
|
||||
hideExistingItem(item.key)
|
||||
insertSkipExisting(item)
|
||||
}
|
||||
|
||||
@Query("UPDATE Searchable SET hidden = 0 WHERE `key` = :key")
|
||||
fun unhideItem(key: String)
|
||||
|
||||
@Query("SELECT hidden FROM Searchable WHERE `key` = :key UNION SELECT 0 as hidden ORDER BY hidden DESC LIMIT 1")
|
||||
fun isHidden(key: String): Flow<Boolean>
|
||||
|
||||
@Query("SELECT `key` FROM SEARCHABLE WHERE hidden = 1")
|
||||
fun getHiddenItemKeys(): Flow<List<String>>
|
||||
|
||||
@Query("SELECT * FROM SEARCHABLE WHERE hidden = 1")
|
||||
fun getHiddenItems(): Flow<List<SavedSearchableEntity>>
|
||||
|
||||
@Query("UPDATE Searchable SET launchCount = launchCount + 1 WHERE `key` = :key")
|
||||
fun incrementExistingLaunchCount(key: String)
|
||||
|
||||
@Transaction
|
||||
fun incrementLaunchCount(item: SavedSearchableEntity, alpha: Double) {
|
||||
incrementExistingLaunchCount(item.key)
|
||||
increaseWeightWhere(item.key, alpha)
|
||||
reduceWeightExcept(item.key, alpha)
|
||||
insertSkipExisting(item)
|
||||
}
|
||||
|
||||
@Query("SELECT * FROM Searchable WHERE `key` = :key")
|
||||
fun getFavorite(key: String): SavedSearchableEntity?
|
||||
|
||||
@Query("SELECT * FROM Searchable WHERE `key` IN (:keys)")
|
||||
suspend fun getFromKeys(keys: List<String>): List<SavedSearchableEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertReplaceExisting(toDatabaseEntity: SavedSearchableEntity)
|
||||
|
||||
@Transaction
|
||||
fun saveFavorites(favorites: List<SavedSearchableEntity>) {
|
||||
deleteAllFavorites()
|
||||
insertAll(favorites)
|
||||
}
|
||||
|
||||
@Query("DELETE FROM Searchable WHERE hidden = 0")
|
||||
fun deleteAllFavorites()
|
||||
|
||||
@Query("UPDATE Searchable SET `pinned` = 0")
|
||||
fun unpinAll()
|
||||
|
||||
@Query("UPDATE Searchable SET `pinned` = 0, `launchCount` = 0 WHERE `key` = :key")
|
||||
suspend fun resetPinStatusAndLaunchCounter(key: String)
|
||||
|
||||
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) AND launchCount > 0 ORDER BY launchCount DESC, pinned DESC")
|
||||
fun sortByRelevance(keys: List<String>): Flow<List<String>>
|
||||
|
||||
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) ORDER BY `weight` DESC, pinned DESC")
|
||||
fun sortByWeight(keys: List<String>): Flow<List<String>>
|
||||
|
||||
@Query("UPDATE Searchable SET `weight` = `weight` * (1.0 - :alpha) WHERE `key` != :key")
|
||||
fun reduceWeightExcept(key: String, alpha: Double)
|
||||
|
||||
@Query("UPDATE Searchable SET `weight` = `weight` + :alpha * (1.0 - `weight`) WHERE `key` == :key")
|
||||
fun increaseWeightWhere(key: String, alpha: Double)
|
||||
}
|
||||
@ -0,0 +1,178 @@
|
||||
package de.mm20.launcher2.database
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import androidx.room.Upsert
|
||||
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
||||
import de.mm20.launcher2.database.entities.SavedSearchableUpdatePinEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface SearchableDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insert(searchable: SavedSearchableEntity)
|
||||
|
||||
@Upsert(entity = SavedSearchableEntity::class)
|
||||
suspend fun upsert(searchable: SavedSearchableEntity)
|
||||
|
||||
@Upsert(entity = SavedSearchableEntity::class)
|
||||
suspend fun upsert(searchable: List<SavedSearchableUpdatePinEntity>)
|
||||
|
||||
@Update(entity = SavedSearchableEntity::class)
|
||||
suspend fun update(searchable: SavedSearchableUpdatePinEntity)
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM Searchable " +
|
||||
"WHERE (" +
|
||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||
"(:hidden AND hidden = 1)" +
|
||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun get(
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
hidden: Boolean = false,
|
||||
limit: Int,
|
||||
): Flow<List<SavedSearchableEntity>>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM Searchable " +
|
||||
"WHERE (`type` IN (:includeTypes)) AND " +
|
||||
"(" +
|
||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||
"(:hidden AND hidden = 1)" +
|
||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getIncludeTypes(
|
||||
includeTypes: List<String>?,
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
hidden: Boolean = false,
|
||||
limit: Int,
|
||||
): Flow<List<SavedSearchableEntity>>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM Searchable " +
|
||||
"WHERE (`type` NOT IN (:excludeTypes)) AND " +
|
||||
"(" +
|
||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||
"(:hidden AND hidden = 1)" +
|
||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getExcludeTypes(
|
||||
excludeTypes: List<String>?,
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
hidden: Boolean = false,
|
||||
limit: Int,
|
||||
): Flow<List<SavedSearchableEntity>>
|
||||
|
||||
@Query(
|
||||
"SELECT `key` FROM Searchable " +
|
||||
"WHERE (" +
|
||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||
"(:hidden AND hidden = 1)" +
|
||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getKeys(
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
hidden: Boolean = false,
|
||||
limit: Int,
|
||||
): Flow<List<String>>
|
||||
|
||||
@Query(
|
||||
"SELECT `key` FROM Searchable " +
|
||||
"WHERE (`type` IN (:includeTypes)) AND " +
|
||||
"(" +
|
||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||
"(:hidden AND hidden = 1)" +
|
||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getKeysIncludeTypes(
|
||||
includeTypes: List<String>?,
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
hidden: Boolean = false,
|
||||
limit: Int,
|
||||
): Flow<List<String>>
|
||||
|
||||
@Query(
|
||||
"SELECT `key` FROM Searchable " +
|
||||
"WHERE (`type` NOT IN (:excludeTypes)) AND " +
|
||||
"(" +
|
||||
"(:manuallySorted AND pinPosition > 1) OR " +
|
||||
"(:automaticallySorted AND pinPosition = 1) OR" +
|
||||
"(:frequentlyUsed AND pinPosition = 0 AND launchCount > 0) OR " +
|
||||
"(:hidden AND hidden = 1)" +
|
||||
") AND hidden = :hidden ORDER BY pinPosition DESC, weight DESC, launchCount DESC LIMIT :limit"
|
||||
)
|
||||
fun getKeysExcludeTypes(
|
||||
excludeTypes: List<String>?,
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
hidden: Boolean = false,
|
||||
limit: Int,
|
||||
): Flow<List<String>>
|
||||
|
||||
@Query("SELECT * FROM Searchable WHERE `key` IN (:keys)")
|
||||
suspend fun getByKeys(keys: List<String>): List<SavedSearchableEntity>
|
||||
|
||||
@Query("SELECT * FROM Searchable WHERE `key` = :key")
|
||||
fun getByKey(key: String): Flow<SavedSearchableEntity?>
|
||||
|
||||
@Transaction
|
||||
suspend fun touch(item: SavedSearchableEntity, alpha: Double) {
|
||||
incrementLaunchCount(item.key)
|
||||
increaseWeightWhere(item.key, alpha)
|
||||
reduceWeightExcept(item.key, alpha)
|
||||
insert(item)
|
||||
}
|
||||
|
||||
@Query("UPDATE Searchable SET launchCount = launchCount + 1 WHERE `key` = :key")
|
||||
fun incrementLaunchCount(key: String)
|
||||
|
||||
@Query("UPDATE Searchable SET `weight` = `weight` * (1.0 - :alpha) WHERE `key` != :key")
|
||||
fun reduceWeightExcept(key: String, alpha: Double)
|
||||
|
||||
@Query("UPDATE Searchable SET `weight` = `weight` + :alpha * (1.0 - `weight`) WHERE `key` == :key")
|
||||
fun increaseWeightWhere(key: String, alpha: Double)
|
||||
|
||||
@Query("DELETE FROM Searchable WHERE `key` = :key")
|
||||
suspend fun delete(key: String)
|
||||
|
||||
@Query("UPDATE Searchable SET `pinPosition` = 0")
|
||||
suspend fun unpinAll()
|
||||
|
||||
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) AND launchCount > 0 ORDER BY launchCount DESC, pinPosition DESC")
|
||||
fun sortByRelevance(keys: List<String>): Flow<List<String>>
|
||||
|
||||
@Query("SELECT `key` FROM Searchable WHERE `key` IN (:keys) ORDER BY `weight` DESC, pinPosition DESC")
|
||||
fun sortByWeight(keys: List<String>): Flow<List<String>>
|
||||
|
||||
@Query("SELECT hidden FROM Searchable WHERE `key` = :key UNION SELECT 0 as hidden ORDER BY hidden DESC LIMIT 1")
|
||||
fun isHidden(key: String): Flow<Boolean>
|
||||
|
||||
@Query("SELECT pinPosition FROM Searchable WHERE `key` = :key UNION SELECT 0 as pinPosition ORDER BY pinPosition DESC LIMIT 1")
|
||||
fun isPinned(key: String): Flow<Boolean>
|
||||
}
|
||||
@ -9,8 +9,15 @@ data class SavedSearchableEntity(
|
||||
@PrimaryKey val key: String,
|
||||
val type: String,
|
||||
@ColumnInfo(name = "searchable") val serializedSearchable: String,
|
||||
var launchCount: Int,
|
||||
@ColumnInfo(name = "pinned") var pinPosition: Int,
|
||||
var hidden: Boolean,
|
||||
@ColumnInfo(defaultValue = "0.0") var weight: Double
|
||||
@ColumnInfo(defaultValue = "0") val launchCount: Int,
|
||||
@ColumnInfo(defaultValue = "0") val pinPosition: Int,
|
||||
@ColumnInfo(defaultValue = "0") val hidden: Boolean,
|
||||
@ColumnInfo(defaultValue = "0.0") val weight: Double
|
||||
)
|
||||
|
||||
data class SavedSearchableUpdatePinEntity(
|
||||
val key: String,
|
||||
val type: String,
|
||||
@ColumnInfo(name = "searchable") val serializedSearchable: String,
|
||||
val pinPosition: Int? = null,
|
||||
)
|
||||
@ -0,0 +1,31 @@
|
||||
package de.mm20.launcher2.database.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration_23_24 : Migration(23, 24) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE Searchable RENAME TO Searchable_old")
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `Searchable` (
|
||||
`key` TEXT NOT NULL,
|
||||
`type` TEXT NOT NULL,
|
||||
`searchable` TEXT NOT NULL,
|
||||
`launchCount` INTEGER NOT NULL DEFAULT 0,
|
||||
`pinPosition` INTEGER NOT NULL DEFAULT 0,
|
||||
`hidden` INTEGER NOT NULL DEFAULT 0,
|
||||
`weight` DOUBLE NOT NULL DEFAULT 0.0,
|
||||
PRIMARY KEY(`key`)
|
||||
)
|
||||
"""
|
||||
)
|
||||
database.execSQL(
|
||||
"""
|
||||
INSERT INTO `Searchable` (`key`, `type`, `searchable`, `launchCount`, `pinPosition`, `hidden`, `weight`)
|
||||
SELECT `key`, `type`, `searchable`, `launchCount`, `pinned`, `hidden`, `weight` FROM `Searchable_old`
|
||||
"""
|
||||
)
|
||||
database.execSQL("DROP TABLE Searchable_old")
|
||||
}
|
||||
}
|
||||
@ -44,5 +44,5 @@ dependencies {
|
||||
implementation(project(":core:base"))
|
||||
implementation(project(":core:ktx"))
|
||||
implementation(project(":core:crashreporter"))
|
||||
implementation(project(":data:favorites"))
|
||||
implementation(project(":data:searchable"))
|
||||
}
|
||||
@ -3,7 +3,7 @@ package de.mm20.launcher2.data.customattrs
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.database.entities.CustomAttributeEntity
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@ -46,7 +46,7 @@ interface CustomAttributesRepository {
|
||||
|
||||
internal class CustomAttributesRepositoryImpl(
|
||||
private val appDatabase: AppDatabase,
|
||||
private val favoritesRepository: FavoritesRepository
|
||||
private val searchableRepository: SearchableRepository
|
||||
) : CustomAttributesRepository {
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
|
||||
@ -79,7 +79,7 @@ internal class CustomAttributesRepositoryImpl(
|
||||
override fun setCustomLabel(searchable: SavableSearchable, label: String) {
|
||||
val dao = appDatabase.customAttrsDao()
|
||||
scope.launch {
|
||||
favoritesRepository.save(searchable)
|
||||
searchableRepository.insert(searchable)
|
||||
appDatabase.runInTransaction {
|
||||
dao.clearCustomAttribute(searchable.key, CustomAttributeType.Label.value)
|
||||
dao.setCustomAttribute(
|
||||
@ -102,7 +102,7 @@ internal class CustomAttributesRepositoryImpl(
|
||||
override fun setTags(searchable: SavableSearchable, tags: List<String>) {
|
||||
val dao = appDatabase.customAttrsDao()
|
||||
scope.launch {
|
||||
favoritesRepository.save(searchable)
|
||||
searchableRepository.insert(searchable)
|
||||
dao.setTags(searchable.key, tags.map {
|
||||
CustomTag(it).toDatabaseEntity(searchable.key)
|
||||
})
|
||||
@ -128,7 +128,7 @@ internal class CustomAttributesRepositoryImpl(
|
||||
override fun getItemsForTag(tag: String): Flow<List<SavableSearchable>> {
|
||||
val dao = appDatabase.customAttrsDao()
|
||||
return dao.getItemsWithTag(tag).map {
|
||||
favoritesRepository.getFromKeys(it)
|
||||
searchableRepository.getByKeys(it)
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ internal class CustomAttributesRepositoryImpl(
|
||||
return scope.launch {
|
||||
dao.setItemsWithTag(tag, items.map { it.key })
|
||||
for (item in items) {
|
||||
favoritesRepository.save(item)
|
||||
searchableRepository.insert(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -172,7 +172,7 @@ internal class CustomAttributesRepositoryImpl(
|
||||
}
|
||||
val dao = appDatabase.customAttrsDao()
|
||||
return dao.search("%$query%").map {
|
||||
favoritesRepository.getFromKeys(it).toImmutableList()
|
||||
searchableRepository.getByKeys(it).toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
package de.mm20.launcher2.favorites
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val favoritesModule = module {
|
||||
single<FavoritesRepository> { FavoritesRepositoryImpl(androidContext(), get(), get()) }
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package de.mm20.launcher2.searchable
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val searchableModule = module {
|
||||
single<SearchableRepository> { SearchableRepositoryImpl(androidContext(), get(), get()) }
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package de.mm20.launcher2.favorites
|
||||
package de.mm20.launcher2.searchable
|
||||
|
||||
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
@ -1,4 +1,4 @@
|
||||
package de.mm20.launcher2.favorites
|
||||
package de.mm20.launcher2.searchable
|
||||
|
||||
data class SavedSearchableRankInfo(
|
||||
val key: String,
|
||||
@ -1,58 +1,87 @@
|
||||
package de.mm20.launcher2.favorites
|
||||
package de.mm20.launcher2.searchable
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.room.withTransaction
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
||||
import de.mm20.launcher2.database.entities.SavedSearchableUpdatePinEntity
|
||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings.SearchResultOrderingSettings.WeightFactor
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchableDeserializer
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.koin.core.component.KoinComponent
|
||||
import java.io.File
|
||||
|
||||
interface FavoritesRepository {
|
||||
interface SearchableRepository {
|
||||
|
||||
fun insert(
|
||||
searchable: SavableSearchable,
|
||||
)
|
||||
|
||||
fun upsert(
|
||||
searchable: SavableSearchable,
|
||||
hidden: Boolean? = null,
|
||||
pinned: Boolean? = null,
|
||||
launchCount: Int? = null,
|
||||
weight: Double? = null,
|
||||
)
|
||||
|
||||
fun update(
|
||||
searchable: SavableSearchable,
|
||||
hidden: Boolean? = null,
|
||||
pinned: Boolean? = null,
|
||||
launchCount: Int? = null,
|
||||
weight: Double? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Get favorites
|
||||
* @param includeTypes Include only items of these types. Cannot be used together with excludeTypes.
|
||||
* @param excludeTypes Exclude only items of these types. Cannot be used together with includeTypes.
|
||||
* @param manuallySorted Include items that have been sorted manually
|
||||
* @param automaticallySorted Include items that are pinned but not sorted
|
||||
* @param frequentlyUsed Include items that are not pinned but most frequently used
|
||||
* @param limit Maximum number of items returned.
|
||||
*/
|
||||
fun getFavorites(
|
||||
* Touch a searchable to update its weight and launch counter
|
||||
**/
|
||||
fun touch(
|
||||
searchable: SavableSearchable,
|
||||
)
|
||||
|
||||
fun get(
|
||||
includeTypes: List<String>? = null,
|
||||
excludeTypes: List<String>? = null,
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
limit: Int = 100
|
||||
hidden: Boolean = false,
|
||||
limit: Int = 100,
|
||||
): Flow<List<SavableSearchable>>
|
||||
|
||||
fun getKeys(
|
||||
includeTypes: List<String>? = null,
|
||||
excludeTypes: List<String>? = null,
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
hidden: Boolean = false,
|
||||
limit: Int = 100,
|
||||
): Flow<List<String>>
|
||||
|
||||
|
||||
fun getHiddenCalendarEventKeys(): Flow<List<String>>
|
||||
fun isPinned(searchable: SavableSearchable): Flow<Boolean>
|
||||
fun pinItem(searchable: SavableSearchable)
|
||||
fun unpinItem(searchable: SavableSearchable)
|
||||
fun isHidden(searchable: SavableSearchable): Flow<Boolean>
|
||||
fun hideItem(searchable: SavableSearchable)
|
||||
fun unhideItem(searchable: SavableSearchable)
|
||||
fun incrementLaunchCounter(searchable: SavableSearchable)
|
||||
fun updateFavorites(
|
||||
manuallySorted: List<SavableSearchable>,
|
||||
automaticallySorted: List<SavableSearchable>,
|
||||
)
|
||||
|
||||
fun getHiddenItems(): Flow<List<SavableSearchable>>
|
||||
fun getHiddenItemKeys(): Flow<List<String>>
|
||||
|
||||
/**
|
||||
* Returns the given keys sorted by relevance.
|
||||
* The first item in the list is the most relevant.
|
||||
@ -65,24 +94,13 @@ interface FavoritesRepository {
|
||||
/**
|
||||
* Remove this item from the Searchable database
|
||||
*/
|
||||
fun remove(searchable: SavableSearchable)
|
||||
|
||||
/**
|
||||
* Remove this item from favorites and reset launch counter
|
||||
*/
|
||||
fun removeFromFavorites(searchable: SavableSearchable)
|
||||
|
||||
/**
|
||||
* Ensure that this searchable exists in the Favorites table.
|
||||
* If it doesn't exist, insert it with 0 launch count, not pinned and not hidden
|
||||
*/
|
||||
fun save(searchable: SavableSearchable)
|
||||
fun delete(searchable: SavableSearchable)
|
||||
|
||||
/**
|
||||
* Get items with the given keys from the favorites database.
|
||||
* Items that don't exist in the database will not be returned.
|
||||
*/
|
||||
suspend fun getFromKeys(keys: List<String>): List<SavableSearchable>
|
||||
suspend fun getByKeys(keys: List<String>): List<SavableSearchable>
|
||||
|
||||
suspend fun export(toDir: File)
|
||||
suspend fun import(fromDir: File)
|
||||
@ -95,125 +113,82 @@ interface FavoritesRepository {
|
||||
suspend fun cleanupDatabase(): Int
|
||||
}
|
||||
|
||||
internal class FavoritesRepositoryImpl(
|
||||
internal class SearchableRepositoryImpl(
|
||||
private val context: Context,
|
||||
private val database: AppDatabase,
|
||||
private val dataStore: LauncherDataStore
|
||||
) : FavoritesRepository, KoinComponent {
|
||||
) : SearchableRepository, KoinComponent {
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
|
||||
override fun getFavorites(
|
||||
includeTypes: List<String>?,
|
||||
excludeTypes: List<String>?,
|
||||
manuallySorted: Boolean,
|
||||
automaticallySorted: Boolean,
|
||||
frequentlyUsed: Boolean,
|
||||
limit: Int
|
||||
): Flow<List<SavableSearchable>> {
|
||||
val dao = database.searchDao()
|
||||
val entities = when {
|
||||
includeTypes == null && excludeTypes == null -> dao.getFavorites(
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
includeTypes != null && excludeTypes == null -> {
|
||||
dao.getFavoritesWithTypes(
|
||||
includeTypes = includeTypes,
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
limit = limit
|
||||
)
|
||||
}
|
||||
|
||||
excludeTypes != null && includeTypes == null -> {
|
||||
dao.getFavoritesWithoutTypes(
|
||||
excludeTypes = excludeTypes,
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
limit = limit
|
||||
)
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("You can either use includeTypes or excludeTypes, not both")
|
||||
}
|
||||
return entities.map {
|
||||
it.mapNotNull { fromDatabaseEntity(it).searchable }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHiddenCalendarEventKeys(): Flow<List<String>> {
|
||||
return database.searchDao().getHiddenCalendarEventKeys()
|
||||
}
|
||||
|
||||
override fun isPinned(searchable: SavableSearchable): Flow<Boolean> {
|
||||
return database.searchDao().isPinned(searchable.key)
|
||||
}
|
||||
|
||||
override fun pinItem(searchable: SavableSearchable) {
|
||||
override fun insert(searchable: SavableSearchable) {
|
||||
val dao = database.searchableDao()
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val dao = database.searchDao()
|
||||
val databaseItem = dao.getFavorite(searchable.key)
|
||||
val savedSearchable = SavedSearchable(
|
||||
dao.insert(
|
||||
SavedSearchableEntity(
|
||||
key = searchable.key,
|
||||
searchable = searchable,
|
||||
launchCount = databaseItem?.launchCount ?: 0,
|
||||
pinPosition = 1,
|
||||
type = searchable.domain,
|
||||
serializedSearchable = searchable.serialize() ?: return@launch,
|
||||
hidden = false,
|
||||
weight = databaseItem?.weight ?: 0.0
|
||||
)
|
||||
savedSearchable.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun unpinItem(searchable: SavableSearchable) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
database.searchDao().unpinFavorite(searchable.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
||||
return database.searchDao().isHidden(searchable.key)
|
||||
}
|
||||
|
||||
override fun hideItem(searchable: SavableSearchable) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val dao = database.searchDao()
|
||||
val databaseItem = dao.getFavorite(searchable.key)
|
||||
val savedSearchable = SavedSearchable(
|
||||
key = searchable.key,
|
||||
searchable = searchable,
|
||||
launchCount = databaseItem?.launchCount ?: 0,
|
||||
launchCount = 0,
|
||||
weight = 0.0,
|
||||
pinPosition = 0,
|
||||
hidden = true,
|
||||
weight = databaseItem?.weight ?: 0.0
|
||||
)
|
||||
savedSearchable.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun unhideItem(searchable: SavableSearchable) {
|
||||
|
||||
override fun upsert(
|
||||
searchable: SavableSearchable,
|
||||
hidden: Boolean?,
|
||||
pinned: Boolean?,
|
||||
launchCount: Int?,
|
||||
weight: Double?
|
||||
) {
|
||||
val dao = database.searchableDao()
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
database.searchDao().unhideItem(searchable.key)
|
||||
}
|
||||
val entity = dao.getByKey(searchable.key).firstOrNull()
|
||||
dao.upsert(
|
||||
SavedSearchableEntity(
|
||||
key = searchable.key,
|
||||
type = searchable.domain,
|
||||
hidden = hidden ?: entity?.hidden ?: false,
|
||||
pinPosition = pinned?.let { if (it) 1 else 0 } ?: entity?.pinPosition ?: 0,
|
||||
launchCount = launchCount ?: entity?.launchCount ?: 0,
|
||||
weight = weight ?: entity?.weight ?: 0.0,
|
||||
serializedSearchable = searchable.serialize() ?: return@launch,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun incrementLaunchCounter(searchable: SavableSearchable) {
|
||||
override fun update(
|
||||
searchable: SavableSearchable,
|
||||
hidden: Boolean?,
|
||||
pinned: Boolean?,
|
||||
launchCount: Int?,
|
||||
weight: Double?
|
||||
) {
|
||||
val dao = database.searchableDao()
|
||||
scope.launch {
|
||||
val entity = dao.getByKey(searchable.key).firstOrNull()
|
||||
dao.upsert(
|
||||
SavedSearchableEntity(
|
||||
key = searchable.key,
|
||||
type = searchable.domain,
|
||||
hidden = hidden ?: entity?.hidden ?: false,
|
||||
pinPosition = pinned?.let { if (it) 1 else 0 } ?: entity?.pinPosition ?: 0,
|
||||
launchCount = launchCount ?: entity?.launchCount ?: 0,
|
||||
weight = weight ?: entity?.weight ?: 0.0,
|
||||
serializedSearchable = searchable.serialize() ?: return@launch,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun touch(searchable: SavableSearchable) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val weightFactor =
|
||||
when (dataStore.data.map { it.resultOrdering.weightFactor }.firstOrNull()) {
|
||||
WeightFactor.Low -> WEIGHT_FACTOR_LOW
|
||||
@ -222,50 +197,109 @@ internal class FavoritesRepositoryImpl(
|
||||
}
|
||||
val item = SavedSearchable(searchable.key, searchable, 0, 0, false, 0.0)
|
||||
item.toDatabaseEntity()?.let {
|
||||
database.searchDao()
|
||||
.incrementLaunchCount(it, weightFactor)
|
||||
}
|
||||
database.searchableDao()
|
||||
.touch(it, weightFactor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHiddenItems(): Flow<List<SavableSearchable>> {
|
||||
return database.searchDao().getHiddenItems().map {
|
||||
override fun get(
|
||||
includeTypes: List<String>?,
|
||||
excludeTypes: List<String>?,
|
||||
manuallySorted: Boolean,
|
||||
automaticallySorted: Boolean,
|
||||
frequentlyUsed: Boolean,
|
||||
hidden: Boolean,
|
||||
limit: Int
|
||||
): Flow<List<SavableSearchable>> {
|
||||
val dao = database.searchableDao()
|
||||
val entities = when {
|
||||
includeTypes == null && excludeTypes == null -> dao.get(
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
hidden = hidden,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
includeTypes == null -> dao.getExcludeTypes(
|
||||
excludeTypes = excludeTypes,
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
hidden = hidden,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
excludeTypes == null -> dao.getIncludeTypes(
|
||||
includeTypes = includeTypes,
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
hidden = hidden,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
else -> throw IllegalArgumentException("Cannot specify both includeTypes and excludeTypes")
|
||||
}
|
||||
|
||||
return entities.map {
|
||||
it.mapNotNull { fromDatabaseEntity(it).searchable }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHiddenItemKeys(): Flow<List<String>> {
|
||||
return database.searchDao().getHiddenItemKeys()
|
||||
}
|
||||
override fun getKeys(
|
||||
includeTypes: List<String>?,
|
||||
excludeTypes: List<String>?,
|
||||
manuallySorted: Boolean,
|
||||
automaticallySorted: Boolean,
|
||||
frequentlyUsed: Boolean,
|
||||
hidden: Boolean,
|
||||
limit: Int
|
||||
): Flow<List<String>> {
|
||||
val dao = database.searchableDao()
|
||||
return when {
|
||||
includeTypes == null && excludeTypes == null -> dao.getKeys(
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
hidden = hidden,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
override fun remove(searchable: SavableSearchable) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
database.searchDao().deleteByKey(searchable.key)
|
||||
}
|
||||
includeTypes == null -> dao.getKeysExcludeTypes(
|
||||
excludeTypes = excludeTypes,
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
hidden = hidden,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
excludeTypes == null -> dao.getKeysIncludeTypes(
|
||||
includeTypes = includeTypes,
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
hidden = hidden,
|
||||
limit = limit
|
||||
)
|
||||
|
||||
else -> throw IllegalArgumentException("Cannot specify both includeTypes and excludeTypes")
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeFromFavorites(searchable: SavableSearchable) {
|
||||
scope.launch {
|
||||
database.searchDao().resetPinStatusAndLaunchCounter(searchable.key)
|
||||
}
|
||||
override fun isPinned(searchable: SavableSearchable): Flow<Boolean> {
|
||||
return database.searchableDao().isPinned(searchable.key)
|
||||
}
|
||||
|
||||
override fun save(searchable: SavableSearchable) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val entity = SavedSearchable(
|
||||
key = searchable.key,
|
||||
searchable = searchable,
|
||||
launchCount = 0,
|
||||
pinPosition = 0,
|
||||
hidden = false,
|
||||
weight = 0.0
|
||||
).toDatabaseEntity() ?: return@withContext
|
||||
database.searchDao().insertSkipExisting(entity)
|
||||
override fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
||||
return database.searchableDao().isHidden(searchable.key)
|
||||
}
|
||||
|
||||
override fun delete(searchable: SavableSearchable) {
|
||||
scope.launch {
|
||||
database.searchableDao().delete(searchable.key)
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,51 +307,42 @@ internal class FavoritesRepositoryImpl(
|
||||
manuallySorted: List<SavableSearchable>,
|
||||
automaticallySorted: List<SavableSearchable>
|
||||
) {
|
||||
val dao = database.searchDao()
|
||||
val dao = database.searchableDao()
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val keys = manuallySorted.map { it.key } + automaticallySorted.map { it.key }
|
||||
val entities = dao.getFromKeys(keys)
|
||||
val updatedManuallySorted = manuallySorted.mapIndexedNotNull { index, searchable ->
|
||||
val entity = entities.find { searchable.key == it.key } ?: SavedSearchable(
|
||||
key = searchable.key,
|
||||
searchable = searchable,
|
||||
launchCount = 0,
|
||||
pinPosition = 0,
|
||||
hidden = false,
|
||||
weight = 0.0
|
||||
).toDatabaseEntity() ?: return@mapIndexedNotNull null
|
||||
entity.pinPosition = manuallySorted.size - index + 1
|
||||
entity
|
||||
}
|
||||
val updatedAutomaticallySorted =
|
||||
automaticallySorted.mapIndexedNotNull { index, searchable ->
|
||||
val entity = entities.find { searchable.key == it.key } ?: SavedSearchable(
|
||||
key = searchable.key,
|
||||
searchable = searchable,
|
||||
launchCount = 0,
|
||||
pinPosition = 0,
|
||||
hidden = false,
|
||||
weight = 0.0
|
||||
).toDatabaseEntity() ?: return@mapIndexedNotNull null
|
||||
entity.pinPosition = 1
|
||||
entity
|
||||
}
|
||||
database.runInTransaction {
|
||||
database.withTransaction {
|
||||
dao.unpinAll()
|
||||
dao.insertAllReplaceExisting(updatedManuallySorted)
|
||||
dao.insertAllReplaceExisting(updatedAutomaticallySorted)
|
||||
dao.upsert(
|
||||
manuallySorted.mapIndexedNotNull { index, savableSearchable ->
|
||||
SavedSearchableUpdatePinEntity(
|
||||
key = savableSearchable.key,
|
||||
type = savableSearchable.domain,
|
||||
pinPosition = manuallySorted.size - index + 1,
|
||||
serializedSearchable = savableSearchable.serialize()
|
||||
?: return@mapIndexedNotNull null,
|
||||
)
|
||||
}
|
||||
)
|
||||
dao.upsert(
|
||||
automaticallySorted.mapNotNull { savableSearchable ->
|
||||
SavedSearchableUpdatePinEntity(
|
||||
key = savableSearchable.key,
|
||||
type = savableSearchable.domain,
|
||||
pinPosition = 1,
|
||||
serializedSearchable = savableSearchable.serialize()
|
||||
?: return@mapNotNull null,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun sortByRelevance(keys: List<String>): Flow<List<String>> {
|
||||
return database.searchDao().sortByRelevance(keys)
|
||||
return database.searchableDao().sortByRelevance(keys)
|
||||
}
|
||||
|
||||
override fun sortByWeight(keys: List<String>): Flow<List<String>> {
|
||||
return database.searchDao().sortByWeight(keys)
|
||||
return database.searchableDao().sortByWeight(keys)
|
||||
}
|
||||
|
||||
private fun fromDatabaseEntity(entity: SavedSearchableEntity): SavedSearchable {
|
||||
@ -337,13 +362,13 @@ internal class FavoritesRepositoryImpl(
|
||||
|
||||
private fun removeInvalidItem(key: String) {
|
||||
scope.launch {
|
||||
database.searchDao().deleteByKey(key)
|
||||
database.searchableDao().delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getFromKeys(keys: List<String>): List<SavableSearchable> {
|
||||
val dao = database.searchDao()
|
||||
return dao.getFromKeys(keys)
|
||||
override suspend fun getByKeys(keys: List<String>): List<SavableSearchable> {
|
||||
val dao = database.searchableDao()
|
||||
return dao.getByKeys(keys)
|
||||
.mapNotNull { fromDatabaseEntity(it).searchable }
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package de.mm20.launcher2.favorites
|
||||
package de.mm20.launcher2.searchable
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.appshortcuts.LauncherShortcutDeserializer
|
||||
@ -12,6 +12,7 @@ import de.mm20.launcher2.contacts.ContactSerializer
|
||||
import de.mm20.launcher2.files.*
|
||||
import de.mm20.launcher2.search.NullDeserializer
|
||||
import de.mm20.launcher2.search.NullSerializer
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.Searchable
|
||||
import de.mm20.launcher2.search.SearchableDeserializer
|
||||
import de.mm20.launcher2.search.SearchableSerializer
|
||||
@ -21,6 +22,10 @@ import de.mm20.launcher2.websites.WebsiteSerializer
|
||||
import de.mm20.launcher2.wikipedia.WikipediaDeserializer
|
||||
import de.mm20.launcher2.wikipedia.WikipediaSerializer
|
||||
|
||||
internal fun SavableSearchable.serialize(): String? {
|
||||
val serializer = getSerializer(this)
|
||||
return serializer.serialize(this)
|
||||
}
|
||||
|
||||
internal fun getSerializer(searchable: Searchable?): SearchableSerializer {
|
||||
if (searchable is LauncherApp) {
|
||||
@ -1,4 +1,4 @@
|
||||
package de.mm20.launcher2.favorites
|
||||
package de.mm20.launcher2.searchable
|
||||
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchableDeserializer
|
||||
@ -40,7 +40,7 @@ dependencies {
|
||||
|
||||
implementation(libs.koin.android)
|
||||
|
||||
implementation(project(":data:favorites"))
|
||||
implementation(project(":data:searchable"))
|
||||
implementation(project(":data:widgets"))
|
||||
implementation(project(":data:search-actions"))
|
||||
implementation(project(":core:preferences"))
|
||||
|
||||
@ -4,7 +4,7 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.export
|
||||
import de.mm20.launcher2.preferences.import
|
||||
@ -21,7 +21,7 @@ import java.util.zip.ZipOutputStream
|
||||
class BackupManager(
|
||||
private val context: Context,
|
||||
private val dataStore: LauncherDataStore,
|
||||
private val favoritesRepository: FavoritesRepository,
|
||||
private val searchableRepository: SearchableRepository,
|
||||
private val widgetRepository: WidgetRepository,
|
||||
private val searchActionRepository: SearchActionRepository,
|
||||
private val customAttrsRepository: CustomAttributesRepository,
|
||||
@ -63,7 +63,7 @@ class BackupManager(
|
||||
}
|
||||
|
||||
if (include.contains(BackupComponent.Favorites)) {
|
||||
favoritesRepository.export(backupDir)
|
||||
searchableRepository.export(backupDir)
|
||||
}
|
||||
|
||||
if (include.contains(BackupComponent.Widgets)) {
|
||||
@ -104,7 +104,7 @@ class BackupManager(
|
||||
}
|
||||
|
||||
if (include.contains(BackupComponent.Favorites)) {
|
||||
favoritesRepository.import(restoreDir)
|
||||
searchableRepository.import(restoreDir)
|
||||
}
|
||||
|
||||
if (include.contains(BackupComponent.Widgets)) {
|
||||
|
||||
1
services/favorites/.gitignore
vendored
Normal file
1
services/favorites/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
46
services/favorites/build.gradle.kts
Normal file
46
services/favorites/build.gradle.kts
Normal file
@ -0,0 +1,46 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = sdk.versions.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = sdk.versions.minSdk.get().toInt()
|
||||
targetSdk = sdk.versions.targetSdk.get().toInt()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
namespace = "de.mm20.launcher2.services.favorites"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.bundles.kotlin)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.koin.android)
|
||||
|
||||
implementation(project(":core:base"))
|
||||
implementation(project(":core:i18n"))
|
||||
implementation(project(":data:searchable"))
|
||||
|
||||
}
|
||||
0
services/favorites/consumer-rules.pro
Normal file
0
services/favorites/consumer-rules.pro
Normal file
21
services/favorites/proguard-rules.pro
vendored
Normal file
21
services/favorites/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
4
services/favorites/src/main/AndroidManifest.xml
Normal file
4
services/favorites/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@ -0,0 +1,72 @@
|
||||
package de.mm20.launcher2.services.favorites
|
||||
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class FavoritesService(
|
||||
val searchableRepository: SearchableRepository,
|
||||
) {
|
||||
|
||||
|
||||
fun getFavorites(
|
||||
includeTypes: List<String>? = null,
|
||||
excludeTypes: List<String>? = null,
|
||||
manuallySorted: Boolean = false,
|
||||
automaticallySorted: Boolean = false,
|
||||
frequentlyUsed: Boolean = false,
|
||||
limit: Int = 100,
|
||||
): Flow<List<SavableSearchable>> {
|
||||
return searchableRepository.get(
|
||||
hidden = false,
|
||||
includeTypes = includeTypes,
|
||||
excludeTypes = excludeTypes,
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted,
|
||||
frequentlyUsed = frequentlyUsed,
|
||||
limit = limit,
|
||||
)
|
||||
}
|
||||
fun isPinned(searchable: SavableSearchable): Flow<Boolean> {
|
||||
return searchableRepository.isPinned(searchable)
|
||||
}
|
||||
|
||||
fun pinItem(searchable: SavableSearchable) {
|
||||
searchableRepository.upsert(
|
||||
searchable,
|
||||
pinned = true,
|
||||
hidden = false,
|
||||
)
|
||||
}
|
||||
|
||||
fun reset(searchable: SavableSearchable) {
|
||||
searchableRepository.update(
|
||||
searchable,
|
||||
pinned = false,
|
||||
hidden = false,
|
||||
weight = 0.0,
|
||||
launchCount = 0,
|
||||
)
|
||||
}
|
||||
|
||||
fun unpinItem(searchable: SavableSearchable) {
|
||||
searchableRepository.upsert(
|
||||
searchable,
|
||||
pinned = false,
|
||||
)
|
||||
}
|
||||
|
||||
fun reportLaunch(searchable: SavableSearchable) {
|
||||
searchableRepository.touch(searchable)
|
||||
}
|
||||
|
||||
fun updateFavorites(
|
||||
manuallySorted: List<SavableSearchable>,
|
||||
automaticallySorted: List<SavableSearchable>
|
||||
) {
|
||||
searchableRepository.updateFavorites(
|
||||
manuallySorted = manuallySorted,
|
||||
automaticallySorted = automaticallySorted
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package de.mm20.launcher2.services.favorites
|
||||
|
||||
import org.koin.dsl.module
|
||||
|
||||
val favoritesModule = module {
|
||||
factory { FavoritesService(get()) }
|
||||
}
|
||||
@ -48,6 +48,6 @@ dependencies {
|
||||
implementation(project(":core:ktx"))
|
||||
implementation(project(":core:crashreporter"))
|
||||
implementation(project(":data:customattrs"))
|
||||
implementation(project(":data:favorites"))
|
||||
implementation(project(":data:searchable"))
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package de.mm20.launcher2.services.tags.impl
|
||||
|
||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.searchable.SearchableRepository
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.data.Tag
|
||||
import de.mm20.launcher2.services.tags.TagsService
|
||||
@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
|
||||
|
||||
internal class TagsServiceImpl(
|
||||
private val customAttributesRepository: CustomAttributesRepository,
|
||||
private val favoritesRepository: FavoritesRepository,
|
||||
private val searchableRepository: SearchableRepository,
|
||||
) : TagsService {
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
override fun getAllTags(startsWith: String?): Flow<List<String>> {
|
||||
@ -22,7 +22,7 @@ internal class TagsServiceImpl(
|
||||
}
|
||||
|
||||
override fun deleteTag(tag: String) {
|
||||
favoritesRepository.remove(Tag(tag))
|
||||
searchableRepository.delete(Tag(tag))
|
||||
customAttributesRepository.deleteTag(tag)
|
||||
}
|
||||
|
||||
@ -44,15 +44,15 @@ internal class TagsServiceImpl(
|
||||
}
|
||||
if (newName != null && newName != tag) {
|
||||
customAttributesRepository.renameTag(tag, newName).join()
|
||||
val pinnedTags = favoritesRepository.getFavorites(
|
||||
val pinnedTags = searchableRepository.get(
|
||||
includeTypes = listOf(Tag.Domain),
|
||||
manuallySorted = true,
|
||||
automaticallySorted = true
|
||||
).first()
|
||||
val oldTag = Tag(tag)
|
||||
if (pinnedTags.any { it.key == oldTag.key }) {
|
||||
favoritesRepository.unpinItem(oldTag)
|
||||
favoritesRepository.pinItem(Tag(newName))
|
||||
searchableRepository.update(oldTag, pinned = false)
|
||||
searchableRepository.update(Tag(newName), pinned = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -283,7 +283,7 @@ include(":data:widgets")
|
||||
include(":data:weather")
|
||||
include(":data:notifications")
|
||||
include(":data:search-actions")
|
||||
include(":data:favorites")
|
||||
include(":data:searchable")
|
||||
|
||||
include(":services:accounts")
|
||||
include(":services:tags")
|
||||
@ -301,3 +301,4 @@ include(":libs:g-services")
|
||||
include(":libs:ms-services")
|
||||
include(":services:global-actions")
|
||||
include(":services:widgets")
|
||||
include(":services:favorites")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user