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(":core:crashreporter"))
|
||||||
implementation(project(":data:currencies"))
|
implementation(project(":data:currencies"))
|
||||||
implementation(project(":data:customattrs"))
|
implementation(project(":data:customattrs"))
|
||||||
implementation(project(":data:favorites"))
|
implementation(project(":data:searchable"))
|
||||||
implementation(project(":data:files"))
|
implementation(project(":data:files"))
|
||||||
implementation(project(":libs:g-services"))
|
implementation(project(":libs:g-services"))
|
||||||
implementation(project(":core:i18n"))
|
implementation(project(":core:i18n"))
|
||||||
@ -157,6 +157,7 @@ dependencies {
|
|||||||
implementation(project(":data:search-actions"))
|
implementation(project(":data:search-actions"))
|
||||||
implementation(project(":services:global-actions"))
|
implementation(project(":services:global-actions"))
|
||||||
implementation(project(":services:widgets"))
|
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
|
// Uncomment this if you want annoying notifications in your debug builds yelling at you how terrible your code is
|
||||||
//debugImplementation(libs.leakcanary)
|
//debugImplementation(libs.leakcanary)
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import de.mm20.launcher2.calculator.calculatorModule
|
|||||||
import de.mm20.launcher2.calendar.calendarModule
|
import de.mm20.launcher2.calendar.calendarModule
|
||||||
import de.mm20.launcher2.contacts.contactsModule
|
import de.mm20.launcher2.contacts.contactsModule
|
||||||
import de.mm20.launcher2.data.customattrs.customAttrsModule
|
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.files.filesModule
|
||||||
import de.mm20.launcher2.icons.iconsModule
|
import de.mm20.launcher2.icons.iconsModule
|
||||||
import de.mm20.launcher2.music.musicModule
|
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.permissions.permissionsModule
|
||||||
import de.mm20.launcher2.preferences.preferencesModule
|
import de.mm20.launcher2.preferences.preferencesModule
|
||||||
import de.mm20.launcher2.searchactions.searchActionsModule
|
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.tags.servicesTagsModule
|
||||||
import de.mm20.launcher2.services.widgets.widgetsServiceModule
|
import de.mm20.launcher2.services.widgets.widgetsServiceModule
|
||||||
import de.mm20.launcher2.weather.weatherModule
|
import de.mm20.launcher2.weather.weatherModule
|
||||||
@ -67,6 +68,7 @@ class LauncherApplication : Application(), CoroutineScope, ImageLoaderFactory {
|
|||||||
customAttrsModule,
|
customAttrsModule,
|
||||||
databaseModule,
|
databaseModule,
|
||||||
favoritesModule,
|
favoritesModule,
|
||||||
|
searchableModule,
|
||||||
filesModule,
|
filesModule,
|
||||||
globalActionsModule,
|
globalActionsModule,
|
||||||
iconsModule,
|
iconsModule,
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
package de.mm20.launcher2.activity
|
package de.mm20.launcher2.activity
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.LauncherApps
|
|
||||||
import android.os.Bundle
|
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.AppShortcut
|
||||||
import de.mm20.launcher2.search.data.LauncherShortcut
|
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
class AddItemActivity : Activity() {
|
class AddItemActivity : Activity() {
|
||||||
|
|
||||||
val favoritesRepository: FavoritesRepository by inject()
|
private val favoritesService: FavoritesService by inject()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val shortcut = AppShortcut.fromPinRequestIntent(this, intent)
|
val shortcut = AppShortcut.fromPinRequestIntent(this, intent)
|
||||||
if (shortcut != null) {
|
if (shortcut != null) {
|
||||||
favoritesRepository.pinItem(shortcut)
|
favoritesService.pinItem(shortcut)
|
||||||
}
|
}
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,7 +133,7 @@ dependencies {
|
|||||||
implementation(project(":data:calculator"))
|
implementation(project(":data:calculator"))
|
||||||
implementation(project(":data:files"))
|
implementation(project(":data:files"))
|
||||||
implementation(project(":data:widgets"))
|
implementation(project(":data:widgets"))
|
||||||
implementation(project(":data:favorites"))
|
implementation(project(":data:searchable"))
|
||||||
implementation(project(":data:wikipedia"))
|
implementation(project(":data:wikipedia"))
|
||||||
implementation(project(":services:badges"))
|
implementation(project(":services:badges"))
|
||||||
implementation(project(":core:crashreporter"))
|
implementation(project(":core:crashreporter"))
|
||||||
@ -151,4 +151,5 @@ dependencies {
|
|||||||
implementation(project(":data:search-actions"))
|
implementation(project(":data:search-actions"))
|
||||||
implementation(project(":services:global-actions"))
|
implementation(project(":services:global-actions"))
|
||||||
implementation(project(":services:widgets"))
|
implementation(project(":services:widgets"))
|
||||||
|
implementation(project(":services:favorites"))
|
||||||
}
|
}
|
||||||
@ -4,10 +4,11 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
||||||
import de.mm20.launcher2.data.customattrs.utils.withCustomLabels
|
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.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.data.Tag
|
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.CalendarWidget
|
||||||
import de.mm20.launcher2.widgets.WidgetRepository
|
import de.mm20.launcher2.widgets.WidgetRepository
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
@ -16,7 +17,7 @@ import org.koin.core.component.inject
|
|||||||
|
|
||||||
abstract class FavoritesVM : ViewModel(), KoinComponent {
|
abstract class FavoritesVM : ViewModel(), KoinComponent {
|
||||||
|
|
||||||
private val favoritesRepository: FavoritesRepository by inject()
|
private val favoritesService: FavoritesService by inject()
|
||||||
internal val widgetRepository: WidgetRepository by inject()
|
internal val widgetRepository: WidgetRepository by inject()
|
||||||
private val customAttributesRepository: CustomAttributesRepository by inject()
|
private val customAttributesRepository: CustomAttributesRepository by inject()
|
||||||
internal val dataStore: LauncherDataStore 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 }
|
val showEditButton = dataStore.data.map { it.favorites.editButton }
|
||||||
abstract val tagsExpanded: Flow<Boolean>
|
abstract val tagsExpanded: Flow<Boolean>
|
||||||
|
|
||||||
val pinnedTags = favoritesRepository.getFavorites(
|
val pinnedTags = favoritesService.getFavorites(
|
||||||
includeTypes = listOf("tag"),
|
includeTypes = listOf("tag"),
|
||||||
manuallySorted = true,
|
manuallySorted = true,
|
||||||
automaticallySorted = true,
|
automaticallySorted = true,
|
||||||
@ -55,7 +56,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
|||||||
val includeFrequentlyUsed = it[2] as Boolean
|
val includeFrequentlyUsed = it[2] as Boolean
|
||||||
val frequentlyUsedRows = it[3] as Int
|
val frequentlyUsedRows = it[3] as Int
|
||||||
|
|
||||||
val pinned = favoritesRepository.getFavorites(
|
val pinned = favoritesService.getFavorites(
|
||||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
||||||
manuallySorted = true,
|
manuallySorted = true,
|
||||||
automaticallySorted = true,
|
automaticallySorted = true,
|
||||||
@ -63,7 +64,7 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
|||||||
)
|
)
|
||||||
if (includeFrequentlyUsed) {
|
if (includeFrequentlyUsed) {
|
||||||
emitAll(pinned.flatMapLatest { pinned ->
|
emitAll(pinned.flatMapLatest { pinned ->
|
||||||
favoritesRepository.getFavorites(
|
favoritesService.getFavorites(
|
||||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
||||||
frequentlyUsed = true,
|
frequentlyUsed = true,
|
||||||
limit = frequentlyUsedRows * columns - pinned.size % columns,
|
limit = frequentlyUsedRows * columns - pinned.size % columns,
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package de.mm20.launcher2.ui.launcher
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Rect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@ -11,7 +10,7 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
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.globalactions.GlobalActionsService
|
||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
@ -38,7 +37,7 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
|||||||
private val dataStore: LauncherDataStore by inject()
|
private val dataStore: LauncherDataStore by inject()
|
||||||
private val globalActionsService: GlobalActionsService by inject()
|
private val globalActionsService: GlobalActionsService by inject()
|
||||||
private val permissionsManager: PermissionsManager 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)
|
private var isSystemInDarkMode = MutableStateFlow(false)
|
||||||
|
|
||||||
@ -137,7 +136,7 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
|||||||
swipeDownAppKey,
|
swipeDownAppKey,
|
||||||
longPressAppKey,
|
longPressAppKey,
|
||||||
doubleTapAppKey
|
doubleTapAppKey
|
||||||
).let { favoritesRepository.getFromKeys(it) }
|
).let { searchableRepository.getByKeys(it) }
|
||||||
|
|
||||||
GestureState(
|
GestureState(
|
||||||
swipeLeftAction = swipeLeftAction,
|
swipeLeftAction = swipeLeftAction,
|
||||||
|
|||||||
@ -5,8 +5,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
import de.mm20.launcher2.searchable.SearchableRepository
|
||||||
import de.mm20.launcher2.favorites.SavedSearchableRankInfo
|
|
||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
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.Website
|
||||||
import de.mm20.launcher2.search.data.Wikipedia
|
import de.mm20.launcher2.search.data.Wikipedia
|
||||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||||
|
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -42,7 +42,8 @@ import org.koin.core.component.inject
|
|||||||
|
|
||||||
class SearchVM : ViewModel(), KoinComponent {
|
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 permissionsManager: PermissionsManager by inject()
|
||||||
private val dataStore: LauncherDataStore 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 favoritesEnabled = dataStore.data.map { it.favorites.enabled }
|
||||||
val hideFavorites = mutableStateOf(false)
|
val hideFavorites = mutableStateOf(false)
|
||||||
|
|
||||||
private val hiddenItemKeys = favoritesRepository
|
private val hiddenItemKeys = searchableRepository
|
||||||
.getHiddenItemKeys()
|
.getKeys(
|
||||||
|
hidden = true,
|
||||||
|
)
|
||||||
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||||
|
|
||||||
val bestMatch = mutableStateOf<Searchable?>(null)
|
val bestMatch = mutableStateOf<Searchable?>(null)
|
||||||
@ -85,7 +88,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
val bestMatch = bestMatch.value
|
val bestMatch = bestMatch.value
|
||||||
if (bestMatch is SavableSearchable) {
|
if (bestMatch is SavableSearchable) {
|
||||||
bestMatch.launch(context, null)
|
bestMatch.launch(context, null)
|
||||||
favoritesRepository.incrementLaunchCounter(bestMatch)
|
favoritesService.reportLaunch(bestMatch)
|
||||||
return
|
return
|
||||||
} else if (bestMatch is SearchAction) {
|
} else if (bestMatch is SearchAction) {
|
||||||
bestMatch.start(context)
|
bestMatch.start(context)
|
||||||
@ -144,11 +147,11 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
val keys = resultsList.mapNotNull { (it as? SavableSearchable)?.key }
|
val keys = resultsList.mapNotNull { (it as? SavableSearchable)?.key }
|
||||||
when (settings.resultOrdering.ordering) {
|
when (settings.resultOrdering.ordering) {
|
||||||
|
|
||||||
Ordering.LaunchCount -> favoritesRepository.sortByRelevance(
|
Ordering.LaunchCount -> searchableRepository.sortByRelevance(
|
||||||
keys
|
keys
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
Ordering.Weighted -> favoritesRepository.sortByWeight(
|
Ordering.Weighted -> searchableRepository.sortByWeight(
|
||||||
keys
|
keys
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
|
|||||||
@ -120,15 +120,15 @@ class AppItemVM(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isShortcutPinned(shortcut: AppShortcut): Flow<Boolean> {
|
fun isShortcutPinned(shortcut: AppShortcut): Flow<Boolean> {
|
||||||
return favoritesRepository.isPinned(shortcut)
|
return searchableRepository.isPinned(shortcut)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pinShortcut(shortcut: AppShortcut) {
|
fun pinShortcut(shortcut: AppShortcut) {
|
||||||
favoritesRepository.pinItem(shortcut)
|
favoritesService.pinItem(shortcut)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unpinShortcut(shortcut: AppShortcut) {
|
fun unpinShortcut(shortcut: AppShortcut) {
|
||||||
favoritesRepository.unpinItem(shortcut)
|
favoritesService.unpinItem(shortcut)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchShortcut(context: Context, shortcut: AppShortcut) {
|
fun launchShortcut(context: Context, shortcut: AppShortcut) {
|
||||||
|
|||||||
@ -6,44 +6,42 @@ import androidx.compose.ui.geometry.Rect
|
|||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
import de.mm20.launcher2.badges.BadgeRepository
|
import de.mm20.launcher2.badges.BadgeRepository
|
||||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
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.IconRepository
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
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.SavableSearchable
|
||||||
import de.mm20.launcher2.search.data.AppShortcut
|
import de.mm20.launcher2.search.data.AppShortcut
|
||||||
import de.mm20.launcher2.search.data.LauncherApp
|
import de.mm20.launcher2.search.data.LauncherApp
|
||||||
|
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
abstract class SearchableItemVM(
|
abstract class SearchableItemVM(
|
||||||
private val searchable: SavableSearchable
|
private val searchable: SavableSearchable
|
||||||
) : KoinComponent {
|
) : 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 badgeRepository: BadgeRepository by inject()
|
||||||
protected val iconRepository: IconRepository by inject()
|
protected val iconRepository: IconRepository by inject()
|
||||||
protected val customAttributesRepository: CustomAttributesRepository by inject()
|
protected val customAttributesRepository: CustomAttributesRepository by inject()
|
||||||
|
|
||||||
val isPinned = favoritesRepository.isPinned(searchable)
|
val isPinned = searchableRepository.isPinned(searchable)
|
||||||
fun pin() {
|
fun pin() {
|
||||||
favoritesRepository.pinItem(searchable)
|
favoritesService.pinItem(searchable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unpin() {
|
fun unpin() {
|
||||||
favoritesRepository.unpinItem(searchable)
|
favoritesService.unpinItem(searchable)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isHidden = favoritesRepository.isHidden(searchable)
|
val isHidden = searchableRepository.isHidden(searchable)
|
||||||
fun hide() {
|
fun hide() {
|
||||||
favoritesRepository.hideItem(searchable)
|
searchableRepository.upsert(searchable, hidden = true, pinned = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unhide() {
|
fun unhide() {
|
||||||
favoritesRepository.unhideItem(searchable)
|
searchableRepository.update(searchable, hidden = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val badge = badgeRepository.getBadge(searchable)
|
val badge = badgeRepository.getBadge(searchable)
|
||||||
@ -71,10 +69,10 @@ abstract class SearchableItemVM(
|
|||||||
}
|
}
|
||||||
val bundle = options.toBundle()
|
val bundle = options.toBundle()
|
||||||
if (searchable.launch(context, bundle)) {
|
if (searchable.launch(context, bundle)) {
|
||||||
favoritesRepository.incrementLaunchCounter(searchable)
|
favoritesService.reportLaunch(searchable)
|
||||||
return true
|
return true
|
||||||
} else if (searchable is LauncherApp || searchable is AppShortcut) {
|
} else if (searchable is LauncherApp || searchable is AppShortcut) {
|
||||||
favoritesRepository.remove(searchable)
|
searchableRepository.delete(searchable)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
|
||||||
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
|
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
|
||||||
import de.mm20.launcher2.ktx.tryStartActivity
|
import de.mm20.launcher2.ktx.tryStartActivity
|
||||||
import de.mm20.launcher2.search.data.AppShortcut
|
import de.mm20.launcher2.search.data.AppShortcut
|
||||||
@ -37,6 +36,6 @@ class ShortcutItemVM(private val shortcut: AppShortcut) : SearchableItemVM(short
|
|||||||
fun deleteShortcut() {
|
fun deleteShortcut() {
|
||||||
if (!canDelete) return
|
if (!canDelete) return
|
||||||
if (shortcut is LauncherShortcut) shortcutRepository.removePinnedShortcut(shortcut)
|
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.Badge
|
||||||
import de.mm20.launcher2.badges.BadgeRepository
|
import de.mm20.launcher2.badges.BadgeRepository
|
||||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
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.IconRepository
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
import de.mm20.launcher2.ktx.normalize
|
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.data.AppShortcut
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
import de.mm20.launcher2.search.data.Tag
|
import de.mm20.launcher2.search.data.Tag
|
||||||
|
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
@ -34,7 +35,7 @@ import org.koin.core.component.inject
|
|||||||
|
|
||||||
class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
||||||
|
|
||||||
private val repository: FavoritesRepository by inject()
|
private val favoritesService: FavoritesService by inject()
|
||||||
private val shortcutRepository: AppShortcutRepository by inject()
|
private val shortcutRepository: AppShortcutRepository by inject()
|
||||||
private val iconRepository: IconRepository by inject()
|
private val iconRepository: IconRepository by inject()
|
||||||
private val badgeRepository: BadgeRepository by inject()
|
private val badgeRepository: BadgeRepository by inject()
|
||||||
@ -58,19 +59,19 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
suspend fun reload(showLoadingIndicator: Boolean = true) {
|
suspend fun reload(showLoadingIndicator: Boolean = true) {
|
||||||
loading.value = showLoadingIndicator
|
loading.value = showLoadingIndicator
|
||||||
manuallySorted = mutableListOf()
|
manuallySorted = mutableListOf()
|
||||||
manuallySorted = repository.getFavorites(
|
manuallySorted = favoritesService.getFavorites(
|
||||||
manuallySorted = true,
|
manuallySorted = true,
|
||||||
excludeTypes = listOf("tag"),
|
excludeTypes = listOf("tag"),
|
||||||
).first().toMutableList()
|
).first().toMutableList()
|
||||||
automaticallySorted = repository.getFavorites(
|
automaticallySorted = favoritesService.getFavorites(
|
||||||
automaticallySorted = true,
|
automaticallySorted = true,
|
||||||
excludeTypes = listOf("tag"),
|
excludeTypes = listOf("tag"),
|
||||||
).first().toMutableList()
|
).first().toMutableList()
|
||||||
frequentlyUsed = repository.getFavorites(
|
frequentlyUsed = favoritesService.getFavorites(
|
||||||
frequentlyUsed = true,
|
frequentlyUsed = true,
|
||||||
excludeTypes = listOf("tag"),
|
excludeTypes = listOf("tag"),
|
||||||
).first().toMutableList()
|
).first().toMutableList()
|
||||||
val pinnedTags = repository.getFavorites(
|
val pinnedTags = favoritesService.getFavorites(
|
||||||
includeTypes = listOf("tag"),
|
includeTypes = listOf("tag"),
|
||||||
manuallySorted = true,
|
manuallySorted = true,
|
||||||
automaticallySorted = true,
|
automaticallySorted = true,
|
||||||
@ -169,7 +170,7 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun save() {
|
private fun save() {
|
||||||
repository.updateFavorites(
|
favoritesService.updateFavorites(
|
||||||
manuallySorted = buildList {
|
manuallySorted = buildList {
|
||||||
pinnedTags.value?.let { addAll(it) }
|
pinnedTags.value?.let { addAll(it) }
|
||||||
addAll(manuallySorted)
|
addAll(manuallySorted)
|
||||||
@ -236,7 +237,7 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
val item =
|
val item =
|
||||||
gridItems.find { it is FavoritesSheetGridItem.Favorite && it.item.key == key } as FavoritesSheetGridItem.Favorite?
|
gridItems.find { it is FavoritesSheetGridItem.Favorite && it.item.key == key } as FavoritesSheetGridItem.Favorite?
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
repository.removeFromFavorites(item.item)
|
favoritesService.reset(item.item)
|
||||||
automaticallySorted.removeAll { it.key == item.item.key }
|
automaticallySorted.removeAll { it.key == item.item.key }
|
||||||
|| manuallySorted.removeAll { it.key == item.item.key }
|
|| manuallySorted.removeAll { it.key == item.item.key }
|
||||||
|| frequentlyUsed.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 }
|
|| frequentlyUsed.removeAll { it.key == item.item.key }
|
||||||
buildItemList()
|
buildItemList()
|
||||||
customAttributesRepository.addTag(item.item, tag)
|
customAttributesRepository.addTag(item.item, tag)
|
||||||
repository.unpinItem(item.item)
|
favoritesService.unpinItem(item.item)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
frequentlyUsed = repository.getFavorites(
|
frequentlyUsed = favoritesService.getFavorites(
|
||||||
frequentlyUsed = true,
|
frequentlyUsed = true,
|
||||||
excludeTypes = listOf("tag"),
|
excludeTypes = listOf("tag"),
|
||||||
).first().toMutableList()
|
).first().toMutableList()
|
||||||
|
|||||||
@ -10,12 +10,13 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import de.mm20.launcher2.calendar.CalendarRepository
|
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.ktx.tryStartActivity
|
||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.search.data.CalendarEvent
|
import de.mm20.launcher2.search.data.CalendarEvent
|
||||||
|
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@ -31,11 +32,12 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
private val dataStore: LauncherDataStore by inject()
|
private val dataStore: LauncherDataStore by inject()
|
||||||
private val calendarRepository: CalendarRepository 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 calendarEvents = MutableLiveData<List<CalendarEvent>>(emptyList())
|
||||||
val pinnedCalendarEvents =
|
val pinnedCalendarEvents =
|
||||||
favoritesRepository.getFavorites(
|
favoritesService.getFavorites(
|
||||||
includeTypes = listOf(CalendarEvent.Domain),
|
includeTypes = listOf(CalendarEvent.Domain),
|
||||||
automaticallySorted = true,
|
automaticallySorted = true,
|
||||||
manuallySorted = true,
|
manuallySorted = true,
|
||||||
@ -158,7 +160,10 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
|||||||
excludeAllDayEvents = settings.hideAlldayEvents,
|
excludeAllDayEvents = settings.hideAlldayEvents,
|
||||||
excludeCalendars = settings.excludeCalendarsList
|
excludeCalendars = settings.excludeCalendarsList
|
||||||
).collectLatest { events ->
|
).collectLatest { events ->
|
||||||
favoritesRepository.getHiddenCalendarEventKeys().collectLatest { hidden ->
|
searchableRepository.getKeys(
|
||||||
|
includeTypes = listOf(CalendarEvent.Domain),
|
||||||
|
hidden = true,
|
||||||
|
).collectLatest { hidden ->
|
||||||
upcomingEvents = events.filter { !hidden.contains(it.key) }
|
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.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
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.LauncherDataStore
|
||||||
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout
|
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.ui.launcher.search.common.grid.SearchResultGrid
|
||||||
import de.mm20.launcher2.widgets.CalendarWidget
|
import de.mm20.launcher2.widgets.CalendarWidget
|
||||||
import de.mm20.launcher2.widgets.WidgetRepository
|
import de.mm20.launcher2.widgets.WidgetRepository
|
||||||
@ -26,7 +26,7 @@ import org.koin.core.component.inject
|
|||||||
|
|
||||||
class FavoritesPartProvider : PartProvider, KoinComponent {
|
class FavoritesPartProvider : PartProvider, KoinComponent {
|
||||||
|
|
||||||
private val favoritesRepository: FavoritesRepository by inject()
|
private val favoritesService: FavoritesService by inject()
|
||||||
private val widgetRepository: WidgetRepository by inject()
|
private val widgetRepository: WidgetRepository by inject()
|
||||||
private val dataStore: LauncherDataStore by inject()
|
private val dataStore: LauncherDataStore by inject()
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ class FavoritesPartProvider : PartProvider, KoinComponent {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val favorites by remember(columns, excludeCalendar, layout) {
|
val favorites by remember(columns, excludeCalendar, layout) {
|
||||||
favoritesRepository.getFavorites(
|
favoritesService.getFavorites(
|
||||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
excludeTypes = if (excludeCalendar) listOf("calendar", "tag") else listOf("tag"),
|
||||||
manuallySorted = true,
|
manuallySorted = true,
|
||||||
automaticallySorted = true,
|
automaticallySorted = true,
|
||||||
|
|||||||
@ -1,19 +1,17 @@
|
|||||||
package de.mm20.launcher2.ui.settings.debug
|
package de.mm20.launcher2.ui.settings.debug
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
||||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
import de.mm20.launcher2.searchable.SearchableRepository
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
class DebugSettingsScreenVM: ViewModel(), KoinComponent {
|
class DebugSettingsScreenVM: ViewModel(), KoinComponent {
|
||||||
|
|
||||||
private val favoritesRepository: FavoritesRepository by inject()
|
private val searchableRepository: SearchableRepository by inject()
|
||||||
private val customAttributesRepository: CustomAttributesRepository by inject()
|
private val customAttributesRepository: CustomAttributesRepository by inject()
|
||||||
suspend fun cleanUpDatabase(): Int {
|
suspend fun cleanUpDatabase(): Int {
|
||||||
var removed = favoritesRepository.cleanupDatabase()
|
var removed = searchableRepository.cleanupDatabase()
|
||||||
removed += customAttributesRepository.cleanupDatabase()
|
removed += customAttributesRepository.cleanupDatabase()
|
||||||
return removed
|
return removed
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
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.IconRepository
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
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 de.mm20.launcher2.search.SavableSearchable
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.WhileSubscribed
|
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.shareIn
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@ -26,7 +24,7 @@ import org.koin.core.component.inject
|
|||||||
class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
||||||
private val dataStore: LauncherDataStore by inject()
|
private val dataStore: LauncherDataStore by inject()
|
||||||
private val permissionsManager: PermissionsManager 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()
|
private val iconRepository: IconRepository by inject()
|
||||||
|
|
||||||
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Accessibility).asLiveData()
|
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 }
|
val swipeLeftApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeLeftApp }
|
||||||
.map {
|
.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)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||||
|
|
||||||
fun setSwipeLeftApp(searchable: SavableSearchable?) {
|
fun setSwipeLeftApp(searchable: SavableSearchable?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
searchable?.let { favoritesRepository.save(it) }
|
searchable?.let { searchableRepository.insert(it) }
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder()
|
||||||
.setGestures(it.gestures.toBuilder()
|
.setGestures(it.gestures.toBuilder()
|
||||||
@ -106,13 +104,13 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
val swipeRightApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeRightApp }
|
val swipeRightApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeRightApp }
|
||||||
.map {
|
.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)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||||
|
|
||||||
fun setSwipeRightApp(searchable: SavableSearchable?) {
|
fun setSwipeRightApp(searchable: SavableSearchable?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
searchable?.let { favoritesRepository.save(it) }
|
searchable?.let { searchableRepository.insert(it) }
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder()
|
||||||
.setGestures(it.gestures.toBuilder()
|
.setGestures(it.gestures.toBuilder()
|
||||||
@ -126,13 +124,13 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
val swipeDownApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeDownApp }
|
val swipeDownApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.swipeDownApp }
|
||||||
.map {
|
.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)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||||
|
|
||||||
fun setSwipeDownApp(searchable: SavableSearchable?) {
|
fun setSwipeDownApp(searchable: SavableSearchable?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
searchable?.let { favoritesRepository.save(it) }
|
searchable?.let { searchableRepository.insert(it) }
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder()
|
||||||
.setGestures(it.gestures.toBuilder()
|
.setGestures(it.gestures.toBuilder()
|
||||||
@ -146,13 +144,13 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
val longPressApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.longPressApp }
|
val longPressApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.longPressApp }
|
||||||
.map {
|
.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)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||||
|
|
||||||
fun setLongPressApp(searchable: SavableSearchable?) {
|
fun setLongPressApp(searchable: SavableSearchable?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
searchable?.let { favoritesRepository.save(it) }
|
searchable?.let { searchableRepository.insert(it) }
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder()
|
||||||
.setGestures(it.gestures.toBuilder()
|
.setGestures(it.gestures.toBuilder()
|
||||||
@ -166,13 +164,13 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
val doubleTapApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.doubleTapApp }
|
val doubleTapApp: Flow<SavableSearchable?> = dataStore.data.map { it.gestures.doubleTapApp }
|
||||||
.map {
|
.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)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(stopTimeoutMillis = 10000), null)
|
||||||
|
|
||||||
fun setDoubleTapApp(searchable: SavableSearchable?) {
|
fun setDoubleTapApp(searchable: SavableSearchable?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
searchable?.let { favoritesRepository.save(it) }
|
searchable?.let { searchableRepository.insert(it) }
|
||||||
dataStore.updateData {
|
dataStore.updateData {
|
||||||
it.toBuilder()
|
it.toBuilder()
|
||||||
.setGestures(it.gestures.toBuilder()
|
.setGestures(it.gestures.toBuilder()
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.liveData
|
import androidx.lifecycle.liveData
|
||||||
import de.mm20.launcher2.applications.AppRepository
|
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.IconRepository
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
@ -26,26 +26,26 @@ import org.koin.core.component.inject
|
|||||||
|
|
||||||
class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
|
class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
|
||||||
private val appRepository: AppRepository by inject()
|
private val appRepository: AppRepository by inject()
|
||||||
private val favoritesRepository: FavoritesRepository by inject()
|
private val searchableRepository: SearchableRepository by inject()
|
||||||
private val iconRepository: IconRepository by inject()
|
private val iconRepository: IconRepository by inject()
|
||||||
|
|
||||||
val allApps = appRepository.getAllInstalledApps().map {
|
val allApps = appRepository.getAllInstalledApps().map {
|
||||||
withContext(Dispatchers.Default) { it.sorted() }
|
withContext(Dispatchers.Default) { it.sorted() }
|
||||||
}.asLiveData()
|
}.asLiveData()
|
||||||
val hiddenItems: LiveData<List<SavableSearchable>> = liveData {
|
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)
|
emit(hidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
||||||
return favoritesRepository.isHidden(searchable)
|
return searchableRepository.isHidden(searchable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setHidden(searchable: SavableSearchable, hidden: Boolean) {
|
fun setHidden(searchable: SavableSearchable, hidden: Boolean) {
|
||||||
if(hidden) {
|
if(hidden) {
|
||||||
favoritesRepository.hideItem(searchable)
|
searchableRepository.upsert(searchable, hidden = true, pinned = false)
|
||||||
} else {
|
} 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_20_21
|
||||||
import de.mm20.launcher2.database.migrations.Migration_21_22
|
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_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_6_7
|
||||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
import de.mm20.launcher2.database.migrations.Migration_7_8
|
||||||
import de.mm20.launcher2.database.migrations.Migration_8_9
|
import de.mm20.launcher2.database.migrations.Migration_8_9
|
||||||
@ -39,14 +40,15 @@ import java.util.UUID
|
|||||||
WidgetEntity::class,
|
WidgetEntity::class,
|
||||||
CustomAttributeEntity::class,
|
CustomAttributeEntity::class,
|
||||||
SearchActionEntity::class
|
SearchActionEntity::class
|
||||||
], version = 23, exportSchema = true
|
], version = 24, exportSchema = true
|
||||||
)
|
)
|
||||||
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
|
@TypeConverters(ComponentNameConverter::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
abstract fun weatherDao(): WeatherDao
|
abstract fun weatherDao(): WeatherDao
|
||||||
abstract fun searchDao(): SearchDao
|
|
||||||
abstract fun iconDao(): IconDao
|
abstract fun iconDao(): IconDao
|
||||||
|
|
||||||
|
abstract fun searchableDao(): SearchableDao
|
||||||
abstract fun widgetDao(): WidgetDao
|
abstract fun widgetDao(): WidgetDao
|
||||||
abstract fun currencyDao(): CurrencyDao
|
abstract fun currencyDao(): CurrencyDao
|
||||||
abstract fun backupDao(): BackupRestoreDao
|
abstract fun backupDao(): BackupRestoreDao
|
||||||
@ -114,6 +116,7 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration_20_21(),
|
Migration_20_21(),
|
||||||
Migration_21_22(),
|
Migration_21_22(),
|
||||||
Migration_22_23(),
|
Migration_22_23(),
|
||||||
|
Migration_23_24(),
|
||||||
).build()
|
).build()
|
||||||
if (_instance == null) _instance = instance
|
if (_instance == null) _instance = instance
|
||||||
return 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,
|
@PrimaryKey val key: String,
|
||||||
val type: String,
|
val type: String,
|
||||||
@ColumnInfo(name = "searchable") val serializedSearchable: String,
|
@ColumnInfo(name = "searchable") val serializedSearchable: String,
|
||||||
var launchCount: Int,
|
@ColumnInfo(defaultValue = "0") val launchCount: Int,
|
||||||
@ColumnInfo(name = "pinned") var pinPosition: Int,
|
@ColumnInfo(defaultValue = "0") val pinPosition: Int,
|
||||||
var hidden: Boolean,
|
@ColumnInfo(defaultValue = "0") val hidden: Boolean,
|
||||||
@ColumnInfo(defaultValue = "0.0") var weight: Double
|
@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:base"))
|
||||||
implementation(project(":core:ktx"))
|
implementation(project(":core:ktx"))
|
||||||
implementation(project(":core:crashreporter"))
|
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.crashreporter.CrashReporter
|
||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
import de.mm20.launcher2.database.entities.CustomAttributeEntity
|
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.ktx.jsonObjectOf
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@ -46,7 +46,7 @@ interface CustomAttributesRepository {
|
|||||||
|
|
||||||
internal class CustomAttributesRepositoryImpl(
|
internal class CustomAttributesRepositoryImpl(
|
||||||
private val appDatabase: AppDatabase,
|
private val appDatabase: AppDatabase,
|
||||||
private val favoritesRepository: FavoritesRepository
|
private val searchableRepository: SearchableRepository
|
||||||
) : CustomAttributesRepository {
|
) : CustomAttributesRepository {
|
||||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ internal class CustomAttributesRepositoryImpl(
|
|||||||
override fun setCustomLabel(searchable: SavableSearchable, label: String) {
|
override fun setCustomLabel(searchable: SavableSearchable, label: String) {
|
||||||
val dao = appDatabase.customAttrsDao()
|
val dao = appDatabase.customAttrsDao()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
favoritesRepository.save(searchable)
|
searchableRepository.insert(searchable)
|
||||||
appDatabase.runInTransaction {
|
appDatabase.runInTransaction {
|
||||||
dao.clearCustomAttribute(searchable.key, CustomAttributeType.Label.value)
|
dao.clearCustomAttribute(searchable.key, CustomAttributeType.Label.value)
|
||||||
dao.setCustomAttribute(
|
dao.setCustomAttribute(
|
||||||
@ -102,7 +102,7 @@ internal class CustomAttributesRepositoryImpl(
|
|||||||
override fun setTags(searchable: SavableSearchable, tags: List<String>) {
|
override fun setTags(searchable: SavableSearchable, tags: List<String>) {
|
||||||
val dao = appDatabase.customAttrsDao()
|
val dao = appDatabase.customAttrsDao()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
favoritesRepository.save(searchable)
|
searchableRepository.insert(searchable)
|
||||||
dao.setTags(searchable.key, tags.map {
|
dao.setTags(searchable.key, tags.map {
|
||||||
CustomTag(it).toDatabaseEntity(searchable.key)
|
CustomTag(it).toDatabaseEntity(searchable.key)
|
||||||
})
|
})
|
||||||
@ -128,7 +128,7 @@ internal class CustomAttributesRepositoryImpl(
|
|||||||
override fun getItemsForTag(tag: String): Flow<List<SavableSearchable>> {
|
override fun getItemsForTag(tag: String): Flow<List<SavableSearchable>> {
|
||||||
val dao = appDatabase.customAttrsDao()
|
val dao = appDatabase.customAttrsDao()
|
||||||
return dao.getItemsWithTag(tag).map {
|
return dao.getItemsWithTag(tag).map {
|
||||||
favoritesRepository.getFromKeys(it)
|
searchableRepository.getByKeys(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ internal class CustomAttributesRepositoryImpl(
|
|||||||
return scope.launch {
|
return scope.launch {
|
||||||
dao.setItemsWithTag(tag, items.map { it.key })
|
dao.setItemsWithTag(tag, items.map { it.key })
|
||||||
for (item in items) {
|
for (item in items) {
|
||||||
favoritesRepository.save(item)
|
searchableRepository.insert(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,7 +172,7 @@ internal class CustomAttributesRepositoryImpl(
|
|||||||
}
|
}
|
||||||
val dao = appDatabase.customAttrsDao()
|
val dao = appDatabase.customAttrsDao()
|
||||||
return dao.search("%$query%").map {
|
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.database.entities.SavedSearchableEntity
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package de.mm20.launcher2.favorites
|
package de.mm20.launcher2.searchable
|
||||||
|
|
||||||
data class SavedSearchableRankInfo(
|
data class SavedSearchableRankInfo(
|
||||||
val key: String,
|
val key: String,
|
||||||
@ -1,58 +1,87 @@
|
|||||||
package de.mm20.launcher2.favorites
|
package de.mm20.launcher2.searchable
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.room.withTransaction
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
||||||
|
import de.mm20.launcher2.database.entities.SavedSearchableUpdatePinEntity
|
||||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.preferences.Settings.SearchResultOrderingSettings.WeightFactor
|
import de.mm20.launcher2.preferences.Settings.SearchResultOrderingSettings.WeightFactor
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.SearchableDeserializer
|
import de.mm20.launcher2.search.SearchableDeserializer
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.*
|
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.JSONArray
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import java.io.File
|
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
|
* Touch a searchable to update its weight and launch counter
|
||||||
* @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.
|
fun touch(
|
||||||
* @param manuallySorted Include items that have been sorted manually
|
searchable: SavableSearchable,
|
||||||
* @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 get(
|
||||||
*/
|
|
||||||
fun getFavorites(
|
|
||||||
includeTypes: List<String>? = null,
|
includeTypes: List<String>? = null,
|
||||||
excludeTypes: List<String>? = null,
|
excludeTypes: List<String>? = null,
|
||||||
manuallySorted: Boolean = false,
|
manuallySorted: Boolean = false,
|
||||||
automaticallySorted: Boolean = false,
|
automaticallySorted: Boolean = false,
|
||||||
frequentlyUsed: Boolean = false,
|
frequentlyUsed: Boolean = false,
|
||||||
limit: Int = 100
|
hidden: Boolean = false,
|
||||||
|
limit: Int = 100,
|
||||||
): Flow<List<SavableSearchable>>
|
): 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 isPinned(searchable: SavableSearchable): Flow<Boolean>
|
||||||
fun pinItem(searchable: SavableSearchable)
|
|
||||||
fun unpinItem(searchable: SavableSearchable)
|
|
||||||
fun isHidden(searchable: SavableSearchable): Flow<Boolean>
|
fun isHidden(searchable: SavableSearchable): Flow<Boolean>
|
||||||
fun hideItem(searchable: SavableSearchable)
|
|
||||||
fun unhideItem(searchable: SavableSearchable)
|
|
||||||
fun incrementLaunchCounter(searchable: SavableSearchable)
|
|
||||||
fun updateFavorites(
|
fun updateFavorites(
|
||||||
manuallySorted: List<SavableSearchable>,
|
manuallySorted: List<SavableSearchable>,
|
||||||
automaticallySorted: List<SavableSearchable>,
|
automaticallySorted: List<SavableSearchable>,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getHiddenItems(): Flow<List<SavableSearchable>>
|
|
||||||
fun getHiddenItemKeys(): Flow<List<String>>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given keys sorted by relevance.
|
* Returns the given keys sorted by relevance.
|
||||||
* The first item in the list is the most relevant.
|
* The first item in the list is the most relevant.
|
||||||
@ -65,24 +94,13 @@ interface FavoritesRepository {
|
|||||||
/**
|
/**
|
||||||
* Remove this item from the Searchable database
|
* Remove this item from the Searchable database
|
||||||
*/
|
*/
|
||||||
fun remove(searchable: SavableSearchable)
|
fun delete(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)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get items with the given keys from the favorites database.
|
* Get items with the given keys from the favorites database.
|
||||||
* Items that don't exist in the database will not be returned.
|
* 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 export(toDir: File)
|
||||||
suspend fun import(fromDir: File)
|
suspend fun import(fromDir: File)
|
||||||
@ -95,177 +113,193 @@ interface FavoritesRepository {
|
|||||||
suspend fun cleanupDatabase(): Int
|
suspend fun cleanupDatabase(): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class FavoritesRepositoryImpl(
|
internal class SearchableRepositoryImpl(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val database: AppDatabase,
|
private val database: AppDatabase,
|
||||||
private val dataStore: LauncherDataStore
|
private val dataStore: LauncherDataStore
|
||||||
) : FavoritesRepository, KoinComponent {
|
) : SearchableRepository, KoinComponent {
|
||||||
|
|
||||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
|
|
||||||
override fun getFavorites(
|
override fun insert(searchable: SavableSearchable) {
|
||||||
|
val dao = database.searchableDao()
|
||||||
|
scope.launch {
|
||||||
|
dao.insert(
|
||||||
|
SavedSearchableEntity(
|
||||||
|
key = searchable.key,
|
||||||
|
type = searchable.domain,
|
||||||
|
serializedSearchable = searchable.serialize() ?: return@launch,
|
||||||
|
hidden = false,
|
||||||
|
launchCount = 0,
|
||||||
|
weight = 0.0,
|
||||||
|
pinPosition = 0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun upsert(
|
||||||
|
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 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 {
|
||||||
|
val weightFactor =
|
||||||
|
when (dataStore.data.map { it.resultOrdering.weightFactor }.firstOrNull()) {
|
||||||
|
WeightFactor.Low -> WEIGHT_FACTOR_LOW
|
||||||
|
WeightFactor.High -> WEIGHT_FACTOR_HIGH
|
||||||
|
else -> WEIGHT_FACTOR_MEDIUM
|
||||||
|
}
|
||||||
|
val item = SavedSearchable(searchable.key, searchable, 0, 0, false, 0.0)
|
||||||
|
item.toDatabaseEntity()?.let {
|
||||||
|
database.searchableDao()
|
||||||
|
.touch(it, weightFactor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(
|
||||||
includeTypes: List<String>?,
|
includeTypes: List<String>?,
|
||||||
excludeTypes: List<String>?,
|
excludeTypes: List<String>?,
|
||||||
manuallySorted: Boolean,
|
manuallySorted: Boolean,
|
||||||
automaticallySorted: Boolean,
|
automaticallySorted: Boolean,
|
||||||
frequentlyUsed: Boolean,
|
frequentlyUsed: Boolean,
|
||||||
|
hidden: Boolean,
|
||||||
limit: Int
|
limit: Int
|
||||||
): Flow<List<SavableSearchable>> {
|
): Flow<List<SavableSearchable>> {
|
||||||
val dao = database.searchDao()
|
val dao = database.searchableDao()
|
||||||
val entities = when {
|
val entities = when {
|
||||||
includeTypes == null && excludeTypes == null -> dao.getFavorites(
|
includeTypes == null && excludeTypes == null -> dao.get(
|
||||||
manuallySorted = manuallySorted,
|
manuallySorted = manuallySorted,
|
||||||
automaticallySorted = automaticallySorted,
|
automaticallySorted = automaticallySorted,
|
||||||
frequentlyUsed = frequentlyUsed,
|
frequentlyUsed = frequentlyUsed,
|
||||||
|
hidden = hidden,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
|
|
||||||
includeTypes != null && excludeTypes == null -> {
|
includeTypes == null -> dao.getExcludeTypes(
|
||||||
dao.getFavoritesWithTypes(
|
excludeTypes = excludeTypes,
|
||||||
includeTypes = includeTypes,
|
manuallySorted = manuallySorted,
|
||||||
manuallySorted = manuallySorted,
|
automaticallySorted = automaticallySorted,
|
||||||
automaticallySorted = automaticallySorted,
|
frequentlyUsed = frequentlyUsed,
|
||||||
frequentlyUsed = frequentlyUsed,
|
hidden = hidden,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
excludeTypes != null && includeTypes == null -> {
|
excludeTypes == null -> dao.getIncludeTypes(
|
||||||
dao.getFavoritesWithoutTypes(
|
includeTypes = includeTypes,
|
||||||
excludeTypes = excludeTypes,
|
manuallySorted = manuallySorted,
|
||||||
manuallySorted = manuallySorted,
|
automaticallySorted = automaticallySorted,
|
||||||
automaticallySorted = automaticallySorted,
|
frequentlyUsed = frequentlyUsed,
|
||||||
frequentlyUsed = frequentlyUsed,
|
hidden = hidden,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("You can either use includeTypes or excludeTypes, not both")
|
else -> throw IllegalArgumentException("Cannot specify both includeTypes and excludeTypes")
|
||||||
}
|
}
|
||||||
|
|
||||||
return entities.map {
|
return entities.map {
|
||||||
it.mapNotNull { fromDatabaseEntity(it).searchable }
|
it.mapNotNull { fromDatabaseEntity(it).searchable }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getHiddenCalendarEventKeys(): Flow<List<String>> {
|
override fun getKeys(
|
||||||
return database.searchDao().getHiddenCalendarEventKeys()
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
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 isPinned(searchable: SavableSearchable): Flow<Boolean> {
|
override fun isPinned(searchable: SavableSearchable): Flow<Boolean> {
|
||||||
return database.searchDao().isPinned(searchable.key)
|
return database.searchableDao().isPinned(searchable.key)
|
||||||
}
|
|
||||||
|
|
||||||
override fun pinItem(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,
|
|
||||||
pinPosition = 1,
|
|
||||||
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> {
|
override fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
|
||||||
return database.searchDao().isHidden(searchable.key)
|
return database.searchableDao().isHidden(searchable.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hideItem(searchable: SavableSearchable) {
|
override fun delete(searchable: SavableSearchable) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
database.searchableDao().delete(searchable.key)
|
||||||
val dao = database.searchDao()
|
|
||||||
val databaseItem = dao.getFavorite(searchable.key)
|
|
||||||
val savedSearchable = SavedSearchable(
|
|
||||||
key = searchable.key,
|
|
||||||
searchable = searchable,
|
|
||||||
launchCount = databaseItem?.launchCount ?: 0,
|
|
||||||
pinPosition = 0,
|
|
||||||
hidden = true,
|
|
||||||
weight = databaseItem?.weight ?: 0.0
|
|
||||||
)
|
|
||||||
savedSearchable.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun unhideItem(searchable: SavableSearchable) {
|
|
||||||
scope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
database.searchDao().unhideItem(searchable.key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun incrementLaunchCounter(searchable: SavableSearchable) {
|
|
||||||
scope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val weightFactor =
|
|
||||||
when (dataStore.data.map { it.resultOrdering.weightFactor }.firstOrNull()) {
|
|
||||||
WeightFactor.Low -> WEIGHT_FACTOR_LOW
|
|
||||||
WeightFactor.High -> WEIGHT_FACTOR_HIGH
|
|
||||||
else -> WEIGHT_FACTOR_MEDIUM
|
|
||||||
}
|
|
||||||
val item = SavedSearchable(searchable.key, searchable, 0, 0, false, 0.0)
|
|
||||||
item.toDatabaseEntity()?.let {
|
|
||||||
database.searchDao()
|
|
||||||
.incrementLaunchCount(it, weightFactor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHiddenItems(): Flow<List<SavableSearchable>> {
|
|
||||||
return database.searchDao().getHiddenItems().map {
|
|
||||||
it.mapNotNull { fromDatabaseEntity(it).searchable }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHiddenItemKeys(): Flow<List<String>> {
|
|
||||||
return database.searchDao().getHiddenItemKeys()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun remove(searchable: SavableSearchable) {
|
|
||||||
scope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
database.searchDao().deleteByKey(searchable.key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeFromFavorites(searchable: SavableSearchable) {
|
|
||||||
scope.launch {
|
|
||||||
database.searchDao().resetPinStatusAndLaunchCounter(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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,51 +307,42 @@ internal class FavoritesRepositoryImpl(
|
|||||||
manuallySorted: List<SavableSearchable>,
|
manuallySorted: List<SavableSearchable>,
|
||||||
automaticallySorted: List<SavableSearchable>
|
automaticallySorted: List<SavableSearchable>
|
||||||
) {
|
) {
|
||||||
val dao = database.searchDao()
|
val dao = database.searchableDao()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
database.withTransaction {
|
||||||
val keys = manuallySorted.map { it.key } + automaticallySorted.map { it.key }
|
dao.unpinAll()
|
||||||
val entities = dao.getFromKeys(keys)
|
dao.upsert(
|
||||||
val updatedManuallySorted = manuallySorted.mapIndexedNotNull { index, searchable ->
|
manuallySorted.mapIndexedNotNull { index, savableSearchable ->
|
||||||
val entity = entities.find { searchable.key == it.key } ?: SavedSearchable(
|
SavedSearchableUpdatePinEntity(
|
||||||
key = searchable.key,
|
key = savableSearchable.key,
|
||||||
searchable = searchable,
|
type = savableSearchable.domain,
|
||||||
launchCount = 0,
|
pinPosition = manuallySorted.size - index + 1,
|
||||||
pinPosition = 0,
|
serializedSearchable = savableSearchable.serialize()
|
||||||
hidden = false,
|
?: return@mapIndexedNotNull null,
|
||||||
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 {
|
)
|
||||||
dao.unpinAll()
|
dao.upsert(
|
||||||
dao.insertAllReplaceExisting(updatedManuallySorted)
|
automaticallySorted.mapNotNull { savableSearchable ->
|
||||||
dao.insertAllReplaceExisting(updatedAutomaticallySorted)
|
SavedSearchableUpdatePinEntity(
|
||||||
}
|
key = savableSearchable.key,
|
||||||
|
type = savableSearchable.domain,
|
||||||
|
pinPosition = 1,
|
||||||
|
serializedSearchable = savableSearchable.serialize()
|
||||||
|
?: return@mapNotNull null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sortByRelevance(keys: List<String>): Flow<List<String>> {
|
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>> {
|
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 {
|
private fun fromDatabaseEntity(entity: SavedSearchableEntity): SavedSearchable {
|
||||||
@ -337,13 +362,13 @@ internal class FavoritesRepositoryImpl(
|
|||||||
|
|
||||||
private fun removeInvalidItem(key: String) {
|
private fun removeInvalidItem(key: String) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
database.searchDao().deleteByKey(key)
|
database.searchableDao().delete(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getFromKeys(keys: List<String>): List<SavableSearchable> {
|
override suspend fun getByKeys(keys: List<String>): List<SavableSearchable> {
|
||||||
val dao = database.searchDao()
|
val dao = database.searchableDao()
|
||||||
return dao.getFromKeys(keys)
|
return dao.getByKeys(keys)
|
||||||
.mapNotNull { fromDatabaseEntity(it).searchable }
|
.mapNotNull { fromDatabaseEntity(it).searchable }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package de.mm20.launcher2.favorites
|
package de.mm20.launcher2.searchable
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import de.mm20.launcher2.appshortcuts.LauncherShortcutDeserializer
|
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.files.*
|
||||||
import de.mm20.launcher2.search.NullDeserializer
|
import de.mm20.launcher2.search.NullDeserializer
|
||||||
import de.mm20.launcher2.search.NullSerializer
|
import de.mm20.launcher2.search.NullSerializer
|
||||||
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
import de.mm20.launcher2.search.SearchableDeserializer
|
import de.mm20.launcher2.search.SearchableDeserializer
|
||||||
import de.mm20.launcher2.search.SearchableSerializer
|
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.WikipediaDeserializer
|
||||||
import de.mm20.launcher2.wikipedia.WikipediaSerializer
|
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 {
|
internal fun getSerializer(searchable: Searchable?): SearchableSerializer {
|
||||||
if (searchable is LauncherApp) {
|
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.SavableSearchable
|
||||||
import de.mm20.launcher2.search.SearchableDeserializer
|
import de.mm20.launcher2.search.SearchableDeserializer
|
||||||
@ -40,7 +40,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.koin.android)
|
implementation(libs.koin.android)
|
||||||
|
|
||||||
implementation(project(":data:favorites"))
|
implementation(project(":data:searchable"))
|
||||||
implementation(project(":data:widgets"))
|
implementation(project(":data:widgets"))
|
||||||
implementation(project(":data:search-actions"))
|
implementation(project(":data:search-actions"))
|
||||||
implementation(project(":core:preferences"))
|
implementation(project(":core:preferences"))
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
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.LauncherDataStore
|
||||||
import de.mm20.launcher2.preferences.export
|
import de.mm20.launcher2.preferences.export
|
||||||
import de.mm20.launcher2.preferences.import
|
import de.mm20.launcher2.preferences.import
|
||||||
@ -21,7 +21,7 @@ import java.util.zip.ZipOutputStream
|
|||||||
class BackupManager(
|
class BackupManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val dataStore: LauncherDataStore,
|
private val dataStore: LauncherDataStore,
|
||||||
private val favoritesRepository: FavoritesRepository,
|
private val searchableRepository: SearchableRepository,
|
||||||
private val widgetRepository: WidgetRepository,
|
private val widgetRepository: WidgetRepository,
|
||||||
private val searchActionRepository: SearchActionRepository,
|
private val searchActionRepository: SearchActionRepository,
|
||||||
private val customAttrsRepository: CustomAttributesRepository,
|
private val customAttrsRepository: CustomAttributesRepository,
|
||||||
@ -63,7 +63,7 @@ class BackupManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (include.contains(BackupComponent.Favorites)) {
|
if (include.contains(BackupComponent.Favorites)) {
|
||||||
favoritesRepository.export(backupDir)
|
searchableRepository.export(backupDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (include.contains(BackupComponent.Widgets)) {
|
if (include.contains(BackupComponent.Widgets)) {
|
||||||
@ -104,7 +104,7 @@ class BackupManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (include.contains(BackupComponent.Favorites)) {
|
if (include.contains(BackupComponent.Favorites)) {
|
||||||
favoritesRepository.import(restoreDir)
|
searchableRepository.import(restoreDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (include.contains(BackupComponent.Widgets)) {
|
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:ktx"))
|
||||||
implementation(project(":core:crashreporter"))
|
implementation(project(":core:crashreporter"))
|
||||||
implementation(project(":data:customattrs"))
|
implementation(project(":data:customattrs"))
|
||||||
implementation(project(":data:favorites"))
|
implementation(project(":data:searchable"))
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package de.mm20.launcher2.services.tags.impl
|
package de.mm20.launcher2.services.tags.impl
|
||||||
|
|
||||||
import de.mm20.launcher2.data.customattrs.CustomAttributesRepository
|
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.SavableSearchable
|
||||||
import de.mm20.launcher2.search.data.Tag
|
import de.mm20.launcher2.search.data.Tag
|
||||||
import de.mm20.launcher2.services.tags.TagsService
|
import de.mm20.launcher2.services.tags.TagsService
|
||||||
@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
internal class TagsServiceImpl(
|
internal class TagsServiceImpl(
|
||||||
private val customAttributesRepository: CustomAttributesRepository,
|
private val customAttributesRepository: CustomAttributesRepository,
|
||||||
private val favoritesRepository: FavoritesRepository,
|
private val searchableRepository: SearchableRepository,
|
||||||
) : TagsService {
|
) : TagsService {
|
||||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
override fun getAllTags(startsWith: String?): Flow<List<String>> {
|
override fun getAllTags(startsWith: String?): Flow<List<String>> {
|
||||||
@ -22,7 +22,7 @@ internal class TagsServiceImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteTag(tag: String) {
|
override fun deleteTag(tag: String) {
|
||||||
favoritesRepository.remove(Tag(tag))
|
searchableRepository.delete(Tag(tag))
|
||||||
customAttributesRepository.deleteTag(tag)
|
customAttributesRepository.deleteTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,15 +44,15 @@ internal class TagsServiceImpl(
|
|||||||
}
|
}
|
||||||
if (newName != null && newName != tag) {
|
if (newName != null && newName != tag) {
|
||||||
customAttributesRepository.renameTag(tag, newName).join()
|
customAttributesRepository.renameTag(tag, newName).join()
|
||||||
val pinnedTags = favoritesRepository.getFavorites(
|
val pinnedTags = searchableRepository.get(
|
||||||
includeTypes = listOf(Tag.Domain),
|
includeTypes = listOf(Tag.Domain),
|
||||||
manuallySorted = true,
|
manuallySorted = true,
|
||||||
automaticallySorted = true
|
automaticallySorted = true
|
||||||
).first()
|
).first()
|
||||||
val oldTag = Tag(tag)
|
val oldTag = Tag(tag)
|
||||||
if (pinnedTags.any { it.key == oldTag.key }) {
|
if (pinnedTags.any { it.key == oldTag.key }) {
|
||||||
favoritesRepository.unpinItem(oldTag)
|
searchableRepository.update(oldTag, pinned = false)
|
||||||
favoritesRepository.pinItem(Tag(newName))
|
searchableRepository.update(Tag(newName), pinned = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -283,7 +283,7 @@ include(":data:widgets")
|
|||||||
include(":data:weather")
|
include(":data:weather")
|
||||||
include(":data:notifications")
|
include(":data:notifications")
|
||||||
include(":data:search-actions")
|
include(":data:search-actions")
|
||||||
include(":data:favorites")
|
include(":data:searchable")
|
||||||
|
|
||||||
include(":services:accounts")
|
include(":services:accounts")
|
||||||
include(":services:tags")
|
include(":services:tags")
|
||||||
@ -301,3 +301,4 @@ include(":libs:g-services")
|
|||||||
include(":libs:ms-services")
|
include(":libs:ms-services")
|
||||||
include(":services:global-actions")
|
include(":services:global-actions")
|
||||||
include(":services:widgets")
|
include(":services:widgets")
|
||||||
|
include(":services:favorites")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user