- PinnableSearchable -> SavableSearchable,
- FavoritesItem -> SavedSearchable,
- FavoritesItemEntity -> SavedSearchableEntity

add type column to searchable table
This commit is contained in:
MM20 2022-10-16 15:53:22 +02:00
parent 7e6a4466ca
commit f88f71e5ee
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
61 changed files with 717 additions and 295 deletions

View File

@ -7,14 +7,13 @@ import android.content.pm.LauncherApps
import android.os.Process
import android.os.UserManager
import androidx.core.content.getSystemService
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import org.json.JSONObject
class LauncherAppSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as LauncherApp
val json = JSONObject()
json.put("package", searchable.`package`)
@ -28,7 +27,7 @@ class LauncherAppSerializer : SearchableSerializer {
}
class LauncherAppDeserializer(val context: Context) : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable? {
override fun deserialize(serialized: String): SavableSearchable? {
val json = JSONObject(serialized)
val launcherApps = context.getSystemService<LauncherApps>()!!
val userManager = context.getSystemService<UserManager>()!!

View File

@ -17,7 +17,7 @@ import de.mm20.launcher2.compat.PackageManagerCompat
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -30,7 +30,7 @@ data class LauncherApp(
val version: String?,
internal val userSerialNumber: Long,
override val labelOverride: String? = null,
) : PinnableSearchable {
) : SavableSearchable {
constructor(context: Context, launcherActivityInfo: LauncherActivityInfo): this(
launcherActivityInfo,

View File

@ -9,18 +9,17 @@ import android.os.Process
import android.os.UserManager
import androidx.core.content.getSystemService
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.LauncherShortcut
import de.mm20.launcher2.search.data.LegacyShortcut
import de.mm20.launcher2.search.Searchable
import org.json.JSONObject
import org.koin.core.component.KoinComponent
class LauncherShortcutSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as LauncherShortcut
return jsonObjectOf(
"packagename" to searchable.launcherShortcut.`package`,
@ -38,7 +37,7 @@ class LauncherShortcutDeserializer(
val context: Context
) : SearchableDeserializer, KoinComponent {
override fun deserialize(serialized: String): PinnableSearchable? {
override fun deserialize(serialized: String): SavableSearchable? {
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
if (!launcherApps.hasShortcutHostPermission()) return null
else {
@ -82,7 +81,7 @@ class LauncherShortcutDeserializer(
}
class LegacyShortcutSerializer: SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as LegacyShortcut
return jsonObjectOf(
"label" to searchable.label,
@ -103,7 +102,7 @@ class LegacyShortcutSerializer: SearchableSerializer {
class LegacyShortcutDeserializer(
val context: Context
): SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable {
override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized)
val label = json.getString("label")
val intent = Intent.parseUri(json.getString("intent"), 0)

View File

@ -7,10 +7,9 @@ import de.mm20.launcher2.appshortcuts.R
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
interface AppShortcut: PinnableSearchable {
interface AppShortcut: SavableSearchable {
val appName: String?

View File

@ -15,7 +15,6 @@ import de.mm20.launcher2.appshortcuts.R
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.PinnableSearchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

View File

@ -5,12 +5,10 @@ import android.content.Intent
import android.content.Intent.ShortcutIconResource
import android.graphics.drawable.AdaptiveIconDrawable
import android.os.Bundle
import android.util.Log
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getDrawableOrNull
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.PinnableSearchable
data class LegacyShortcut(
val intent: Intent,

View File

@ -183,7 +183,7 @@ class BackupManager(
companion object {
private const val BackupFormatMajor = 1
private const val BackupFormatMinor = 2
private const val BackupFormatMinor = 3
internal const val BackupFormat = "$BackupFormatMajor.$BackupFormatMinor"
}
}

View File

@ -7,7 +7,7 @@ import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.ktx.romanize
import java.text.Collator
interface PinnableSearchable : Searchable, Comparable<PinnableSearchable> {
interface SavableSearchable : Searchable, Comparable<SavableSearchable> {
val domain: String
val key: String
@ -16,7 +16,7 @@ interface PinnableSearchable : Searchable, Comparable<PinnableSearchable> {
val labelOverride: String?
get() = null
fun overrideLabel(label: String): PinnableSearchable
fun overrideLabel(label: String): SavableSearchable
fun launch(context: Context, options: Bundle?): Boolean
@ -34,7 +34,7 @@ interface PinnableSearchable : Searchable, Comparable<PinnableSearchable> {
): LauncherIcon? = null
override fun compareTo(other: PinnableSearchable): Int {
override fun compareTo(other: SavableSearchable): Int {
val label1 = labelOverride ?: label
val label2 = other.labelOverride ?: other.label
return Collator.getInstance().apply { strength = Collator.SECONDARY }

View File

@ -1,11 +1,11 @@
package de.mm20.launcher2.search
interface SearchableDeserializer {
fun deserialize(serialized: String): PinnableSearchable?
fun deserialize(serialized: String): SavableSearchable?
}
class NullDeserializer: SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable? {
override fun deserialize(serialized: String): SavableSearchable? {
return null
}

View File

@ -1,12 +1,12 @@
package de.mm20.launcher2.search
interface SearchableSerializer {
fun serialize(searchable: PinnableSearchable): String?
fun serialize(searchable: SavableSearchable): String?
val typePrefix: String
}
class NullSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String? {
override fun serialize(searchable: SavableSearchable): String? {
return null
}

View File

@ -7,16 +7,15 @@ import android.content.pm.PackageManager
import android.provider.CalendarContract
import androidx.core.content.ContextCompat
import androidx.core.database.getStringOrNull
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.Searchable
import org.json.JSONObject
import java.util.*
class CalendarEventSerializer: SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as CalendarEvent
val json = JSONObject()
json.put("id", searchable.id)
@ -28,7 +27,7 @@ class CalendarEventSerializer: SearchableSerializer {
}
class CalendarEventDeserializer(val context: Context): SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable? {
override fun deserialize(serialized: String): SavableSearchable? {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) return null
val json = JSONObject(serialized)
val id = json.getLong("id")

View File

@ -9,8 +9,7 @@ import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TextLayer
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import java.text.SimpleDateFormat
data class CalendarEvent(
@ -25,7 +24,7 @@ data class CalendarEvent(
val description: String,
val calendar: Long,
override val labelOverride: String? = null,
) : PinnableSearchable {
) : SavableSearchable {
override val domain: String = Domain

View File

@ -6,15 +6,14 @@ import android.content.pm.PackageManager
import android.provider.ContactsContract
import androidx.core.content.ContextCompat
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.Contact
import de.mm20.launcher2.search.Searchable
import org.json.JSONObject
class ContactSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as Contact
return jsonObjectOf(
"id" to searchable.id
@ -26,7 +25,7 @@ class ContactSerializer : SearchableSerializer {
}
class ContactDeserializer(val context: Context) : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable? {
override fun deserialize(serialized: String): SavableSearchable? {
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_CONTACTS

View File

@ -11,7 +11,7 @@ import androidx.core.graphics.drawable.toDrawable
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.asBitmap
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -29,7 +29,7 @@ data class Contact(
val whatsapp: Set<ContactInfo>,
val postals: Set<ContactInfo>,
override val labelOverride: String? = null
) : Searchable, PinnableSearchable {
) : Searchable, SavableSearchable {
override val domain: String = Domain
override val key: String

View File

@ -5,7 +5,7 @@ import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.database.entities.CustomAttributeEntity
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -19,24 +19,24 @@ import java.io.File
interface CustomAttributesRepository {
fun search(query: String): Flow<ImmutableList<PinnableSearchable>>
fun search(query: String): Flow<ImmutableList<SavableSearchable>>
fun getCustomIcon(searchable: PinnableSearchable): Flow<CustomIcon?>
fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?)
fun getCustomIcon(searchable: SavableSearchable): Flow<CustomIcon?>
fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?)
fun getCustomLabels(items: List<PinnableSearchable>): Flow<List<CustomLabel>>
fun setCustomLabel(searchable: PinnableSearchable, label: String)
fun clearCustomLabel(searchable: PinnableSearchable)
fun getCustomLabels(items: List<SavableSearchable>): Flow<List<CustomLabel>>
fun setCustomLabel(searchable: SavableSearchable, label: String)
fun clearCustomLabel(searchable: SavableSearchable)
fun setTags(searchable: PinnableSearchable, tags: List<String>)
fun getTags(searchable: PinnableSearchable): Flow<List<String>>
fun setTags(searchable: SavableSearchable, tags: List<String>)
fun getTags(searchable: SavableSearchable): Flow<List<String>>
suspend fun export(toDir: File)
suspend fun import(fromDir: File)
suspend fun getAllTags(startsWith: String? = null): List<String>
fun getItemsForTag(tag: String): Flow<List<PinnableSearchable>>
fun addTag(item: PinnableSearchable, tag: String)
fun getItemsForTag(tag: String): Flow<List<SavableSearchable>>
fun addTag(item: SavableSearchable, tag: String)
suspend fun cleanupDatabase(): Int
}
@ -46,7 +46,7 @@ internal class CustomAttributesRepositoryImpl(
) : CustomAttributesRepository {
private val scope = CoroutineScope(Job() + Dispatchers.Default)
override fun getCustomIcon(searchable: PinnableSearchable): Flow<CustomIcon?> {
override fun getCustomIcon(searchable: SavableSearchable): Flow<CustomIcon?> {
val dao = appDatabase.customAttrsDao()
return dao.getCustomAttribute(searchable.key, CustomAttributeType.Icon.value)
.map {
@ -54,7 +54,7 @@ internal class CustomAttributesRepositoryImpl(
}
}
override fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?) {
override fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?) {
val dao = appDatabase.customAttrsDao()
scope.launch {
dao.clearCustomAttribute(searchable.key, CustomAttributeType.Icon.value)
@ -64,7 +64,7 @@ internal class CustomAttributesRepositoryImpl(
}
}
override fun getCustomLabels(items: List<PinnableSearchable>): Flow<List<CustomLabel>> {
override fun getCustomLabels(items: List<SavableSearchable>): Flow<List<CustomLabel>> {
val dao = appDatabase.customAttrsDao()
return dao.getCustomAttributes(items.map { it.key }, CustomAttributeType.Label.value)
.map { list ->
@ -72,7 +72,7 @@ internal class CustomAttributesRepositoryImpl(
}
}
override fun setCustomLabel(searchable: PinnableSearchable, label: String) {
override fun setCustomLabel(searchable: SavableSearchable, label: String) {
val dao = appDatabase.customAttrsDao()
scope.launch {
favoritesRepository.save(searchable)
@ -88,14 +88,14 @@ internal class CustomAttributesRepositoryImpl(
}
}
override fun clearCustomLabel(searchable: PinnableSearchable) {
override fun clearCustomLabel(searchable: SavableSearchable) {
val dao = appDatabase.customAttrsDao()
scope.launch {
dao.clearCustomAttribute(searchable.key, CustomAttributeType.Label.value)
}
}
override fun setTags(searchable: PinnableSearchable, tags: List<String>) {
override fun setTags(searchable: SavableSearchable, tags: List<String>) {
val dao = appDatabase.customAttrsDao()
scope.launch {
favoritesRepository.save(searchable)
@ -105,7 +105,7 @@ internal class CustomAttributesRepositoryImpl(
}
}
override fun getTags(searchable: PinnableSearchable): Flow<List<String>> {
override fun getTags(searchable: SavableSearchable): Flow<List<String>> {
val dao = appDatabase.customAttrsDao()
return dao.getCustomAttributes(listOf(searchable.key), CustomAttributeType.Tag.value).map {
it.map { it.value }
@ -121,21 +121,21 @@ internal class CustomAttributesRepositoryImpl(
}
}
override fun getItemsForTag(tag: String): Flow<List<PinnableSearchable>> {
override fun getItemsForTag(tag: String): Flow<List<SavableSearchable>> {
val dao = appDatabase.customAttrsDao()
return dao.getItemsWithTag(tag).map {
favoritesRepository.getFromKeys(it)
}
}
override fun addTag(item: PinnableSearchable, tag: String) {
override fun addTag(item: SavableSearchable, tag: String) {
val dao = appDatabase.customAttrsDao()
scope.launch {
dao.addTag(item.key, tag)
}
}
override fun search(query: String): Flow<ImmutableList<PinnableSearchable>> {
override fun search(query: String): Flow<ImmutableList<SavableSearchable>> {
if (query.isBlank()) {
return flow {
emit(persistentListOf())

View File

@ -1,12 +1,12 @@
package de.mm20.launcher2.customattrs.utils
import de.mm20.launcher2.customattrs.CustomAttributesRepository
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
fun <T: PinnableSearchable>Flow<List<T>>.withCustomLabels(
fun <T: SavableSearchable>Flow<List<T>>.withCustomLabels(
customAttributesRepository: CustomAttributesRepository,
): Flow<List<T>> = channelFlow {
this@withCustomLabels.collectLatest { items ->

View File

@ -0,0 +1,456 @@
{
"formatVersion": 1,
"database": {
"version": 18,
"identityHash": "4ae30e68d30e179ef6d30b562bbc3011",
"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, `pinned` INTEGER NOT NULL, `hidden` INTEGER NOT NULL, 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
},
{
"fieldPath": "pinPosition",
"columnName": "pinned",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "hidden",
"columnName": "hidden",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"key"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "Websearch",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`urlTemplate` TEXT NOT NULL, `label` TEXT NOT NULL, `color` INTEGER NOT NULL, `icon` TEXT, `encoding` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"fields": [
{
"fieldPath": "urlTemplate",
"columnName": "urlTemplate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "encoding",
"columnName": "encoding",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"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, `componentName` TEXT, `drawable` TEXT, `iconPack` TEXT NOT NULL, `scale` REAL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"fields": [
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "componentName",
"columnName": "componentName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "drawable",
"columnName": "drawable",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "iconPack",
"columnName": "iconPack",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "scale",
"columnName": "scale",
"affinity": "REAL",
"notNull": false
},
{
"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, 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
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"packageName"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "Widget",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `data` TEXT NOT NULL, `height` INTEGER NOT NULL, `position` INTEGER NOT NULL, `label` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"fields": [
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "data",
"columnName": "data",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "height",
"columnName": "height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"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": []
}
],
"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, '4ae30e68d30e179ef6d30b562bbc3011')"
]
}
}

View File

@ -12,13 +12,13 @@ import androidx.sqlite.db.SupportSQLiteDatabase
import de.mm20.launcher2.database.entities.*
@Database(entities = [ForecastEntity::class,
FavoritesItemEntity::class,
SavedSearchableEntity::class,
WebsearchEntity::class,
CurrencyEntity::class,
IconEntity::class,
IconPackEntity::class,
WidgetEntity::class,
CustomAttributeEntity::class], version = 17, exportSchema = true)
CustomAttributeEntity::class], version = 18, exportSchema = true)
@TypeConverters(ComponentNameConverter::class, StringListConverter::class)
abstract class AppDatabase : RoomDatabase() {
@ -62,6 +62,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration_14_15(),
Migration_15_16(),
Migration_16_17(),
Migration_17_18(),
).build()
if (_instance == null) _instance = instance
return instance
@ -172,5 +173,15 @@ class Migration_16_17 : Migration(16, 17) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Websearch ADD COLUMN encoding INTEGER")
}
}
class Migration_17_18: Migration(17, 18) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Searchable ADD COLUMN type TEXT NOT NULL DEFAULT ''")
database.execSQL("""
UPDATE Searchable
SET type = SUBSTR(`key`, 0, INSTR(`key`, '://')),
searchable = SUBSTR(`searchable`, INSTR(`searchable`, '#') + 1)
""".trimIndent())
}
}

View File

@ -5,7 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import de.mm20.launcher2.database.entities.CustomAttributeEntity
import de.mm20.launcher2.database.entities.FavoritesItemEntity
import de.mm20.launcher2.database.entities.SavedSearchableEntity
import de.mm20.launcher2.database.entities.WebsearchEntity
import de.mm20.launcher2.database.entities.WidgetEntity
@ -16,10 +16,10 @@ interface BackupRestoreDao {
suspend fun wipeFavorites()
@Query("SELECT * FROM Searchable LIMIT :limit OFFSET :offset")
suspend fun exportFavorites(limit: Int, offset: Int): List<FavoritesItemEntity>
suspend fun exportFavorites(limit: Int, offset: Int): List<SavedSearchableEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun importFavorites(items: List<FavoritesItemEntity>)
suspend fun importFavorites(items: List<SavedSearchableEntity>)
@Query("DELETE FROM Widget")
suspend fun wipeWidgets()

View File

@ -1,7 +1,7 @@
package de.mm20.launcher2.database
import androidx.room.*
import de.mm20.launcher2.database.entities.FavoritesItemEntity
import de.mm20.launcher2.database.entities.SavedSearchableEntity
import de.mm20.launcher2.database.entities.WebsearchEntity
import kotlinx.coroutines.flow.Flow
@ -9,16 +9,16 @@ import kotlinx.coroutines.flow.Flow
interface SearchDao {
@Insert()
fun insertAll(items: List<FavoritesItemEntity>)
fun insertAll(items: List<SavedSearchableEntity>)
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertAllSkipExisting(items: List<FavoritesItemEntity>)
fun insertAllSkipExisting(items: List<SavedSearchableEntity>)
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertSkipExisting(items: FavoritesItemEntity)
fun insertSkipExisting(items: SavedSearchableEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllReplaceExisting(items: List<FavoritesItemEntity>)
fun insertAllReplaceExisting(items: List<SavedSearchableEntity>)
@Query("SELECT * FROM Searchable " +
@ -31,7 +31,7 @@ interface SearchDao {
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
limit: Int,
): Flow<List<FavoritesItemEntity>>
): Flow<List<SavedSearchableEntity>>
@Query("SELECT * FROM Searchable " +
"WHERE SUBSTR(`key`, 0, INSTR(`key`, '://')) IN (:includeTypes) AND (" +
@ -45,10 +45,10 @@ interface SearchDao {
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
limit: Int,
): Flow<List<FavoritesItemEntity>>
): Flow<List<SavedSearchableEntity>>
@Query("SELECT * FROM Searchable " +
"WHERE SUBSTR(`key`, 0, INSTR(`key`, '://')) NOT IN (:excludeTypes) AND (" +
"WHERE `type` NOT IN (:excludeTypes) AND (" +
"(:manuallySorted AND pinned > 1) OR " +
"(:automaticallySorted AND pinned = 1) OR" +
"(:frequentlyUsed AND pinned = 0 AND launchCount > 0)" +
@ -59,7 +59,7 @@ interface SearchDao {
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
limit: Int,
): Flow<List<FavoritesItemEntity>>
): Flow<List<SavedSearchableEntity>>
@Query("SELECT `key` FROM Searchable WHERE hidden = 1 AND `key` LIKE 'calendar://%'")
fun getHiddenCalendarEventKeys(): Flow<List<String>>
@ -69,7 +69,7 @@ interface SearchDao {
fun getPinCount(): Int
@Query("SELECT * FROM Searchable WHERE pinned = 0 AND launchCount > 0 AND hidden = 0 AND NOT `key` LIKE 'calendar://%' ORDER BY launchCount DESC LIMIT :count")
fun getAutoFavorites(count: Int): List<FavoritesItemEntity>
fun getAutoFavorites(count: Int): List<SavedSearchableEntity>
@Query("DELETE FROM Searchable WHERE `key` IN (:keys)")
fun deleteAll(keys: List<String>)
@ -79,7 +79,7 @@ interface SearchDao {
fun pinExistingItem(key: String)
@Transaction
fun pinToFavorites(item: FavoritesItemEntity) {
fun pinToFavorites(item: SavedSearchableEntity) {
pinExistingItem(item.key)
insertSkipExisting(item)
}
@ -102,7 +102,7 @@ interface SearchDao {
fun hideExistingItem(key: String)
@Transaction
fun hideItem(item: FavoritesItemEntity) {
fun hideItem(item: SavedSearchableEntity) {
hideExistingItem(item.key)
insertSkipExisting(item)
}
@ -117,7 +117,7 @@ interface SearchDao {
fun getHiddenItemKeys(): Flow<List<String>>
@Query("SELECT * FROM SEARCHABLE WHERE hidden = 1")
fun getHiddenItems(): Flow<List<FavoritesItemEntity>>
fun getHiddenItems(): Flow<List<SavedSearchableEntity>>
@Query("SELECT * FROM Websearch ORDER BY label ASC")
fun getWebSearches(): Flow<List<WebsearchEntity>>
@ -135,22 +135,22 @@ interface SearchDao {
fun incrementExistingLaunchCount(key: String)
@Transaction
fun incrementLaunchCount(item: FavoritesItemEntity) {
fun incrementLaunchCount(item: SavedSearchableEntity) {
incrementExistingLaunchCount(item.key)
insertSkipExisting(item)
}
@Query("SELECT * FROM Searchable WHERE `key` = :key")
fun getFavorite(key: String): FavoritesItemEntity?
fun getFavorite(key: String): SavedSearchableEntity?
@Query("SELECT * FROM Searchable WHERE `key` IN (:keys)")
suspend fun getFromKeys(keys: List<String>): List<FavoritesItemEntity>
suspend fun getFromKeys(keys: List<String>): List<SavedSearchableEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertReplaceExisting(toDatabaseEntity: FavoritesItemEntity)
fun insertReplaceExisting(toDatabaseEntity: SavedSearchableEntity)
@Transaction
fun saveFavorites(favorites: List<FavoritesItemEntity>) {
fun saveFavorites(favorites: List<SavedSearchableEntity>) {
deleteAllFavorites()
insertAll(favorites)
}

View File

@ -5,8 +5,9 @@ import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "Searchable")
data class FavoritesItemEntity(
data class SavedSearchableEntity(
@PrimaryKey val key: String,
val type: String,
@ColumnInfo(name = "searchable") val serializedSearchable: String,
var launchCount: Int,
@ColumnInfo(name = "pinned") var pinPosition: Int,

View File

@ -4,11 +4,10 @@ import android.content.Context
import android.util.Log
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.database.entities.FavoritesItemEntity
import de.mm20.launcher2.database.entities.SavedSearchableEntity
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.data.CalendarEvent
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.json.JSONArray
@ -33,46 +32,46 @@ interface FavoritesRepository {
automaticallySorted: Boolean = false,
frequentlyUsed: Boolean = false,
limit: Int = 100
): Flow<List<PinnableSearchable>>
): Flow<List<SavableSearchable>>
fun getHiddenCalendarEventKeys(): Flow<List<String>>
fun isPinned(searchable: PinnableSearchable): Flow<Boolean>
fun pinItem(searchable: PinnableSearchable)
fun unpinItem(searchable: PinnableSearchable)
fun isHidden(searchable: PinnableSearchable): Flow<Boolean>
fun hideItem(searchable: PinnableSearchable)
fun unhideItem(searchable: PinnableSearchable)
fun incrementLaunchCounter(searchable: PinnableSearchable)
fun isPinned(searchable: SavableSearchable): Flow<Boolean>
fun pinItem(searchable: SavableSearchable)
fun unpinItem(searchable: SavableSearchable)
fun isHidden(searchable: SavableSearchable): Flow<Boolean>
fun hideItem(searchable: SavableSearchable)
fun unhideItem(searchable: SavableSearchable)
fun incrementLaunchCounter(searchable: SavableSearchable)
fun updateFavorites(
manuallySorted: List<PinnableSearchable>,
automaticallySorted: List<PinnableSearchable>,
manuallySorted: List<SavableSearchable>,
automaticallySorted: List<SavableSearchable>,
)
fun getHiddenItems(): Flow<List<PinnableSearchable>>
fun getHiddenItems(): Flow<List<SavableSearchable>>
fun getHiddenItemKeys(): Flow<List<String>>
/**
* Remove this item from the Searchable database
*/
fun remove(searchable: PinnableSearchable)
fun remove(searchable: SavableSearchable)
/**
* Remove this item from favorites and reset launch counter
*/
fun removeFromFavorites(searchable: PinnableSearchable)
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: PinnableSearchable)
fun save(searchable: SavableSearchable)
/**
* Get items with the given keys from the favorites database.
* Items that don't exist in the database will not be returned.
*/
suspend fun getFromKeys(keys: List<String>): List<PinnableSearchable>
suspend fun getFromKeys(keys: List<String>): List<SavableSearchable>
suspend fun export(toDir: File)
suspend fun import(fromDir: File)
@ -99,7 +98,7 @@ internal class FavoritesRepositoryImpl(
automaticallySorted: Boolean,
frequentlyUsed: Boolean,
limit: Int
): Flow<List<PinnableSearchable>> {
): Flow<List<SavableSearchable>> {
val dao = database.searchDao()
val entities = when {
includeTypes == null && excludeTypes == null -> dao.getFavorites(
@ -137,28 +136,28 @@ internal class FavoritesRepositoryImpl(
return database.searchDao().getHiddenCalendarEventKeys()
}
override fun isPinned(searchable: PinnableSearchable): Flow<Boolean> {
override fun isPinned(searchable: SavableSearchable): Flow<Boolean> {
return AppDatabase.getInstance(context).searchDao().isPinned(searchable.key)
}
override fun pinItem(searchable: PinnableSearchable) {
override fun pinItem(searchable: SavableSearchable) {
scope.launch {
withContext(Dispatchers.IO) {
val dao = AppDatabase.getInstance(context).searchDao()
val databaseItem = dao.getFavorite(searchable.key)
val favoritesItem = FavoritesItem(
val savedSearchable = SavedSearchable(
key = searchable.key,
searchable = searchable,
launchCount = databaseItem?.launchCount ?: 0,
pinPosition = 1,
hidden = false
)
favoritesItem.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
savedSearchable.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
}
}
}
override fun unpinItem(searchable: PinnableSearchable) {
override fun unpinItem(searchable: SavableSearchable) {
scope.launch {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().unpinFavorite(searchable.key)
@ -166,28 +165,28 @@ internal class FavoritesRepositoryImpl(
}
}
override fun isHidden(searchable: PinnableSearchable): Flow<Boolean> {
override fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
return AppDatabase.getInstance(context).searchDao().isHidden(searchable.key)
}
override fun hideItem(searchable: PinnableSearchable) {
override fun hideItem(searchable: SavableSearchable) {
scope.launch {
withContext(Dispatchers.IO) {
val dao = AppDatabase.getInstance(context).searchDao()
val databaseItem = dao.getFavorite(searchable.key)
val favoritesItem = FavoritesItem(
val savedSearchable = SavedSearchable(
key = searchable.key,
searchable = searchable,
launchCount = databaseItem?.launchCount ?: 0,
pinPosition = 0,
hidden = true
)
favoritesItem.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
savedSearchable.toDatabaseEntity()?.let { dao.insertReplaceExisting(it) }
}
}
}
override fun unhideItem(searchable: PinnableSearchable) {
override fun unhideItem(searchable: SavableSearchable) {
scope.launch {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().unhideItem(searchable.key)
@ -195,10 +194,10 @@ internal class FavoritesRepositoryImpl(
}
}
override fun incrementLaunchCounter(searchable: PinnableSearchable) {
override fun incrementLaunchCounter(searchable: SavableSearchable) {
scope.launch {
withContext(Dispatchers.IO) {
val item = FavoritesItem(searchable.key, searchable, 0, 0, false)
val item = SavedSearchable(searchable.key, searchable, 0, 0, false)
item.toDatabaseEntity()?.let {
AppDatabase.getInstance(context).searchDao()
.incrementLaunchCount(it)
@ -207,7 +206,7 @@ internal class FavoritesRepositoryImpl(
}
}
override fun getHiddenItems(): Flow<List<PinnableSearchable>> {
override fun getHiddenItems(): Flow<List<SavableSearchable>> {
return database.searchDao().getHiddenItems().map {
it.mapNotNull { fromDatabaseEntity(it).searchable }
}
@ -217,7 +216,7 @@ internal class FavoritesRepositoryImpl(
return database.searchDao().getHiddenItemKeys()
}
override fun remove(searchable: PinnableSearchable) {
override fun remove(searchable: SavableSearchable) {
scope.launch {
withContext(Dispatchers.IO) {
database.searchDao().deleteByKey(searchable.key)
@ -225,16 +224,16 @@ internal class FavoritesRepositoryImpl(
}
}
override fun removeFromFavorites(searchable: PinnableSearchable) {
override fun removeFromFavorites(searchable: SavableSearchable) {
scope.launch {
database.searchDao().resetPinStatusAndLaunchCounter(searchable.key)
}
}
override fun save(searchable: PinnableSearchable) {
override fun save(searchable: SavableSearchable) {
scope.launch {
withContext(Dispatchers.IO) {
val entity = FavoritesItem(
val entity = SavedSearchable(
key = searchable.key,
searchable = searchable,
launchCount = 0,
@ -247,8 +246,8 @@ internal class FavoritesRepositoryImpl(
}
override fun updateFavorites(
manuallySorted: List<PinnableSearchable>,
automaticallySorted: List<PinnableSearchable>
manuallySorted: List<SavableSearchable>,
automaticallySorted: List<SavableSearchable>
) {
val dao = database.searchDao()
scope.launch {
@ -256,7 +255,7 @@ internal class FavoritesRepositoryImpl(
val keys = manuallySorted.map { it.key } + automaticallySorted.map { it.key }
val entities = dao.getFromKeys(keys)
val updatedManuallySorted = manuallySorted.mapIndexedNotNull { index, searchable ->
val entity = entities.find { searchable.key == it.key } ?: FavoritesItem(
val entity = entities.find { searchable.key == it.key } ?: SavedSearchable(
key = searchable.key,
searchable = searchable,
launchCount = 0,
@ -268,7 +267,7 @@ internal class FavoritesRepositoryImpl(
}
val updatedAutomaticallySorted =
automaticallySorted.mapIndexedNotNull { index, searchable ->
val entity = entities.find { searchable.key == it.key } ?: FavoritesItem(
val entity = entities.find { searchable.key == it.key } ?: SavedSearchable(
key = searchable.key,
searchable = searchable,
launchCount = 0,
@ -288,12 +287,12 @@ internal class FavoritesRepositoryImpl(
}
private fun fromDatabaseEntity(entity: FavoritesItemEntity): FavoritesItem {
private fun fromDatabaseEntity(entity: SavedSearchableEntity): SavedSearchable {
val deserializer: SearchableDeserializer =
getDeserializer(context, entity.serializedSearchable)
val searchable = deserializer.deserialize(entity.serializedSearchable.substringAfter("#"))
getDeserializer(context, entity.type)
val searchable = deserializer.deserialize(entity.serializedSearchable)
if (searchable == null) removeInvalidItem(entity.key)
return FavoritesItem(
return SavedSearchable(
key = entity.key,
searchable = searchable,
launchCount = entity.launchCount,
@ -308,7 +307,7 @@ internal class FavoritesRepositoryImpl(
}
}
override suspend fun getFromKeys(keys: List<String>): List<PinnableSearchable> {
override suspend fun getFromKeys(keys: List<String>): List<SavableSearchable> {
val dao = database.searchDao()
return dao.getFromKeys(keys)
.mapNotNull { fromDatabaseEntity(it).searchable }
@ -324,6 +323,7 @@ internal class FavoritesRepositoryImpl(
jsonArray.put(
jsonObjectOf(
"key" to fav.key,
"type" to fav.type,
"hidden" to fav.hidden,
"launchCount" to fav.launchCount,
"pinPosition" to fav.pinPosition,
@ -348,14 +348,15 @@ internal class FavoritesRepositoryImpl(
fromDir.listFiles { _, name -> name.startsWith("favorites.") } ?: return@withContext
for (file in files) {
val favorites = mutableListOf<FavoritesItemEntity>()
val favorites = mutableListOf<SavedSearchableEntity>()
try {
val jsonArray = JSONArray(file.inputStream().reader().readText())
for (i in 0 until jsonArray.length()) {
val json = jsonArray.getJSONObject(i)
val entity = FavoritesItemEntity(
val entity = SavedSearchableEntity(
key = json.getString("key"),
type = json.optString("type").takeIf { it.isNotEmpty() } ?: continue,
serializedSearchable = json.getString("searchable"),
launchCount = json.getInt("launchCount"),
hidden = json.getBoolean("hidden"),

View File

@ -1,30 +1,28 @@
package de.mm20.launcher2.favorites
import de.mm20.launcher2.database.entities.FavoritesItemEntity
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.database.entities.SavedSearchableEntity
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.Searchable
data class FavoritesItem(
data class SavedSearchable(
val key: String,
/**
* null if searchable could not be deserialized (i.e. the app has been uninstalled)
*/
val searchable: PinnableSearchable?,
val searchable: SavableSearchable?,
var launchCount: Int,
var pinPosition: Int,
var hidden: Boolean
) {
private val serializer: SearchableSerializer = getSerializer(searchable)
fun toDatabaseEntity(): FavoritesItemEntity? {
val serializer = serializer
fun toDatabaseEntity(): SavedSearchableEntity? {
val serializer = getSerializer(searchable)
val data = searchable?.let { serializer.serialize(it) } ?: return null
return FavoritesItemEntity(
return SavedSearchableEntity(
key = key,
serializedSearchable = "${serializer.typePrefix}#${data}",
type = searchable.domain,
serializedSearchable = data,
hidden = hidden,
pinPosition = pinPosition,
launchCount = launchCount

View File

@ -65,45 +65,44 @@ internal fun getSerializer(searchable: Searchable?): SearchableSerializer {
return NullSerializer()
}
internal fun getDeserializer(context: Context, serialized: String): SearchableDeserializer {
val type = serialized.substringBefore("#")
if (type == "app") {
internal fun getDeserializer(context: Context, type: String): SearchableDeserializer {
if (type == LauncherApp.Domain) {
return LauncherAppDeserializer(context)
}
if (type == "shortcut") {
if (type == LauncherShortcut.Domain) {
return LauncherShortcutDeserializer(context)
}
if (type == "legacyshortcut") {
if (type == LegacyShortcut.Domain) {
return LegacyShortcutDeserializer(context)
}
if (type == "calendar") {
if (type == CalendarEvent.Domain) {
return CalendarEventDeserializer(context)
}
if (type == "contact") {
if (type == Contact.Domain) {
return ContactDeserializer(context)
}
if (type == "wikipedia") {
if (type == Wikipedia.Domain) {
return WikipediaDeserializer(context)
}
if (type == "gdrive") {
if (type == GDriveFile.Domain) {
return GDriveFileDeserializer()
}
if (type == "onedrive") {
if (type == OneDriveFile.Domain) {
return OneDriveFileDeserializer()
}
if (type == "nextcloud") {
if (type == NextcloudFile.Domain) {
return NextcloudFileDeserializer()
}
if (type == "owncloud") {
if (type == OwncloudFile.Domain) {
return OwncloudFileDeserializer()
}
if (type == "file") {
if (type == LocalFile.Domain) {
return LocalFileDeserializer(context)
}
if (type == "website") {
if (type == Website.Domain) {
return WebsiteDeserializer()
}
if (type == "tag") {
if (type == Tag.Domain) {
return TagDeserializer()
}
return NullDeserializer()

View File

@ -1,14 +1,13 @@
package de.mm20.launcher2.favorites
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.Tag
import org.json.JSONObject
class TagSerializer: SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as Tag
val json = JSONObject()
json.put("tag", searchable.tag)
@ -20,7 +19,7 @@ class TagSerializer: SearchableSerializer {
}
class TagDeserializer: SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable {
override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized)
return Tag(json.getString("tag"))

View File

@ -5,13 +5,12 @@ import android.os.Bundle
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TextLayer
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
data class Tag(
val tag: String,
override val labelOverride: String? = null
): PinnableSearchable {
): SavableSearchable {
override val domain: String = Domain
@ -23,7 +22,7 @@ data class Tag(
override fun launch(context: Context, options: Bundle?): Boolean {
return false
}
override fun overrideLabel(label: String): PinnableSearchable {
override fun overrideLabel(label: String): SavableSearchable {
return this.copy(labelOverride = label)
}

View File

@ -6,8 +6,7 @@ import androidx.core.database.getStringOrNull
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.*
@ -16,7 +15,7 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class LocalFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as LocalFile
return jsonObjectOf(
"id" to searchable.id
@ -30,7 +29,7 @@ class LocalFileSerializer : SearchableSerializer {
class LocalFileDeserializer(
val context: Context
) : SearchableDeserializer, KoinComponent {
override fun deserialize(serialized: String): PinnableSearchable? {
override fun deserialize(serialized: String): SavableSearchable? {
val permissionsManager: PermissionsManager = get()
if (!permissionsManager.checkPermissionOnce(
PermissionGroup.ExternalStorage
@ -75,7 +74,7 @@ class LocalFileDeserializer(
}
class GDriveFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as GDriveFile
return jsonObjectOf(
"id" to searchable.fileId,
@ -104,7 +103,7 @@ class GDriveFileSerializer : SearchableSerializer {
}
class GDriveFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable {
override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized)
val id = json.getString("id")
val label = json.getString("label")
@ -135,7 +134,7 @@ class GDriveFileDeserializer : SearchableDeserializer {
}
class OneDriveFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as OneDriveFile
return jsonObjectOf(
"id" to searchable.fileId,
@ -162,7 +161,7 @@ class OneDriveFileSerializer : SearchableSerializer {
}
class OneDriveFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable {
override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized)
val fileId = json.getString("id")
val label = json.getString("label")
@ -190,7 +189,7 @@ class OneDriveFileDeserializer : SearchableDeserializer {
}
class NextcloudFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as NextcloudFile
return jsonObjectOf(
"id" to searchable.fileId,
@ -217,7 +216,7 @@ class NextcloudFileSerializer : SearchableSerializer {
}
class NextcloudFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable {
override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized)
val id = json.getLong("id")
val label = json.getString("label")
@ -243,7 +242,7 @@ class NextcloudFileDeserializer : SearchableDeserializer {
}
class OwncloudFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as OwncloudFile
return jsonObjectOf(
"id" to searchable.fileId,
@ -270,7 +269,7 @@ class OwncloudFileSerializer : SearchableSerializer {
}
class OwncloudFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable {
override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized)
val id = json.getLong("id")
val label = json.getString("label")

View File

@ -6,11 +6,10 @@ import de.mm20.launcher2.files.R
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import java.util.*
interface File : PinnableSearchable {
interface File : SavableSearchable {
val path: String
val mimeType: String
val size: Long

View File

@ -20,10 +20,8 @@ import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.formatToString
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.media.ThumbnailUtilsCompat
import de.mm20.launcher2.search.PinnableSearchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import java.io.IOException
import java.io.File as JavaIOFile

View File

@ -13,9 +13,8 @@ import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation
import de.mm20.launcher2.icons.transformations.transform
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -99,7 +98,7 @@ class IconRepository(
}
fun getIcon(searchable: PinnableSearchable, size: Int): Flow<LauncherIcon> = channelFlow {
fun getIcon(searchable: SavableSearchable, size: Int): Flow<LauncherIcon> = channelFlow {
iconProviders.collectLatest { providers ->
transformations.collectLatest { transformations ->
customAttributesRepository.getCustomIcon(searchable).collectLatest { customIcon ->
@ -190,7 +189,7 @@ class IconRepository(
}
suspend fun getCustomIconSuggestions(
searchable: PinnableSearchable,
searchable: SavableSearchable,
size: Int
): List<CustomIconWithPreview> {
val suggestions = mutableListOf<CustomIconWithPreview>()
@ -302,7 +301,7 @@ class IconRepository(
}
suspend fun getUncustomizedDefaultIcon(searchable: PinnableSearchable, size: Int): CustomIconWithPreview? {
suspend fun getUncustomizedDefaultIcon(searchable: SavableSearchable, size: Int): CustomIconWithPreview? {
val icon = iconProviders.first().getFirstIcon(searchable, size)
?.transform(transformations.first()) ?: return null
return CustomIconWithPreview(
@ -339,7 +338,7 @@ class IconRepository(
return iconPackIcons + themedIcons
}
fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?) {
fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?) {
customAttributesRepository.setCustomIcon(searchable, icon)
}

View File

@ -6,12 +6,11 @@ import android.content.pm.PackageManager
import de.mm20.launcher2.icons.DynamicCalendarIcon
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
class CalendarIconProvider(val context: Context): IconProvider {
override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
if(searchable !is LauncherApp) return null
val component = ComponentName(searchable.`package`, searchable.activity)
val pm = context.packageManager

View File

@ -4,14 +4,13 @@ import android.content.ComponentName
import de.mm20.launcher2.customattrs.CustomIconPackIcon
import de.mm20.launcher2.icons.IconPackManager
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
class CustomIconPackIconProvider(
private val customIcon: CustomIconPackIcon,
private val iconPackManager: IconPackManager,
) : IconProvider {
override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
return iconPackManager.getIcon(
customIcon.iconPackPackage,
ComponentName.unflattenFromString(customIcon.iconComponentName) ?: return null

View File

@ -3,14 +3,13 @@ package de.mm20.launcher2.icons.providers
import de.mm20.launcher2.customattrs.CustomThemedIcon
import de.mm20.launcher2.icons.IconPackManager
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
class CustomThemedIconProvider(
private val customIcon: CustomThemedIcon,
private val iconPackManager: IconPackManager,
): IconProvider {
override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
return iconPackManager.getThemedIcon(customIcon.iconPackageName)
}
}

View File

@ -8,12 +8,11 @@ import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RotateDrawable
import androidx.core.content.res.ResourcesCompat
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
class GoogleClockIconProvider(val context: Context) : IconProvider {
override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null
if (searchable.`package` != "com.google.android.deskclock") return null
val pm = context.packageManager

View File

@ -3,9 +3,8 @@ package de.mm20.launcher2.icons.providers
import android.content.ComponentName
import android.content.Context
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -14,7 +13,7 @@ class IconPackIconProvider(
private val iconPack: String,
private val iconPackManager: IconPackManager,
): IconProvider {
override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null
val component = ComponentName(searchable.`package`, searchable.activity)

View File

@ -1,15 +1,14 @@
package de.mm20.launcher2.icons.providers
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
interface IconProvider {
suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon?
suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon?
}
internal suspend fun Iterable<IconProvider>.getFirstIcon(
searchable: PinnableSearchable,
searchable: SavableSearchable,
size: Int
): LauncherIcon? {
for (provider in this) {

View File

@ -2,11 +2,10 @@ package de.mm20.launcher2.icons.providers
import android.content.Context
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
class PlaceholderIconProvider(val context: Context) : IconProvider {
override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon {
return searchable.getPlaceholderIcon(context)
}
}

View File

@ -2,14 +2,13 @@ package de.mm20.launcher2.icons.providers
import android.content.Context
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
class SystemIconProvider(
private val context: Context,
private val themedIcons: Boolean,
) : IconProvider {
override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
return searchable.loadIcon(context, size, themedIcons)
}
}

View File

@ -1,15 +1,14 @@
package de.mm20.launcher2.icons.providers
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
internal class ThemedIconProvider(
private val iconPackManager: IconPackManager,
) : IconProvider {
override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null
return iconPackManager.getThemedIcon(searchable.`package`)
}

View File

@ -2,14 +2,13 @@ package de.mm20.launcher2.icons.providers
import android.content.Context
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
internal class ThemedPlaceholderIconProvider(
private val context: Context,
) : IconProvider {
override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon {
val icon = searchable.getPlaceholderIcon(context)
return StaticLauncherIcon(

View File

@ -36,7 +36,6 @@ import de.mm20.launcher2.wikipedia.WikipediaRepository
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.channelFlow
@ -216,7 +215,7 @@ internal class SearchServiceImpl(
}
launch {
results
.map { it.toList().sortedBy { it as? PinnableSearchable }.toImmutableList() }
.map { it.toList().sortedBy { it as? SavableSearchable }.toImmutableList() }
.collectLatest {
send(it)
}
@ -235,7 +234,7 @@ internal data class SearchResults(
val unitConverters: List<UnitConverter> = emptyList(),
val websites: List<Website> = emptyList(),
val wikipedia: List<Wikipedia> = emptyList(),
val other: List<PinnableSearchable> = emptyList(),
val other: List<SavableSearchable> = emptyList(),
) {
fun toList(): List<Searchable> {
return (apps + shortcuts + contacts + calendars + websites + wikipedia + other).distinctBy { it.key } + calculators+ unitConverters

View File

@ -6,7 +6,7 @@ import de.mm20.launcher2.customattrs.CustomAttributesRepository
import de.mm20.launcher2.customattrs.utils.withCustomLabels
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.Tag
import de.mm20.launcher2.widgets.WidgetRepository
import kotlinx.coroutines.flow.*
@ -32,7 +32,7 @@ open class FavoritesVM : ViewModel(), KoinComponent {
it.filterIsInstance<Tag>()
}
val favorites: Flow<List<PinnableSearchable>> = selectedTag.flatMapLatest { tag ->
val favorites: Flow<List<SavableSearchable>> = selectedTag.flatMapLatest { tag ->
if (tag == null) {
val columns = dataStore.data.map { it.grid.columnCount }
val excludeCalendar = widgetRepository.isCalendarWidgetEnabled()

View File

@ -89,8 +89,7 @@ import androidx.compose.ui.unit.toSize
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.MissingPermissionBanner
@ -739,7 +738,7 @@ fun ShortcutPicker(viewModel: EditFavoritesSheetVM) {
}
sealed interface FavoritesSheetGridItem {
class Favorite(val item: PinnableSearchable) : FavoritesSheetGridItem
class Favorite(val item: SavableSearchable) : FavoritesSheetGridItem
class Divider(val section: FavoritesSheetSection) : FavoritesSheetGridItem
class Spacer(val span: Int = 1) : FavoritesSheetGridItem
object EmptySection : FavoritesSheetGridItem

View File

@ -20,7 +20,7 @@ import de.mm20.launcher2.ktx.normalize
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.Tag
@ -48,9 +48,9 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
val createShortcutTarget = MutableLiveData<FavoritesSheetSection?>(null)
private var manuallySorted: MutableList<PinnableSearchable> = mutableListOf()
private var automaticallySorted: MutableList<PinnableSearchable> = mutableListOf()
private var frequentlyUsed: MutableList<PinnableSearchable> = mutableListOf()
private var manuallySorted: MutableList<SavableSearchable> = mutableListOf()
private var automaticallySorted: MutableList<SavableSearchable> = mutableListOf()
private var frequentlyUsed: MutableList<SavableSearchable> = mutableListOf()
val pinnedTags = MutableLiveData<List<Tag>>(emptyList())
val availableTags = MutableLiveData<List<Tag>>(emptyList())
@ -179,7 +179,7 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
)
}
fun getIcon(searchable: PinnableSearchable, size: Int): Flow<LauncherIcon?> {
fun getIcon(searchable: SavableSearchable, size: Int): Flow<LauncherIcon?> {
return iconRepository.getIcon(searchable, size)
}

View File

@ -16,15 +16,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
@Composable
fun HiddenItemsSheet(
items: List<PinnableSearchable>,
items: List<SavableSearchable>,
onDismiss: () -> Unit
) {
val viewModel: HiddenItemsSheetVM = viewModel()

View File

@ -43,8 +43,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner
@ -378,7 +377,7 @@ fun SearchColumn(
}
fun LazyListScope.GridResults(
items: ImmutableList<PinnableSearchable>,
items: ImmutableList<SavableSearchable>,
columns: Int,
reverse: Boolean,
showLabels: Boolean,
@ -443,7 +442,7 @@ fun LazyListScope.GridResults(
@Composable
fun GridRow(
modifier: Modifier = Modifier,
items: ImmutableList<PinnableSearchable>,
items: ImmutableList<SavableSearchable>,
columns: Int,
showLabels: Boolean,
) {
@ -467,7 +466,7 @@ fun GridRow(
}
fun LazyListScope.ListResults(
items: ImmutableList<PinnableSearchable>,
items: ImmutableList<SavableSearchable>,
reverse: Boolean,
key: String,
before: (@Composable () -> Unit)? = null,
@ -520,7 +519,7 @@ fun LazyListScope.ListResults(
@Composable
fun ListRow(
modifier: Modifier = Modifier,
item: PinnableSearchable,
item: SavableSearchable,
) {
Box(
modifier = modifier.padding(

View File

@ -1,17 +1,15 @@
package de.mm20.launcher2.ui.launcher.search
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.customattrs.CustomAttributesRepository
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchService
import de.mm20.launcher2.search.WebsearchRepository
import de.mm20.launcher2.search.data.*
@ -47,7 +45,7 @@ class SearchVM : ViewModel(), KoinComponent {
val unitConverterResults = MutableLiveData<List<UnitConverter>>(emptyList())
val websearchResults = MutableLiveData<List<Websearch>>(emptyList())
val hiddenResults = MutableLiveData<List<PinnableSearchable>>(emptyList())
val hiddenResults = MutableLiveData<List<SavableSearchable>>(emptyList())
val favoritesEnabled = dataStore.data.map { it.favorites.enabled }
val hideFavorites = MutableLiveData(false)
@ -90,7 +88,7 @@ class SearchVM : ViewModel(), KoinComponent {
wikipedia = it.wikipediaSearch,
).collectLatest { results ->
hiddenItemKeys.collectLatest { hiddenKeys ->
val hidden = mutableListOf<PinnableSearchable>()
val hidden = mutableListOf<SavableSearchable>()
val apps = mutableListOf<LauncherApp>()
val workApps = mutableListOf<LauncherApp>()
val shortcuts = mutableListOf<AppShortcut>()
@ -103,7 +101,7 @@ class SearchVM : ViewModel(), KoinComponent {
val website = mutableListOf<Website>()
for (r in results) {
when {
r is PinnableSearchable && hiddenKeys.contains(r.key) -> {
r is SavableSearchable && hiddenKeys.contains(r.key) -> {
hidden.add(r)
}
r is LauncherApp && !r.isMainProfile -> workApps.add(r)

View File

@ -10,7 +10,7 @@ import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.LauncherApp
import kotlinx.coroutines.flow.Flow
@ -18,7 +18,7 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
abstract class SearchableItemVM(
private val searchable: PinnableSearchable
private val searchable: SavableSearchable
) : KoinComponent {
protected val favoritesRepository: FavoritesRepository by inject()
protected val badgeRepository: BadgeRepository by inject()

View File

@ -23,8 +23,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.icons.CustomIconWithPreview
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
@ -36,7 +35,7 @@ import kotlinx.coroutines.launch
@Composable
fun CustomizeSearchableSheet(
searchable: PinnableSearchable,
searchable: SavableSearchable,
onDismiss: () -> Unit,
) {
val viewModel: CustomizeSearchableSheetVM =

View File

@ -7,8 +7,7 @@ import de.mm20.launcher2.customattrs.CustomIcon
import de.mm20.launcher2.icons.CustomIconWithPreview
import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent
@ -16,7 +15,7 @@ import org.koin.core.component.inject
import kotlin.coroutines.coroutineContext
class CustomizeSearchableSheetVM(
private val searchable: PinnableSearchable
private val searchable: SavableSearchable
) : KoinComponent {
private val iconRepository: IconRepository by inject()
private val customAttributesRepository: CustomAttributesRepository by inject()

View File

@ -22,7 +22,7 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.*
import de.mm20.launcher2.ui.component.LauncherCard
@ -45,7 +45,7 @@ import kotlinx.coroutines.delay
@Composable
fun GridItem(modifier: Modifier = Modifier, item: PinnableSearchable, showLabels: Boolean = true) {
fun GridItem(modifier: Modifier = Modifier, item: SavableSearchable, showLabels: Boolean = true) {
val viewModel = remember(item.key) { GridItemVM(item) }
val context = LocalContext.current

View File

@ -1,9 +1,8 @@
package de.mm20.launcher2.ui.launcher.search.common.grid
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
class GridItemVM(
searchable: PinnableSearchable
searchable: SavableSearchable
): SearchableItemVM(searchable)

View File

@ -5,15 +5,14 @@ import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.locals.LocalGridColumns
import kotlin.math.ceil
@Composable
fun SearchResultGrid(
items: List<PinnableSearchable>,
items: List<SavableSearchable>,
modifier: Modifier = Modifier,
showLabels: Boolean = true,
columns: Int = LocalGridColumns.current,

View File

@ -8,8 +8,7 @@ import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.*
import de.mm20.launcher2.ui.component.InnerCard
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItem
@ -18,7 +17,7 @@ import de.mm20.launcher2.ui.launcher.search.files.FileItem
import de.mm20.launcher2.ui.launcher.search.shortcut.AppShortcutItem
@Composable
fun ListItem(modifier: Modifier = Modifier, item: PinnableSearchable) {
fun ListItem(modifier: Modifier = Modifier, item: SavableSearchable) {
var showDetails by remember { mutableStateOf(false) }
val context = LocalContext.current

View File

@ -1,9 +1,8 @@
package de.mm20.launcher2.ui.launcher.search.common.list
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
class ListItemVM(
searchable: PinnableSearchable
searchable: SavableSearchable
): SearchableItemVM(searchable)

View File

@ -8,13 +8,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.layout.BottomReversed
@Composable
fun SearchResultList(
items: List<PinnableSearchable>,
items: List<SavableSearchable>,
modifier: Modifier = Modifier,
reverse: Boolean = false
) {

View File

@ -14,9 +14,8 @@ import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
@ -33,18 +32,18 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
val allApps = appRepository.getAllInstalledApps().map {
withContext(Dispatchers.Default) { it.sorted() }
}.asLiveData()
val hiddenItems: LiveData<List<PinnableSearchable>> = liveData {
val hiddenItems: LiveData<List<SavableSearchable>> = liveData {
val hidden = withContext(Dispatchers.Default) {
favoritesRepository.getHiddenItems().first().filter { it !is LauncherApp }.sorted()
}
emit(hidden)
}
fun isHidden(searchable: PinnableSearchable): Flow<Boolean> {
fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
return favoritesRepository.isHidden(searchable)
}
fun setHidden(searchable: PinnableSearchable, hidden: Boolean) {
fun setHidden(searchable: SavableSearchable, hidden: Boolean) {
if(hidden) {
favoritesRepository.hideItem(searchable)
} else {
@ -52,11 +51,11 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
}
}
fun getIcon(searchable: PinnableSearchable, size: Int): Flow<LauncherIcon> {
fun getIcon(searchable: SavableSearchable, size: Int): Flow<LauncherIcon> {
return iconRepository.getIcon(searchable, size)
}
fun launch(context: Context, searchable: PinnableSearchable) {
fun launch(context: Context, searchable: SavableSearchable) {
val bundle = Bundle()
if (isAtLeastApiLevel(31)) {
bundle.putInt("android.activity.splashScreenStyle", 1)

View File

@ -9,8 +9,7 @@ import coil.imageLoader
import coil.request.ImageRequest
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.websites.R
import java.util.concurrent.ExecutionException
@ -22,7 +21,7 @@ data class Website(
val favicon: String,
val color: Int,
override val labelOverride: String? = null,
) : PinnableSearchable {
) : SavableSearchable {
override val domain: String = Domain

View File

@ -1,15 +1,14 @@
package de.mm20.launcher2.websites
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.Website
import org.json.JSONObject
class WebsiteSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as Website
return jsonObjectOf(
"label" to searchable.label,
@ -26,7 +25,7 @@ class WebsiteSerializer : SearchableSerializer {
}
class WebsiteDeserializer: SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable? {
override fun deserialize(serialized: String): SavableSearchable? {
val json = JSONObject(serialized)
return Website(
label = json.getString("label"),

View File

@ -11,8 +11,7 @@ import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.wikipedia.R
data class Wikipedia(
@ -22,7 +21,7 @@ data class Wikipedia(
val image: String?,
val wikipediaUrl: String,
override val labelOverride: String? = null,
) : PinnableSearchable {
) : SavableSearchable {
override val domain: String = Domain

View File

@ -1,15 +1,14 @@
package de.mm20.launcher2.wikipedia
import android.content.Context
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.Wikipedia
import org.json.JSONObject
class WikipediaSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String {
override fun serialize(searchable: SavableSearchable): String {
searchable as Wikipedia
val json = JSONObject()
json.put("label", searchable.label)
@ -25,7 +24,7 @@ class WikipediaSerializer : SearchableSerializer {
}
class WikipediaDeserializer(val context: Context) : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable? {
override fun deserialize(serialized: String): SavableSearchable? {
val json = JSONObject(serialized)
return Wikipedia(
label = json.getString("label"),