Reorganize Searchable data types

This commit is contained in:
MM20
2022-10-12 20:36:39 +02:00
parent a01b0aa03d
commit bcda89c211
98 changed files with 817 additions and 738 deletions
@@ -15,9 +15,13 @@ import de.mm20.launcher2.ktx.normalize
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SearchableRepository
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.LauncherShortcut
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -27,7 +31,7 @@ import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.FuzzyScore
import java.util.*
interface AppShortcutRepository {
interface AppShortcutRepository: SearchableRepository<AppShortcut> {
suspend fun getShortcutsForActivity(
launcherActivityInfo: LauncherActivityInfo,
count: Int = 5
@@ -35,8 +39,6 @@ interface AppShortcutRepository {
suspend fun getShortcutsConfigActivities(): List<LauncherApp>
fun search(query: String): Flow<List<AppShortcut>>
fun removePinnedShortcut(shortcut: LauncherShortcut)
}
@@ -72,32 +74,31 @@ internal class AppShortcutRepositoryImpl(
LauncherShortcut(
context,
it,
launcherActivityInfo.label.toString()
)
} ?: emptyList())
appShortcuts
}
override fun search(query: String) = channelFlow<List<AppShortcut>> {
override fun search(query: String) = channelFlow<ImmutableList<AppShortcut>> {
if (query.length < 3) {
send(emptyList())
send(persistentListOf())
return@channelFlow
}
withContext(Dispatchers.IO) {
if (!permissionsManager.checkPermissionOnce(PermissionGroup.AppShortcuts)) {
send(emptyList())
send(persistentListOf())
return@withContext
}
dataStore.data.map { it.appShortcutSearch.enabled }.collectLatest { enabled ->
if (!enabled) {
send(emptyList())
send(persistentListOf())
return@collectLatest
}
shortcutChangeEmitter.collectLatest {
val launcherApps =
context.getSystemService<LauncherApps>() ?: return@collectLatest send(
emptyList()
persistentListOf()
)
val shortcutQuery = LauncherApps.ShortcutQuery()
@@ -124,17 +125,11 @@ internal class AppShortcutRepositoryImpl(
send(
shortcuts.mapNotNull {
val label = try {
pm.getApplicationInfo(it.`package`, 0).loadLabel(pm).toString()
} catch (e: PackageManager.NameNotFoundException) {
""
}
LauncherShortcut(
context,
it,
label
it
)
}
}.toImmutableList()
)
}
}
@@ -5,22 +5,22 @@ import android.content.Intent
import android.content.Intent.ShortcutIconResource
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Process
import android.os.UserManager
import androidx.core.content.getSystemService
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.LauncherShortcut
import de.mm20.launcher2.search.data.LegacyShortcut
import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.search.Searchable
import org.json.JSONObject
import org.koin.core.component.KoinComponent
class LauncherShortcutSerializer : SearchableSerializer {
override fun serialize(searchable: Searchable): String {
override fun serialize(searchable: PinnableSearchable): String {
searchable as LauncherShortcut
return jsonObjectOf(
"packagename" to searchable.launcherShortcut.`package`,
@@ -38,7 +38,7 @@ class LauncherShortcutDeserializer(
val context: Context
) : SearchableDeserializer, KoinComponent {
override fun deserialize(serialized: String): Searchable? {
override fun deserialize(serialized: String): PinnableSearchable? {
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
if (!launcherApps.hasShortcutHostPermission()) return null
else {
@@ -75,7 +75,6 @@ class LauncherShortcutDeserializer(
return LauncherShortcut(
context = context,
launcherShortcut = shortcuts[0],
appName = appName
)
}
}
@@ -83,7 +82,7 @@ class LauncherShortcutDeserializer(
}
class LegacyShortcutSerializer: SearchableSerializer {
override fun serialize(searchable: Searchable): String? {
override fun serialize(searchable: PinnableSearchable): String {
searchable as LegacyShortcut
return jsonObjectOf(
"label" to searchable.label,
@@ -104,7 +103,7 @@ class LegacyShortcutSerializer: SearchableSerializer {
class LegacyShortcutDeserializer(
val context: Context
): SearchableDeserializer {
override fun deserialize(serialized: String): Searchable? {
override fun deserialize(serialized: String): PinnableSearchable {
val json = JSONObject(serialized)
val label = json.getString("label")
val intent = Intent.parseUri(json.getString("intent"), 0)
@@ -7,10 +7,15 @@ import de.mm20.launcher2.appshortcuts.R
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
interface AppShortcut: PinnableSearchable {
abstract class AppShortcut(
val appName: String?
) : Searchable() {
override val preferDetailsOverLaunch: Boolean
get() = false
override fun getPlaceholderIcon(context: Context): StaticLauncherIcon {
return StaticLauncherIcon(
@@ -4,6 +4,7 @@ import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.graphics.drawable.AdaptiveIconDrawable
import android.os.Bundle
@@ -14,36 +15,55 @@ import de.mm20.launcher2.appshortcuts.R
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.PinnableSearchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
* Represents a modern (Android O+) launcher shortcut
*/
class LauncherShortcut(
context: Context,
data class LauncherShortcut(
val launcherShortcut: ShortcutInfo,
appName: String
) : AppShortcut(appName) {
override val appName: String?,
internal val userSerialNumber: Long,
override val labelOverride: String? = null,
) : AppShortcut {
override val domain: String = Domain
constructor(
context: Context,
launcherShortcut: ShortcutInfo,
): this(
launcherShortcut = launcherShortcut,
appName = try {
context.packageManager.getApplicationInfo(launcherShortcut.`package`, 0)
.loadLabel(context.packageManager).toString()
} catch (e: PackageManager.NameNotFoundException) {
null
},
userSerialNumber = launcherShortcut.userHandle.getSerialNumber(context)
)
override val label: String
get() = launcherShortcut.shortLabel?.toString() ?: ""
override fun overrideLabel(label: String): LauncherShortcut {
return this.copy(labelOverride = label)
}
override val preferDetailsOverLaunch: Boolean = false
internal val userSerialNumber: Long = launcherShortcut.userHandle.getSerialNumber(context)
val isMainProfile = launcherShortcut.userHandle == Process.myUserHandle()
override val key: String
get() = if (isMainProfile) {
"shortcut://${launcherShortcut.`package`}/${launcherShortcut.id}"
"$domain://${launcherShortcut.`package`}/${launcherShortcut.id}"
} else {
"shortcut://${launcherShortcut.`package`}/${launcherShortcut.id}:${userSerialNumber}"
"$domain://${launcherShortcut.`package`}/${launcherShortcut.id}:${userSerialNumber}"
}
override fun getLaunchIntent(context: Context): Intent? {
return launcherShortcut.intent
}
override fun launch(context: Context, options: Bundle?): Boolean {
val launcherApps = context.getSystemService<LauncherApps>()!!
try {
@@ -123,10 +143,10 @@ class LauncherShortcut(
return LauncherShortcut(
context,
shortcutInfo,
context.packageManager.getApplicationInfo(shortcutInfo.`package`, 0)
.loadLabel(context.packageManager).toString()
)
}
const val Domain = "shortcut"
}
}
@@ -4,22 +4,32 @@ import android.content.Context
import android.content.Intent
import android.content.Intent.ShortcutIconResource
import android.graphics.drawable.AdaptiveIconDrawable
import android.os.Bundle
import android.util.Log
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getDrawableOrNull
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.PinnableSearchable
class LegacyShortcut(
data class LegacyShortcut(
val intent: Intent,
override val label: String,
appName: String?,
override val appName: String?,
val iconResource: ShortcutIconResource?,
) : AppShortcut(appName) {
override val key: String
get() = "legacyshortcut://${intent.toUri(0)}"
override val labelOverride: String? = null,
) : AppShortcut {
override fun getLaunchIntent(context: Context): Intent {
return intent
override val domain = Domain
override val key: String = "$domain://${intent.toUri(0)}"
override fun overrideLabel(label: String): LegacyShortcut {
return this.copy(labelOverride = label)
}
override fun launch(context: Context, options: Bundle?): Boolean {
return context.tryStartActivity(intent, options)
}
val packageName: String?
@@ -67,6 +77,9 @@ class LegacyShortcut(
}
companion object {
const val Domain = "legacyshortcut"
fun fromPinRequestIntent(context: Context, data: Intent): LegacyShortcut? {
val intent: Intent? = data.extras?.getParcelable(Intent.EXTRA_SHORTCUT_INTENT)
val name: String? = data.extras?.getString(Intent.EXTRA_SHORTCUT_NAME)