Reorganize Searchable data types
This commit is contained in:
@@ -44,7 +44,6 @@ dependencies {
|
||||
implementation(libs.commons.text)
|
||||
|
||||
implementation(project(":applications"))
|
||||
implementation(project(":search"))
|
||||
implementation(project(":permissions"))
|
||||
implementation(project(":base"))
|
||||
implementation(project(":preferences"))
|
||||
|
||||
+12
-17
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user