- 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.Process
import android.os.UserManager import android.os.UserManager
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
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
import org.json.JSONObject import org.json.JSONObject
class LauncherAppSerializer : SearchableSerializer { class LauncherAppSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as LauncherApp searchable as LauncherApp
val json = JSONObject() val json = JSONObject()
json.put("package", searchable.`package`) json.put("package", searchable.`package`)
@ -28,7 +27,7 @@ class LauncherAppSerializer : SearchableSerializer {
} }
class LauncherAppDeserializer(val context: Context) : SearchableDeserializer { class LauncherAppDeserializer(val context: Context) : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable? { override fun deserialize(serialized: String): SavableSearchable? {
val json = JSONObject(serialized) val json = JSONObject(serialized)
val launcherApps = context.getSystemService<LauncherApps>()!! val launcherApps = context.getSystemService<LauncherApps>()!!
val userManager = context.getSystemService<UserManager>()!! 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.icons.*
import de.mm20.launcher2.ktx.getSerialNumber import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel 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.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -30,7 +30,7 @@ data class LauncherApp(
val version: String?, val version: String?,
internal val userSerialNumber: Long, internal val userSerialNumber: Long,
override val labelOverride: String? = null, override val labelOverride: String? = null,
) : PinnableSearchable { ) : SavableSearchable {
constructor(context: Context, launcherActivityInfo: LauncherActivityInfo): this( constructor(context: Context, launcherActivityInfo: LauncherActivityInfo): this(
launcherActivityInfo, launcherActivityInfo,

View File

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

View File

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

View File

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

View File

@ -183,7 +183,7 @@ class BackupManager(
companion object { companion object {
private const val BackupFormatMajor = 1 private const val BackupFormatMajor = 1
private const val BackupFormatMinor = 2 private const val BackupFormatMinor = 3
internal const val BackupFormat = "$BackupFormatMajor.$BackupFormatMinor" 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 de.mm20.launcher2.ktx.romanize
import java.text.Collator import java.text.Collator
interface PinnableSearchable : Searchable, Comparable<PinnableSearchable> { interface SavableSearchable : Searchable, Comparable<SavableSearchable> {
val domain: String val domain: String
val key: String val key: String
@ -16,7 +16,7 @@ interface PinnableSearchable : Searchable, Comparable<PinnableSearchable> {
val labelOverride: String? val labelOverride: String?
get() = null get() = null
fun overrideLabel(label: String): PinnableSearchable fun overrideLabel(label: String): SavableSearchable
fun launch(context: Context, options: Bundle?): Boolean fun launch(context: Context, options: Bundle?): Boolean
@ -34,7 +34,7 @@ interface PinnableSearchable : Searchable, Comparable<PinnableSearchable> {
): LauncherIcon? = null ): LauncherIcon? = null
override fun compareTo(other: PinnableSearchable): Int { override fun compareTo(other: SavableSearchable): Int {
val label1 = labelOverride ?: label val label1 = labelOverride ?: label
val label2 = other.labelOverride ?: other.label val label2 = other.labelOverride ?: other.label
return Collator.getInstance().apply { strength = Collator.SECONDARY } return Collator.getInstance().apply { strength = Collator.SECONDARY }

View File

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

View File

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

View File

@ -7,16 +7,15 @@ import android.content.pm.PackageManager
import android.provider.CalendarContract import android.provider.CalendarContract
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.database.getStringOrNull 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.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.CalendarEvent import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.Searchable
import org.json.JSONObject import org.json.JSONObject
import java.util.* import java.util.*
class CalendarEventSerializer: SearchableSerializer { class CalendarEventSerializer: SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as CalendarEvent searchable as CalendarEvent
val json = JSONObject() val json = JSONObject()
json.put("id", searchable.id) json.put("id", searchable.id)
@ -28,7 +27,7 @@ class CalendarEventSerializer: SearchableSerializer {
} }
class CalendarEventDeserializer(val context: Context): SearchableDeserializer { 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 if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) return null
val json = JSONObject(serialized) val json = JSONObject(serialized)
val id = json.getLong("id") 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.StaticLauncherIcon
import de.mm20.launcher2.icons.TextLayer import de.mm20.launcher2.icons.TextLayer
import de.mm20.launcher2.ktx.tryStartActivity 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 java.text.SimpleDateFormat import java.text.SimpleDateFormat
data class CalendarEvent( data class CalendarEvent(
@ -25,7 +24,7 @@ data class CalendarEvent(
val description: String, val description: String,
val calendar: Long, val calendar: Long,
override val labelOverride: String? = null, override val labelOverride: String? = null,
) : PinnableSearchable { ) : SavableSearchable {
override val domain: String = Domain override val domain: String = Domain

View File

@ -6,15 +6,14 @@ import android.content.pm.PackageManager
import android.provider.ContactsContract import android.provider.ContactsContract
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import de.mm20.launcher2.ktx.jsonObjectOf 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.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.Contact import de.mm20.launcher2.search.data.Contact
import de.mm20.launcher2.search.Searchable
import org.json.JSONObject import org.json.JSONObject
class ContactSerializer : SearchableSerializer { class ContactSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as Contact searchable as Contact
return jsonObjectOf( return jsonObjectOf(
"id" to searchable.id "id" to searchable.id
@ -26,7 +25,7 @@ class ContactSerializer : SearchableSerializer {
} }
class ContactDeserializer(val context: Context) : SearchableDeserializer { class ContactDeserializer(val context: Context) : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable? { override fun deserialize(serialized: String): SavableSearchable? {
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(
context, context,
Manifest.permission.READ_CONTACTS 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.icons.*
import de.mm20.launcher2.ktx.asBitmap import de.mm20.launcher2.ktx.asBitmap
import de.mm20.launcher2.ktx.tryStartActivity 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 de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -29,7 +29,7 @@ data class Contact(
val whatsapp: Set<ContactInfo>, val whatsapp: Set<ContactInfo>,
val postals: Set<ContactInfo>, val postals: Set<ContactInfo>,
override val labelOverride: String? = null override val labelOverride: String? = null
) : Searchable, PinnableSearchable { ) : Searchable, SavableSearchable {
override val domain: String = Domain override val domain: String = Domain
override val key: String 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.database.entities.CustomAttributeEntity
import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.ktx.jsonObjectOf 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.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@ -19,24 +19,24 @@ import java.io.File
interface CustomAttributesRepository { interface CustomAttributesRepository {
fun search(query: String): Flow<ImmutableList<PinnableSearchable>> fun search(query: String): Flow<ImmutableList<SavableSearchable>>
fun getCustomIcon(searchable: PinnableSearchable): Flow<CustomIcon?> fun getCustomIcon(searchable: SavableSearchable): Flow<CustomIcon?>
fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?) fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?)
fun getCustomLabels(items: List<PinnableSearchable>): Flow<List<CustomLabel>> fun getCustomLabels(items: List<SavableSearchable>): Flow<List<CustomLabel>>
fun setCustomLabel(searchable: PinnableSearchable, label: String) fun setCustomLabel(searchable: SavableSearchable, label: String)
fun clearCustomLabel(searchable: PinnableSearchable) fun clearCustomLabel(searchable: SavableSearchable)
fun setTags(searchable: PinnableSearchable, tags: List<String>) fun setTags(searchable: SavableSearchable, tags: List<String>)
fun getTags(searchable: PinnableSearchable): Flow<List<String>> fun getTags(searchable: SavableSearchable): Flow<List<String>>
suspend fun export(toDir: File) suspend fun export(toDir: File)
suspend fun import(fromDir: File) suspend fun import(fromDir: File)
suspend fun getAllTags(startsWith: String? = null): List<String> suspend fun getAllTags(startsWith: String? = null): List<String>
fun getItemsForTag(tag: String): Flow<List<PinnableSearchable>> fun getItemsForTag(tag: String): Flow<List<SavableSearchable>>
fun addTag(item: PinnableSearchable, tag: String) fun addTag(item: SavableSearchable, tag: String)
suspend fun cleanupDatabase(): Int suspend fun cleanupDatabase(): Int
} }
@ -46,7 +46,7 @@ internal class CustomAttributesRepositoryImpl(
) : CustomAttributesRepository { ) : CustomAttributesRepository {
private val scope = CoroutineScope(Job() + Dispatchers.Default) 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() val dao = appDatabase.customAttrsDao()
return dao.getCustomAttribute(searchable.key, CustomAttributeType.Icon.value) return dao.getCustomAttribute(searchable.key, CustomAttributeType.Icon.value)
.map { .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() val dao = appDatabase.customAttrsDao()
scope.launch { scope.launch {
dao.clearCustomAttribute(searchable.key, CustomAttributeType.Icon.value) 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() val dao = appDatabase.customAttrsDao()
return dao.getCustomAttributes(items.map { it.key }, CustomAttributeType.Label.value) return dao.getCustomAttributes(items.map { it.key }, CustomAttributeType.Label.value)
.map { list -> .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() val dao = appDatabase.customAttrsDao()
scope.launch { scope.launch {
favoritesRepository.save(searchable) favoritesRepository.save(searchable)
@ -88,14 +88,14 @@ internal class CustomAttributesRepositoryImpl(
} }
} }
override fun clearCustomLabel(searchable: PinnableSearchable) { override fun clearCustomLabel(searchable: SavableSearchable) {
val dao = appDatabase.customAttrsDao() val dao = appDatabase.customAttrsDao()
scope.launch { scope.launch {
dao.clearCustomAttribute(searchable.key, CustomAttributeType.Label.value) 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() val dao = appDatabase.customAttrsDao()
scope.launch { scope.launch {
favoritesRepository.save(searchable) 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() val dao = appDatabase.customAttrsDao()
return dao.getCustomAttributes(listOf(searchable.key), CustomAttributeType.Tag.value).map { return dao.getCustomAttributes(listOf(searchable.key), CustomAttributeType.Tag.value).map {
it.map { it.value } 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() val dao = appDatabase.customAttrsDao()
return dao.getItemsWithTag(tag).map { return dao.getItemsWithTag(tag).map {
favoritesRepository.getFromKeys(it) favoritesRepository.getFromKeys(it)
} }
} }
override fun addTag(item: PinnableSearchable, tag: String) { override fun addTag(item: SavableSearchable, tag: String) {
val dao = appDatabase.customAttrsDao() val dao = appDatabase.customAttrsDao()
scope.launch { scope.launch {
dao.addTag(item.key, tag) dao.addTag(item.key, tag)
} }
} }
override fun search(query: String): Flow<ImmutableList<PinnableSearchable>> { override fun search(query: String): Flow<ImmutableList<SavableSearchable>> {
if (query.isBlank()) { if (query.isBlank()) {
return flow { return flow {
emit(persistentListOf()) emit(persistentListOf())

View File

@ -1,12 +1,12 @@
package de.mm20.launcher2.customattrs.utils package de.mm20.launcher2.customattrs.utils
import de.mm20.launcher2.customattrs.CustomAttributesRepository 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.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
fun <T: PinnableSearchable>Flow<List<T>>.withCustomLabels( fun <T: SavableSearchable>Flow<List<T>>.withCustomLabels(
customAttributesRepository: CustomAttributesRepository, customAttributesRepository: CustomAttributesRepository,
): Flow<List<T>> = channelFlow { ): Flow<List<T>> = channelFlow {
this@withCustomLabels.collectLatest { items -> 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.* import de.mm20.launcher2.database.entities.*
@Database(entities = [ForecastEntity::class, @Database(entities = [ForecastEntity::class,
FavoritesItemEntity::class, SavedSearchableEntity::class,
WebsearchEntity::class, WebsearchEntity::class,
CurrencyEntity::class, CurrencyEntity::class,
IconEntity::class, IconEntity::class,
IconPackEntity::class, IconPackEntity::class,
WidgetEntity::class, WidgetEntity::class,
CustomAttributeEntity::class], version = 17, exportSchema = true) CustomAttributeEntity::class], version = 18, exportSchema = true)
@TypeConverters(ComponentNameConverter::class, StringListConverter::class) @TypeConverters(ComponentNameConverter::class, StringListConverter::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
@ -62,6 +62,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration_14_15(), Migration_14_15(),
Migration_15_16(), Migration_15_16(),
Migration_16_17(), Migration_16_17(),
Migration_17_18(),
).build() ).build()
if (_instance == null) _instance = instance if (_instance == null) _instance = instance
return instance return instance
@ -172,5 +173,15 @@ class Migration_16_17 : Migration(16, 17) {
override fun migrate(database: SupportSQLiteDatabase) { override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Websearch ADD COLUMN encoding INTEGER") 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.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import de.mm20.launcher2.database.entities.CustomAttributeEntity 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.WebsearchEntity
import de.mm20.launcher2.database.entities.WidgetEntity import de.mm20.launcher2.database.entities.WidgetEntity
@ -16,10 +16,10 @@ interface BackupRestoreDao {
suspend fun wipeFavorites() suspend fun wipeFavorites()
@Query("SELECT * FROM Searchable LIMIT :limit OFFSET :offset") @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) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun importFavorites(items: List<FavoritesItemEntity>) suspend fun importFavorites(items: List<SavedSearchableEntity>)
@Query("DELETE FROM Widget") @Query("DELETE FROM Widget")
suspend fun wipeWidgets() suspend fun wipeWidgets()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,13 @@
package de.mm20.launcher2.favorites 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.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.Tag import de.mm20.launcher2.search.data.Tag
import org.json.JSONObject import org.json.JSONObject
class TagSerializer: SearchableSerializer { class TagSerializer: SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as Tag searchable as Tag
val json = JSONObject() val json = JSONObject()
json.put("tag", searchable.tag) json.put("tag", searchable.tag)
@ -20,7 +19,7 @@ class TagSerializer: SearchableSerializer {
} }
class TagDeserializer: SearchableDeserializer { class TagDeserializer: SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable { override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized) val json = JSONObject(serialized)
return Tag(json.getString("tag")) 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.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TextLayer import de.mm20.launcher2.icons.TextLayer
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
data class Tag( data class Tag(
val tag: String, val tag: String,
override val labelOverride: String? = null override val labelOverride: String? = null
): PinnableSearchable { ): SavableSearchable {
override val domain: String = Domain override val domain: String = Domain
@ -23,7 +22,7 @@ data class Tag(
override fun launch(context: Context, options: Bundle?): Boolean { override fun launch(context: Context, options: Bundle?): Boolean {
return false return false
} }
override fun overrideLabel(label: String): PinnableSearchable { override fun overrideLabel(label: String): SavableSearchable {
return this.copy(labelOverride = label) 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.ktx.jsonObjectOf
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.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
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
import de.mm20.launcher2.search.data.* import de.mm20.launcher2.search.data.*
@ -16,7 +15,7 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
class LocalFileSerializer : SearchableSerializer { class LocalFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as LocalFile searchable as LocalFile
return jsonObjectOf( return jsonObjectOf(
"id" to searchable.id "id" to searchable.id
@ -30,7 +29,7 @@ class LocalFileSerializer : SearchableSerializer {
class LocalFileDeserializer( class LocalFileDeserializer(
val context: Context val context: Context
) : SearchableDeserializer, KoinComponent { ) : SearchableDeserializer, KoinComponent {
override fun deserialize(serialized: String): PinnableSearchable? { override fun deserialize(serialized: String): SavableSearchable? {
val permissionsManager: PermissionsManager = get() val permissionsManager: PermissionsManager = get()
if (!permissionsManager.checkPermissionOnce( if (!permissionsManager.checkPermissionOnce(
PermissionGroup.ExternalStorage PermissionGroup.ExternalStorage
@ -75,7 +74,7 @@ class LocalFileDeserializer(
} }
class GDriveFileSerializer : SearchableSerializer { class GDriveFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as GDriveFile searchable as GDriveFile
return jsonObjectOf( return jsonObjectOf(
"id" to searchable.fileId, "id" to searchable.fileId,
@ -104,7 +103,7 @@ class GDriveFileSerializer : SearchableSerializer {
} }
class GDriveFileDeserializer : SearchableDeserializer { class GDriveFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable { override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized) val json = JSONObject(serialized)
val id = json.getString("id") val id = json.getString("id")
val label = json.getString("label") val label = json.getString("label")
@ -135,7 +134,7 @@ class GDriveFileDeserializer : SearchableDeserializer {
} }
class OneDriveFileSerializer : SearchableSerializer { class OneDriveFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as OneDriveFile searchable as OneDriveFile
return jsonObjectOf( return jsonObjectOf(
"id" to searchable.fileId, "id" to searchable.fileId,
@ -162,7 +161,7 @@ class OneDriveFileSerializer : SearchableSerializer {
} }
class OneDriveFileDeserializer : SearchableDeserializer { class OneDriveFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable { override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized) val json = JSONObject(serialized)
val fileId = json.getString("id") val fileId = json.getString("id")
val label = json.getString("label") val label = json.getString("label")
@ -190,7 +189,7 @@ class OneDriveFileDeserializer : SearchableDeserializer {
} }
class NextcloudFileSerializer : SearchableSerializer { class NextcloudFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as NextcloudFile searchable as NextcloudFile
return jsonObjectOf( return jsonObjectOf(
"id" to searchable.fileId, "id" to searchable.fileId,
@ -217,7 +216,7 @@ class NextcloudFileSerializer : SearchableSerializer {
} }
class NextcloudFileDeserializer : SearchableDeserializer { class NextcloudFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable { override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized) val json = JSONObject(serialized)
val id = json.getLong("id") val id = json.getLong("id")
val label = json.getString("label") val label = json.getString("label")
@ -243,7 +242,7 @@ class NextcloudFileDeserializer : SearchableDeserializer {
} }
class OwncloudFileSerializer : SearchableSerializer { class OwncloudFileSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as OwncloudFile searchable as OwncloudFile
return jsonObjectOf( return jsonObjectOf(
"id" to searchable.fileId, "id" to searchable.fileId,
@ -270,7 +269,7 @@ class OwncloudFileSerializer : SearchableSerializer {
} }
class OwncloudFileDeserializer : SearchableDeserializer { class OwncloudFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable { override fun deserialize(serialized: String): SavableSearchable {
val json = JSONObject(serialized) val json = JSONObject(serialized)
val id = json.getLong("id") val id = json.getLong("id")
val label = json.getString("label") 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.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import java.util.* import java.util.*
interface File : PinnableSearchable { interface File : SavableSearchable {
val path: String val path: String
val mimeType: String val mimeType: String
val size: Long 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.formatToString
import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.media.ThumbnailUtilsCompat import de.mm20.launcher2.media.ThumbnailUtilsCompat
import de.mm20.launcher2.search.PinnableSearchable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import java.io.IOException import java.io.IOException
import java.io.File as JavaIOFile 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.LegacyToAdaptiveTransformation
import de.mm20.launcher2.icons.transformations.transform import de.mm20.launcher2.icons.transformations.transform
import de.mm20.launcher2.preferences.LauncherDataStore 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.data.LauncherApp
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job 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 -> iconProviders.collectLatest { providers ->
transformations.collectLatest { transformations -> transformations.collectLatest { transformations ->
customAttributesRepository.getCustomIcon(searchable).collectLatest { customIcon -> customAttributesRepository.getCustomIcon(searchable).collectLatest { customIcon ->
@ -190,7 +189,7 @@ class IconRepository(
} }
suspend fun getCustomIconSuggestions( suspend fun getCustomIconSuggestions(
searchable: PinnableSearchable, searchable: SavableSearchable,
size: Int size: Int
): List<CustomIconWithPreview> { ): List<CustomIconWithPreview> {
val suggestions = mutableListOf<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) val icon = iconProviders.first().getFirstIcon(searchable, size)
?.transform(transformations.first()) ?: return null ?.transform(transformations.first()) ?: return null
return CustomIconWithPreview( return CustomIconWithPreview(
@ -339,7 +338,7 @@ class IconRepository(
return iconPackIcons + themedIcons return iconPackIcons + themedIcons
} }
fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?) { fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?) {
customAttributesRepository.setCustomIcon(searchable, icon) 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.DynamicCalendarIcon
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherApp
class CalendarIconProvider(val context: Context): IconProvider { 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 if(searchable !is LauncherApp) return null
val component = ComponentName(searchable.`package`, searchable.activity) val component = ComponentName(searchable.`package`, searchable.activity)
val pm = context.packageManager val pm = context.packageManager

View File

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

View File

@ -8,12 +8,11 @@ import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RotateDrawable import android.graphics.drawable.RotateDrawable
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import de.mm20.launcher2.icons.* import de.mm20.launcher2.icons.*
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherApp
class GoogleClockIconProvider(val context: Context) : IconProvider { 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 !is LauncherApp) return null
if (searchable.`package` != "com.google.android.deskclock") return null if (searchable.`package` != "com.google.android.deskclock") return null
val pm = context.packageManager val pm = context.packageManager

View File

@ -3,9 +3,8 @@ package de.mm20.launcher2.icons.providers
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import de.mm20.launcher2.icons.* 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.data.LauncherApp
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -14,7 +13,7 @@ class IconPackIconProvider(
private val iconPack: String, private val iconPack: String,
private val iconPackManager: IconPackManager, private val iconPackManager: IconPackManager,
): IconProvider { ): 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 !is LauncherApp) return null
val component = ComponentName(searchable.`package`, searchable.activity) val component = ComponentName(searchable.`package`, searchable.activity)

View File

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

View File

@ -2,11 +2,10 @@ package de.mm20.launcher2.icons.providers
import android.content.Context import android.content.Context
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
class PlaceholderIconProvider(val context: Context) : IconProvider { 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) return searchable.getPlaceholderIcon(context)
} }
} }

View File

@ -2,14 +2,13 @@ package de.mm20.launcher2.icons.providers
import android.content.Context import android.content.Context
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
class SystemIconProvider( class SystemIconProvider(
private val context: Context, private val context: Context,
private val themedIcons: Boolean, private val themedIcons: Boolean,
) : IconProvider { ) : 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) return searchable.loadIcon(context, size, themedIcons)
} }
} }

View File

@ -1,15 +1,14 @@
package de.mm20.launcher2.icons.providers package de.mm20.launcher2.icons.providers
import de.mm20.launcher2.icons.* import de.mm20.launcher2.icons.*
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherApp
internal class ThemedIconProvider( internal class ThemedIconProvider(
private val iconPackManager: IconPackManager, private val iconPackManager: IconPackManager,
) : IconProvider { ) : 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 !is LauncherApp) return null
return iconPackManager.getThemedIcon(searchable.`package`) return iconPackManager.getThemedIcon(searchable.`package`)
} }

View File

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

View File

@ -36,7 +36,6 @@ import de.mm20.launcher2.wikipedia.WikipediaRepository
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
@ -216,7 +215,7 @@ internal class SearchServiceImpl(
} }
launch { launch {
results results
.map { it.toList().sortedBy { it as? PinnableSearchable }.toImmutableList() } .map { it.toList().sortedBy { it as? SavableSearchable }.toImmutableList() }
.collectLatest { .collectLatest {
send(it) send(it)
} }
@ -235,7 +234,7 @@ internal data class SearchResults(
val unitConverters: List<UnitConverter> = emptyList(), val unitConverters: List<UnitConverter> = emptyList(),
val websites: List<Website> = emptyList(), val websites: List<Website> = emptyList(),
val wikipedia: List<Wikipedia> = emptyList(), val wikipedia: List<Wikipedia> = emptyList(),
val other: List<PinnableSearchable> = emptyList(), val other: List<SavableSearchable> = emptyList(),
) { ) {
fun toList(): List<Searchable> { fun toList(): List<Searchable> {
return (apps + shortcuts + contacts + calendars + websites + wikipedia + other).distinctBy { it.key } + calculators+ unitConverters 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.customattrs.utils.withCustomLabels
import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.Tag import de.mm20.launcher2.search.data.Tag
import de.mm20.launcher2.widgets.WidgetRepository import de.mm20.launcher2.widgets.WidgetRepository
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -32,7 +32,7 @@ open class FavoritesVM : ViewModel(), KoinComponent {
it.filterIsInstance<Tag>() it.filterIsInstance<Tag>()
} }
val favorites: Flow<List<PinnableSearchable>> = selectedTag.flatMapLatest { tag -> val favorites: Flow<List<SavableSearchable>> = selectedTag.flatMapLatest { tag ->
if (tag == null) { if (tag == null) {
val columns = dataStore.data.map { it.grid.columnCount } val columns = dataStore.data.map { it.grid.columnCount }
val excludeCalendar = widgetRepository.isCalendarWidgetEnabled() val excludeCalendar = widgetRepository.isCalendarWidgetEnabled()

View File

@ -89,8 +89,7 @@ import androidx.compose.ui.unit.toSize
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.MissingPermissionBanner import de.mm20.launcher2.ui.component.MissingPermissionBanner
@ -739,7 +738,7 @@ fun ShortcutPicker(viewModel: EditFavoritesSheetVM) {
} }
sealed interface FavoritesSheetGridItem { sealed interface FavoritesSheetGridItem {
class Favorite(val item: PinnableSearchable) : FavoritesSheetGridItem class Favorite(val item: SavableSearchable) : FavoritesSheetGridItem
class Divider(val section: FavoritesSheetSection) : FavoritesSheetGridItem class Divider(val section: FavoritesSheetSection) : FavoritesSheetGridItem
class Spacer(val span: Int = 1) : FavoritesSheetGridItem class Spacer(val span: Int = 1) : FavoritesSheetGridItem
object EmptySection : 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.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.PinnableSearchable 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
@ -48,9 +48,9 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
val createShortcutTarget = MutableLiveData<FavoritesSheetSection?>(null) val createShortcutTarget = MutableLiveData<FavoritesSheetSection?>(null)
private var manuallySorted: MutableList<PinnableSearchable> = mutableListOf() private var manuallySorted: MutableList<SavableSearchable> = mutableListOf()
private var automaticallySorted: MutableList<PinnableSearchable> = mutableListOf() private var automaticallySorted: MutableList<SavableSearchable> = mutableListOf()
private var frequentlyUsed: MutableList<PinnableSearchable> = mutableListOf() private var frequentlyUsed: MutableList<SavableSearchable> = mutableListOf()
val pinnedTags = MutableLiveData<List<Tag>>(emptyList()) val pinnedTags = MutableLiveData<List<Tag>>(emptyList())
val availableTags = 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) 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
@Composable @Composable
fun HiddenItemsSheet( fun HiddenItemsSheet(
items: List<PinnableSearchable>, items: List<SavableSearchable>,
onDismiss: () -> Unit onDismiss: () -> Unit
) { ) {
val viewModel: HiddenItemsSheetVM = viewModel() 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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.LauncherCard import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.component.MissingPermissionBanner import de.mm20.launcher2.ui.component.MissingPermissionBanner
@ -378,7 +377,7 @@ fun SearchColumn(
} }
fun LazyListScope.GridResults( fun LazyListScope.GridResults(
items: ImmutableList<PinnableSearchable>, items: ImmutableList<SavableSearchable>,
columns: Int, columns: Int,
reverse: Boolean, reverse: Boolean,
showLabels: Boolean, showLabels: Boolean,
@ -443,7 +442,7 @@ fun LazyListScope.GridResults(
@Composable @Composable
fun GridRow( fun GridRow(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
items: ImmutableList<PinnableSearchable>, items: ImmutableList<SavableSearchable>,
columns: Int, columns: Int,
showLabels: Boolean, showLabels: Boolean,
) { ) {
@ -467,7 +466,7 @@ fun GridRow(
} }
fun LazyListScope.ListResults( fun LazyListScope.ListResults(
items: ImmutableList<PinnableSearchable>, items: ImmutableList<SavableSearchable>,
reverse: Boolean, reverse: Boolean,
key: String, key: String,
before: (@Composable () -> Unit)? = null, before: (@Composable () -> Unit)? = null,
@ -520,7 +519,7 @@ fun LazyListScope.ListResults(
@Composable @Composable
fun ListRow( fun ListRow(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
item: PinnableSearchable, item: SavableSearchable,
) { ) {
Box( Box(
modifier = modifier.padding( modifier = modifier.padding(

View File

@ -1,17 +1,15 @@
package de.mm20.launcher2.ui.launcher.search package de.mm20.launcher2.ui.launcher.search
import android.util.Log
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData 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.customattrs.CustomAttributesRepository
import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.favorites.FavoritesRepository
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.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchService import de.mm20.launcher2.search.SearchService
import de.mm20.launcher2.search.WebsearchRepository import de.mm20.launcher2.search.WebsearchRepository
import de.mm20.launcher2.search.data.* import de.mm20.launcher2.search.data.*
@ -47,7 +45,7 @@ class SearchVM : ViewModel(), KoinComponent {
val unitConverterResults = MutableLiveData<List<UnitConverter>>(emptyList()) val unitConverterResults = MutableLiveData<List<UnitConverter>>(emptyList())
val websearchResults = MutableLiveData<List<Websearch>>(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 favoritesEnabled = dataStore.data.map { it.favorites.enabled }
val hideFavorites = MutableLiveData(false) val hideFavorites = MutableLiveData(false)
@ -90,7 +88,7 @@ class SearchVM : ViewModel(), KoinComponent {
wikipedia = it.wikipediaSearch, wikipedia = it.wikipediaSearch,
).collectLatest { results -> ).collectLatest { results ->
hiddenItemKeys.collectLatest { hiddenKeys -> hiddenItemKeys.collectLatest { hiddenKeys ->
val hidden = mutableListOf<PinnableSearchable>() val hidden = mutableListOf<SavableSearchable>()
val apps = mutableListOf<LauncherApp>() val apps = mutableListOf<LauncherApp>()
val workApps = mutableListOf<LauncherApp>() val workApps = mutableListOf<LauncherApp>()
val shortcuts = mutableListOf<AppShortcut>() val shortcuts = mutableListOf<AppShortcut>()
@ -103,7 +101,7 @@ class SearchVM : ViewModel(), KoinComponent {
val website = mutableListOf<Website>() val website = mutableListOf<Website>()
for (r in results) { for (r in results) {
when { when {
r is PinnableSearchable && hiddenKeys.contains(r.key) -> { r is SavableSearchable && hiddenKeys.contains(r.key) -> {
hidden.add(r) hidden.add(r)
} }
r is LauncherApp && !r.isMainProfile -> workApps.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.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
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.AppShortcut
import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherApp
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -18,7 +18,7 @@ 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: PinnableSearchable private val searchable: SavableSearchable
) : KoinComponent { ) : KoinComponent {
protected val favoritesRepository: FavoritesRepository by inject() protected val favoritesRepository: FavoritesRepository by inject()
protected val badgeRepository: BadgeRepository 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 androidx.compose.ui.unit.dp
import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.icons.CustomIconWithPreview import de.mm20.launcher2.icons.CustomIconWithPreview
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.ShapedLauncherIcon import de.mm20.launcher2.ui.component.ShapedLauncherIcon
@ -36,7 +35,7 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun CustomizeSearchableSheet( fun CustomizeSearchableSheet(
searchable: PinnableSearchable, searchable: SavableSearchable,
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
val viewModel: CustomizeSearchableSheetVM = 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.CustomIconWithPreview
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.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -16,7 +15,7 @@ import org.koin.core.component.inject
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
class CustomizeSearchableSheetVM( class CustomizeSearchableSheetVM(
private val searchable: PinnableSearchable private val searchable: SavableSearchable
) : KoinComponent { ) : KoinComponent {
private val iconRepository: IconRepository by inject() private val iconRepository: IconRepository by inject()
private val customAttributesRepository: CustomAttributesRepository 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.unit.dp
import androidx.compose.ui.window.Popup import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties 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.Searchable
import de.mm20.launcher2.search.data.* import de.mm20.launcher2.search.data.*
import de.mm20.launcher2.ui.component.LauncherCard import de.mm20.launcher2.ui.component.LauncherCard
@ -45,7 +45,7 @@ import kotlinx.coroutines.delay
@Composable @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 viewModel = remember(item.key) { GridItemVM(item) }
val context = LocalContext.current val context = LocalContext.current

View File

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

View File

@ -5,15 +5,14 @@ import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.ui.layout.BottomReversed import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.locals.LocalGridColumns import de.mm20.launcher2.ui.locals.LocalGridColumns
import kotlin.math.ceil import kotlin.math.ceil
@Composable @Composable
fun SearchResultGrid( fun SearchResultGrid(
items: List<PinnableSearchable>, items: List<SavableSearchable>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showLabels: Boolean = true, showLabels: Boolean = true,
columns: Int = LocalGridColumns.current, 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.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
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.search.data.*
import de.mm20.launcher2.ui.component.InnerCard import de.mm20.launcher2.ui.component.InnerCard
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItem 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 import de.mm20.launcher2.ui.launcher.search.shortcut.AppShortcutItem
@Composable @Composable
fun ListItem(modifier: Modifier = Modifier, item: PinnableSearchable) { fun ListItem(modifier: Modifier = Modifier, item: SavableSearchable) {
var showDetails by remember { mutableStateOf(false) } var showDetails by remember { mutableStateOf(false) }
val context = LocalContext.current val context = LocalContext.current

View File

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

View File

@ -8,13 +8,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.key import androidx.compose.runtime.key
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.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.ui.layout.BottomReversed import de.mm20.launcher2.ui.layout.BottomReversed
@Composable @Composable
fun SearchResultList( fun SearchResultList(
items: List<PinnableSearchable>, items: List<SavableSearchable>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
reverse: Boolean = false 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.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
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ -33,18 +32,18 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent {
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<PinnableSearchable>> = liveData { val hiddenItems: LiveData<List<SavableSearchable>> = liveData {
val hidden = withContext(Dispatchers.Default) { val hidden = withContext(Dispatchers.Default) {
favoritesRepository.getHiddenItems().first().filter { it !is LauncherApp }.sorted() favoritesRepository.getHiddenItems().first().filter { it !is LauncherApp }.sorted()
} }
emit(hidden) emit(hidden)
} }
fun isHidden(searchable: PinnableSearchable): Flow<Boolean> { fun isHidden(searchable: SavableSearchable): Flow<Boolean> {
return favoritesRepository.isHidden(searchable) return favoritesRepository.isHidden(searchable)
} }
fun setHidden(searchable: PinnableSearchable, hidden: Boolean) { fun setHidden(searchable: SavableSearchable, hidden: Boolean) {
if(hidden) { if(hidden) {
favoritesRepository.hideItem(searchable) favoritesRepository.hideItem(searchable)
} else { } 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) return iconRepository.getIcon(searchable, size)
} }
fun launch(context: Context, searchable: PinnableSearchable) { fun launch(context: Context, searchable: SavableSearchable) {
val bundle = Bundle() val bundle = Bundle()
if (isAtLeastApiLevel(31)) { if (isAtLeastApiLevel(31)) {
bundle.putInt("android.activity.splashScreenStyle", 1) bundle.putInt("android.activity.splashScreenStyle", 1)

View File

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

View File

@ -1,15 +1,14 @@
package de.mm20.launcher2.websites package de.mm20.launcher2.websites
import de.mm20.launcher2.ktx.jsonObjectOf 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.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.Website import de.mm20.launcher2.search.data.Website
import org.json.JSONObject import org.json.JSONObject
class WebsiteSerializer : SearchableSerializer { class WebsiteSerializer : SearchableSerializer {
override fun serialize(searchable: PinnableSearchable): String { override fun serialize(searchable: SavableSearchable): String {
searchable as Website searchable as Website
return jsonObjectOf( return jsonObjectOf(
"label" to searchable.label, "label" to searchable.label,
@ -26,7 +25,7 @@ class WebsiteSerializer : SearchableSerializer {
} }
class WebsiteDeserializer: SearchableDeserializer { class WebsiteDeserializer: SearchableDeserializer {
override fun deserialize(serialized: String): PinnableSearchable? { override fun deserialize(serialized: String): SavableSearchable? {
val json = JSONObject(serialized) val json = JSONObject(serialized)
return Website( return Website(
label = json.getString("label"), 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.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.ktx.tryStartActivity 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 de.mm20.launcher2.wikipedia.R import de.mm20.launcher2.wikipedia.R
data class Wikipedia( data class Wikipedia(
@ -22,7 +21,7 @@ data class Wikipedia(
val image: String?, val image: String?,
val wikipediaUrl: String, val wikipediaUrl: String,
override val labelOverride: String? = null, override val labelOverride: String? = null,
) : PinnableSearchable { ) : SavableSearchable {
override val domain: String = Domain override val domain: String = Domain

View File

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