From bcda89c2117b7a77e9b8cbbac0f3b5c57b055412 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Wed, 12 Oct 2022 20:36:39 +0200 Subject: [PATCH] Reorganize Searchable data types --- applications/build.gradle.kts | 1 - .../launcher2/applications/AppRepository.kt | 19 ++-- .../launcher2/search/data/AppInstallation.kt | 73 ------------ .../launcher2/search/data/AppSerialization.kt | 11 +- .../mm20/launcher2/search/data/Application.kt | 96 ---------------- .../mm20/launcher2/search/data/LauncherApp.kt | 106 +++++++++++++++--- appshortcuts/build.gradle.kts | 1 - .../appshortcuts/AppShortcutRepository.kt | 29 ++--- .../appshortcuts/AppShortcutSerialization.kt | 13 +-- .../mm20/launcher2/search/data/AppShortcut.kt | 9 +- .../launcher2/search/data/LauncherShortcut.kt | 46 +++++--- .../launcher2/search/data/LegacyShortcut.kt | 27 +++-- backup/build.gradle.kts | 2 +- .../de/mm20/launcher2/backup/BackupManager.kt | 2 - badges/build.gradle.kts | 1 - .../mm20/launcher2/badges/BadgeRepository.kt | 2 +- .../providers/AppShortcutBadgeProvider.kt | 2 +- .../badges/providers/BadgeProvider.kt | 2 +- .../badges/providers/CloudBadgeProvider.kt | 2 +- .../providers/NotificationBadgeProvider.kt | 5 +- .../providers/SuspendedAppsBadgeProvider.kt | 6 +- .../providers/WorkProfileBadgeProvider.kt | 3 +- .../launcher2/search/PinnableSearchable.kt | 41 +++++++ .../de/mm20/launcher2/search/Searchable.kt | 6 + .../search/SearchableDeserializer.kt | 12 ++ .../launcher2/search/SearchableRepository.kt | 8 ++ .../launcher2/search/SearchableSerializer.kt | 6 +- calculator/build.gradle.kts | 2 +- .../mm20/launcher2/search/data/Calculator.kt | 11 +- calendar/build.gradle.kts | 1 - .../launcher2/calendar/CalendarRepository.kt | 16 +-- .../calendar/CalendarSerialization.kt | 7 +- .../launcher2/search/data/CalendarEvent.kt | 30 ++++- contacts/build.gradle.kts | 1 - .../launcher2/contacts/ContactRepository.kt | 18 +-- .../contacts/ContactSerialization.kt | 7 +- .../de/mm20/launcher2/search/data/Contact.kt | 32 ++++-- customattrs/build.gradle.kts | 2 +- .../customattrs/CustomAttributesRepository.kt | 42 +++---- favorites/build.gradle.kts | 1 - .../mm20/launcher2/favorites/FavoritesItem.kt | 5 +- .../favorites/FavoritesRepository.kt | 65 ++++++----- .../mm20/launcher2/favorites/Serialization.kt | 1 + .../launcher2/favorites/TagSerialization.kt | 8 +- .../java/de/mm20/launcher2/search/data/Tag.kt | 26 ++++- files/build.gradle.kts | 1 - .../mm20/launcher2/files/FileSerialization.kt | 27 ++--- .../mm20/launcher2/files/FilesRepository.kt | 15 ++- .../de/mm20/launcher2/search/data/File.kt | 29 +++-- .../mm20/launcher2/search/data/GDriveFile.kt | 37 ++++-- .../mm20/launcher2/search/data/LocalFile.kt | 37 ++++-- .../launcher2/search/data/NextcloudFile.kt | 38 +++++-- .../launcher2/search/data/OneDriveFile.kt | 37 ++++-- .../launcher2/search/data/OwncloudFile.kt | 39 +++++-- icons/build.gradle.kts | 1 - .../de/mm20/launcher2/icons/IconRepository.kt | 11 +- .../icons/providers/CalendarIconProvider.kt | 10 +- .../providers/CustomIconPackIconProvider.kt | 6 +- .../providers/CustomThemedIconProvider.kt | 5 +- .../providers/GoogleClockIconProvider.kt | 10 +- .../icons/providers/IconPackIconProvider.kt | 5 +- .../launcher2/icons/providers/IconProvider.kt | 7 +- .../providers/PlaceholderIconProvider.kt | 5 +- .../icons/providers/SystemIconProvider.kt | 5 +- .../icons/providers/ThemedIconProvider.kt | 19 +--- .../ThemedPlaceholderIconProvider.kt | 5 +- permissions/build.gradle.kts | 1 - .../search/SearchableDeserializer.kt | 14 --- .../mm20/launcher2/search/data/Searchable.kt | 68 ----------- .../mm20/launcher2/ui/common/FavoritesVM.kt | 5 +- .../ui/launcher/modals/EditFavoritesSheet.kt | 6 +- .../launcher/modals/EditFavoritesSheetVM.kt | 12 +- .../ui/launcher/modals/HiddenItemsSheet.kt | 5 +- .../ui/launcher/search/SearchColumn.kt | 11 +- .../launcher2/ui/launcher/search/SearchVM.kt | 23 ++-- .../ui/launcher/search/apps/AppItem.kt | 6 +- .../ui/launcher/search/apps/AppItemVM.kt | 30 ++--- .../search/common/SearchableItemVM.kt | 8 +- .../customattrs/CustomizeSearchableSheet.kt | 5 +- .../customattrs/CustomizeSearchableSheetVM.kt | 6 +- .../launcher/search/common/grid/GridItem.kt | 16 +-- .../launcher/search/common/grid/GridItemVM.kt | 5 +- .../search/common/grid/SearchResultGrid.kt | 5 +- .../launcher/search/common/list/ListItem.kt | 5 +- .../launcher/search/common/list/ListItemVM.kt | 5 +- .../search/common/list/SearchResultList.kt | 5 +- .../ui/launcher/search/website/WebsiteItem.kt | 2 +- .../widgets/favorites/FavoritesWidgetVM.kt | 12 -- .../hiddenitems/HiddenItemsSettingsScreen.kt | 2 - .../HiddenItemsSettingsScreenVM.kt | 43 +++---- .../mm20/launcher2/ui/utils/CustomLabels.kt | 17 +-- unitconverter/build.gradle.kts | 2 +- websites/build.gradle.kts | 1 - .../de/mm20/launcher2/search/data/Website.kt | 34 ++++-- .../websites/WebsiteSerialization.kt | 7 +- wikipedia/build.gradle.kts | 2 +- .../mm20/launcher2/search/data/Wikipedia.kt | 30 ++++- .../wikipedia/WikipediaSerialization.kt | 7 +- 98 files changed, 817 insertions(+), 738 deletions(-) delete mode 100644 applications/src/main/java/de/mm20/launcher2/search/data/AppInstallation.kt delete mode 100644 applications/src/main/java/de/mm20/launcher2/search/data/Application.kt create mode 100644 base/src/main/java/de/mm20/launcher2/search/PinnableSearchable.kt create mode 100644 base/src/main/java/de/mm20/launcher2/search/Searchable.kt create mode 100644 base/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt create mode 100644 base/src/main/java/de/mm20/launcher2/search/SearchableRepository.kt rename {search => base}/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt (58%) delete mode 100644 search/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt delete mode 100644 search/src/main/java/de/mm20/launcher2/search/data/Searchable.kt diff --git a/applications/build.gradle.kts b/applications/build.gradle.kts index 6d5c5b13..6808c20c 100644 --- a/applications/build.gradle.kts +++ b/applications/build.gradle.kts @@ -45,7 +45,6 @@ dependencies { implementation(libs.commons.text) - implementation(project(":search")) implementation(project(":base")) implementation(project(":preferences")) implementation(project(":ktx")) diff --git a/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt b/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt index ba266cd0..b7feb328 100644 --- a/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt +++ b/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt @@ -12,17 +12,18 @@ import android.os.Process import android.os.UserHandle import android.util.Log import de.mm20.launcher2.ktx.normalize -import de.mm20.launcher2.search.data.Application +import de.mm20.launcher2.search.SearchableRepository import de.mm20.launcher2.search.data.LauncherApp +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.withContext import org.apache.commons.text.similarity.FuzzyScore import java.util.* -interface AppRepository { - fun search(query: String): Flow> - fun getAllInstalledApps(): Flow> +interface AppRepository: SearchableRepository { + fun getAllInstalledApps(): Flow> fun getSuspendedPackages(): Flow> } @@ -140,11 +141,11 @@ internal class AppRepositoryImpl( return LauncherApp(context, launcherActivityInfo) } - override fun search(query: String): Flow> = channelFlow { + override fun search(query: String): Flow> = channelFlow { installedApps.collectLatest { apps -> withContext(Dispatchers.Default) { - val appResults = mutableListOf() + val appResults = mutableListOf() if (query.isEmpty()) { appResults.addAll(apps) } else { @@ -158,12 +159,12 @@ internal class AppRepositoryImpl( appResults.sort() - send(appResults) + send(appResults.toImmutableList()) } } } - override fun getAllInstalledApps(): Flow> { + override fun getAllInstalledApps(): Flow> { return installedApps } @@ -174,7 +175,7 @@ internal class AppRepositoryImpl( fuzzyScore.fuzzyScore(normalizedLabel, query.normalize()) >= query.length * 1.5 } - private fun getActivityByComponentName(componentName: ComponentName?): Application? { + private fun getActivityByComponentName(componentName: ComponentName?): LauncherApp? { componentName ?: return null val intent = Intent().setComponent(componentName) val lai = launcherApps.resolveActivity(intent, Process.myUserHandle()) diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/AppInstallation.kt b/applications/src/main/java/de/mm20/launcher2/search/data/AppInstallation.kt deleted file mode 100644 index a6c10327..00000000 --- a/applications/src/main/java/de/mm20/launcher2/search/data/AppInstallation.kt +++ /dev/null @@ -1,73 +0,0 @@ -package de.mm20.launcher2.search.data - -import android.content.Context -import android.content.Intent -import android.content.pm.PackageInstaller -import android.graphics.ColorMatrix -import android.graphics.ColorMatrixColorFilter -import android.graphics.drawable.BitmapDrawable -import androidx.core.content.ContextCompat -import de.mm20.launcher2.applications.R -import de.mm20.launcher2.icons.* - -class AppInstallation( - val session: PackageInstaller.SessionInfo -) : Application( - label = session.appLabel?.toString() ?: "", - `package` = session.appPackageName ?: "", - activity = "", - flags = 0, - version = null -) { - - override val key: String - get() = "installer://${session.installerPackageName}:${session.appPackageName}" - - override fun getLaunchIntent(context: Context): Intent? { - return session.createDetailsIntent() - } - - override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { - return StaticLauncherIcon( - foregroundLayer = TintedIconLayer( - icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!, - scale = 0.5f, - color = 0xFF757575.toInt() - ), - backgroundLayer = ColorLayer(0xFF757575.toInt()) - ) - } - - override suspend fun loadIcon( - context: Context, - size: Int, - themed: Boolean, - ): LauncherIcon { - val icon = session.appIcon ?: return getPlaceholderIcon(context) - val foreground = BitmapDrawable(context.resources, icon) - foreground.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { - setSaturation(0f) - }) - return StaticLauncherIcon( - foregroundLayer = StaticIconLayer( - icon = foreground, - ), - backgroundLayer = ColorLayer(0xFF757575.toInt()) - ) - } - - override fun getStoreDetails(context: Context): StoreLink? { - return getStoreLinkForInstaller(session.installerPackageName, `package`) - } - - companion object { - fun search(context: Context): List { - val installer = context.packageManager.packageInstaller - val sessions = installer.allSessions - val results = sessions.mapNotNull { - if (it.appLabel != null && it.isActive) AppInstallation(it) else null - } - return results - } - } -} \ No newline at end of file diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/AppSerialization.kt b/applications/src/main/java/de/mm20/launcher2/search/data/AppSerialization.kt index b88cee27..1beb8e65 100644 --- a/applications/src/main/java/de/mm20/launcher2/search/data/AppSerialization.kt +++ b/applications/src/main/java/de/mm20/launcher2/search/data/AppSerialization.kt @@ -4,20 +4,17 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.LauncherApps -import android.content.pm.PackageManager -import android.os.Build import android.os.Process import android.os.UserManager -import androidx.annotation.RequiresApi import androidx.core.content.getSystemService -import de.mm20.launcher2.ktx.jsonObjectOf +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.SearchableSerializer import org.json.JSONObject -import org.koin.core.component.KoinComponent class LauncherAppSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as LauncherApp val json = JSONObject() json.put("package", searchable.`package`) @@ -31,7 +28,7 @@ class LauncherAppSerializer : SearchableSerializer { } class LauncherAppDeserializer(val context: Context) : SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable? { val json = JSONObject(serialized) val launcherApps = context.getSystemService()!! val userManager = context.getSystemService()!! diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/Application.kt b/applications/src/main/java/de/mm20/launcher2/search/data/Application.kt deleted file mode 100644 index bae44e1d..00000000 --- a/applications/src/main/java/de/mm20/launcher2/search/data/Application.kt +++ /dev/null @@ -1,96 +0,0 @@ -package de.mm20.launcher2.search.data - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.Color -import androidx.core.content.ContextCompat -import de.mm20.launcher2.applications.R -import de.mm20.launcher2.compat.PackageManagerCompat -import de.mm20.launcher2.icons.ColorLayer -import de.mm20.launcher2.icons.StaticLauncherIcon -import de.mm20.launcher2.icons.TintedIconLayer -import org.json.JSONObject - -abstract class Application( - override val label: String, - val `package`: String, - val activity: String, - val flags: Int, - val version: String?, -) : Searchable() { - - override fun serialize(): String { - val json = JSONObject() - json.put("package", `package`) - json.put("activity", activity) - return json.toString() - } - - override fun getLaunchIntent(context: Context): Intent? { - val intent = Intent() - intent.component = ComponentName(`package`, activity) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - return intent - } - - override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { - return StaticLauncherIcon( - foregroundLayer = TintedIconLayer( - icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!, - scale = 0.5f, - color = 0xff3dda84.toInt(), - ), - backgroundLayer = ColorLayer(0xff3dda84.toInt()) - ) - } - - open fun getStoreDetails(context: Context): StoreLink? { - val pm = context.packageManager - return try { - val installSourceInfo = PackageManagerCompat.getInstallSource(pm, `package`) - getStoreLinkForInstaller(installSourceInfo.initiatingPackageName, `package`) - } catch (e: PackageManager.NameNotFoundException) { - null - } - } - - override val key: String - get() = "app://$`package`:$activity" - - companion object { - internal fun getStoreLinkForInstaller( - installerPackage: String?, - packageName: String? - ): StoreLink? { - if (packageName == null) return null - return when (installerPackage) { - "de.amazon.mShop.android", "com.amazon.venezia" -> { - StoreLink( - "Amazon App Shop", - "http://www.amazon.com/gp/mas/dl/android?p=${packageName}" - ) - } - "com.android.vending" -> { - StoreLink( - "Google Play Store", - "https://play.google.com/store/apps/details?id=${packageName}" - ) - } - "org.fdroid.fdroid", "com.aurora.adroid" -> { - StoreLink( - "F-Droid", - "https://f-droid.org/packages/${packageName}" - ) - } - else -> null - } - } - } -} - -data class StoreLink( - val label: String, - val url: String -) \ No newline at end of file diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt b/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt index b96f3a96..dde7b778 100644 --- a/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt +++ b/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt @@ -10,37 +10,65 @@ import android.graphics.drawable.AdaptiveIconDrawable import android.os.Bundle import android.os.Process import android.os.UserHandle +import androidx.core.content.ContextCompat import androidx.core.content.getSystemService +import de.mm20.launcher2.applications.R +import de.mm20.launcher2.compat.PackageManagerCompat import de.mm20.launcher2.icons.* import de.mm20.launcher2.ktx.getSerialNumber import de.mm20.launcher2.ktx.isAtLeastApiLevel +import de.mm20.launcher2.search.PinnableSearchable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -/** - * An [Application] based on an [android.content.pm.LauncherActivityInfo] - */ -class LauncherApp( - context: Context, - val launcherActivityInfo: LauncherActivityInfo -) : Application( - label = launcherActivityInfo.label.toString(), - `package` = launcherActivityInfo.applicationInfo.packageName, - activity = launcherActivityInfo.name, - flags = launcherActivityInfo.applicationInfo.flags, - version = getPackageVersionName(context, launcherActivityInfo.applicationInfo.packageName), -) { +data class LauncherApp( + val launcherActivityInfo: LauncherActivityInfo, + override val label: String, + val `package`: String, + val activity: String, + val flags: Int, + val version: String?, + internal val userSerialNumber: Long, + override val labelOverride: String? = null, +) : PinnableSearchable { + + constructor(context: Context, launcherActivityInfo: LauncherActivityInfo): this( + launcherActivityInfo, + label = launcherActivityInfo.label.toString(), + `package` = launcherActivityInfo.applicationInfo.packageName, + activity = launcherActivityInfo.name, + flags = launcherActivityInfo.applicationInfo.flags, + version = getPackageVersionName(context, launcherActivityInfo.applicationInfo.packageName), + userSerialNumber = launcherActivityInfo.user.getSerialNumber(context) + ) - internal val userSerialNumber: Long = launcherActivityInfo.user.getSerialNumber(context) val isMainProfile = launcherActivityInfo.user == Process.myUserHandle() + override val domain: String = Domain + override val preferDetailsOverLaunch: Boolean = false + + override fun overrideLabel(label: String): LauncherApp { + return this.copy(labelOverride = label) + } + override val key: String - get() = if (isMainProfile) "app://$`package`:$activity" else "app://$`package`:$activity:${userSerialNumber}" + get() = if (isMainProfile) "${domain}://$`package`:$activity" else "${domain}://$`package`:$activity:${userSerialNumber}" fun getUser(): UserHandle? { return launcherActivityInfo.user } + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { + return StaticLauncherIcon( + foregroundLayer = TintedIconLayer( + icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!, + scale = 0.5f, + color = 0xff3dda84.toInt(), + ), + backgroundLayer = ColorLayer(0xff3dda84.toInt()) + ) + } + override suspend fun loadIcon( context: Context, size: Int, @@ -107,7 +135,46 @@ class LauncherApp( return true } + fun getStoreDetails(context: Context): StoreLink? { + val pm = context.packageManager + return try { + val installSourceInfo = PackageManagerCompat.getInstallSource(pm, `package`) + getStoreLinkForInstaller(installSourceInfo.initiatingPackageName, `package`) + } catch (e: PackageManager.NameNotFoundException) { + null + } + } + + + companion object { + private fun getStoreLinkForInstaller( + installerPackage: String?, + packageName: String? + ): StoreLink? { + if (packageName == null) return null + return when (installerPackage) { + "de.amazon.mShop.android", "com.amazon.venezia" -> { + StoreLink( + "Amazon App Shop", + "http://www.amazon.com/gp/mas/dl/android?p=${packageName}" + ) + } + "com.android.vending" -> { + StoreLink( + "Google Play Store", + "https://play.google.com/store/apps/details?id=${packageName}" + ) + } + "org.fdroid.fdroid", "com.aurora.adroid" -> { + StoreLink( + "F-Droid", + "https://f-droid.org/packages/${packageName}" + ) + } + else -> null + } + } fun getPackageVersionName(context: Context, packageName: String): String? { return try { @@ -116,5 +183,12 @@ class LauncherApp( null } } + + const val Domain = "app" } -} \ No newline at end of file +} + +data class StoreLink( + val label: String, + val url: String +) \ No newline at end of file diff --git a/appshortcuts/build.gradle.kts b/appshortcuts/build.gradle.kts index 67acd0c6..3c208a99 100644 --- a/appshortcuts/build.gradle.kts +++ b/appshortcuts/build.gradle.kts @@ -44,7 +44,6 @@ dependencies { implementation(libs.commons.text) implementation(project(":applications")) - implementation(project(":search")) implementation(project(":permissions")) implementation(project(":base")) implementation(project(":preferences")) diff --git a/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutRepository.kt b/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutRepository.kt index b5c205b0..cee3bb87 100644 --- a/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutRepository.kt +++ b/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutRepository.kt @@ -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 { suspend fun getShortcutsForActivity( launcherActivityInfo: LauncherActivityInfo, count: Int = 5 @@ -35,8 +39,6 @@ interface AppShortcutRepository { suspend fun getShortcutsConfigActivities(): List - fun search(query: String): Flow> - 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> { + override fun search(query: String) = channelFlow> { 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() ?: 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() ) } } diff --git a/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutSerialization.kt b/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutSerialization.kt index 3685fc5d..a2ee772a 100644 --- a/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutSerialization.kt +++ b/appshortcuts/src/main/java/de/mm20/launcher2/appshortcuts/AppShortcutSerialization.kt @@ -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) diff --git a/appshortcuts/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt b/appshortcuts/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt index 3ac2ee42..023cf122 100644 --- a/appshortcuts/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt +++ b/appshortcuts/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt @@ -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( diff --git a/appshortcuts/src/main/java/de/mm20/launcher2/search/data/LauncherShortcut.kt b/appshortcuts/src/main/java/de/mm20/launcher2/search/data/LauncherShortcut.kt index d1fb223d..2bacc93f 100644 --- a/appshortcuts/src/main/java/de/mm20/launcher2/search/data/LauncherShortcut.kt +++ b/appshortcuts/src/main/java/de/mm20/launcher2/search/data/LauncherShortcut.kt @@ -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()!! 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" } } \ No newline at end of file diff --git a/appshortcuts/src/main/java/de/mm20/launcher2/search/data/LegacyShortcut.kt b/appshortcuts/src/main/java/de/mm20/launcher2/search/data/LegacyShortcut.kt index bb318455..07fa919e 100644 --- a/appshortcuts/src/main/java/de/mm20/launcher2/search/data/LegacyShortcut.kt +++ b/appshortcuts/src/main/java/de/mm20/launcher2/search/data/LegacyShortcut.kt @@ -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) diff --git a/backup/build.gradle.kts b/backup/build.gradle.kts index a6f5b770..f6d3c799 100644 --- a/backup/build.gradle.kts +++ b/backup/build.gradle.kts @@ -41,8 +41,8 @@ dependencies { implementation(libs.koin.android) implementation(project(":favorites")) - implementation(project(":search")) implementation(project(":widgets")) + implementation(project(":search")) implementation(project(":preferences")) implementation(project(":ktx")) implementation(project(":customattrs")) diff --git a/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt b/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt index ff4acae8..c0cf16f5 100644 --- a/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt +++ b/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt @@ -14,8 +14,6 @@ import kotlinx.coroutines.* import java.io.File import java.io.InputStream import java.io.OutputStream -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream diff --git a/badges/build.gradle.kts b/badges/build.gradle.kts index 7e99d0e3..d86b0eb4 100644 --- a/badges/build.gradle.kts +++ b/badges/build.gradle.kts @@ -49,6 +49,5 @@ dependencies { implementation(project(":notifications")) implementation(project(":preferences")) implementation(project(":base")) - implementation(project(":search")) implementation(project(":files")) } \ No newline at end of file diff --git a/badges/src/main/java/de/mm20/launcher2/badges/BadgeRepository.kt b/badges/src/main/java/de/mm20/launcher2/badges/BadgeRepository.kt index ea2ba4b8..d7606789 100644 --- a/badges/src/main/java/de/mm20/launcher2/badges/BadgeRepository.kt +++ b/badges/src/main/java/de/mm20/launcher2/badges/BadgeRepository.kt @@ -3,7 +3,7 @@ package de.mm20.launcher2.badges import android.content.Context import de.mm20.launcher2.badges.providers.* import de.mm20.launcher2.preferences.LauncherDataStore -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import org.koin.core.component.KoinComponent diff --git a/badges/src/main/java/de/mm20/launcher2/badges/providers/AppShortcutBadgeProvider.kt b/badges/src/main/java/de/mm20/launcher2/badges/providers/AppShortcutBadgeProvider.kt index 5d35a81c..dba7bbc4 100644 --- a/badges/src/main/java/de/mm20/launcher2/badges/providers/AppShortcutBadgeProvider.kt +++ b/badges/src/main/java/de/mm20/launcher2/badges/providers/AppShortcutBadgeProvider.kt @@ -6,7 +6,7 @@ import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.graphics.BadgeDrawable 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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow diff --git a/badges/src/main/java/de/mm20/launcher2/badges/providers/BadgeProvider.kt b/badges/src/main/java/de/mm20/launcher2/badges/providers/BadgeProvider.kt index eec5b033..4c977fc3 100644 --- a/badges/src/main/java/de/mm20/launcher2/badges/providers/BadgeProvider.kt +++ b/badges/src/main/java/de/mm20/launcher2/badges/providers/BadgeProvider.kt @@ -1,7 +1,7 @@ package de.mm20.launcher2.badges.providers import de.mm20.launcher2.badges.Badge -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.flow.Flow interface BadgeProvider { diff --git a/badges/src/main/java/de/mm20/launcher2/badges/providers/CloudBadgeProvider.kt b/badges/src/main/java/de/mm20/launcher2/badges/providers/CloudBadgeProvider.kt index 341f9843..28e3bcf5 100644 --- a/badges/src/main/java/de/mm20/launcher2/badges/providers/CloudBadgeProvider.kt +++ b/badges/src/main/java/de/mm20/launcher2/badges/providers/CloudBadgeProvider.kt @@ -2,7 +2,7 @@ package de.mm20.launcher2.badges.providers import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.search.data.File -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt b/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt index a37bf69a..e993c2e1 100644 --- a/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt +++ b/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt @@ -3,9 +3,8 @@ package de.mm20.launcher2.badges.providers import android.app.Notification import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.notifications.NotificationRepository -import de.mm20.launcher2.search.data.Application +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.LauncherApp -import de.mm20.launcher2.search.data.Searchable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest @@ -17,7 +16,7 @@ class NotificationBadgeProvider : BadgeProvider, KoinComponent { private val notificationRepository: NotificationRepository by inject() override fun getBadge(searchable: Searchable): Flow = channelFlow { - if (searchable is Application) { + if (searchable is LauncherApp) { val packageName = searchable.`package` notificationRepository.notifications.map { it.filter { it.packageName == packageName } diff --git a/badges/src/main/java/de/mm20/launcher2/badges/providers/SuspendedAppsBadgeProvider.kt b/badges/src/main/java/de/mm20/launcher2/badges/providers/SuspendedAppsBadgeProvider.kt index 9dee626f..cca49e86 100644 --- a/badges/src/main/java/de/mm20/launcher2/badges/providers/SuspendedAppsBadgeProvider.kt +++ b/badges/src/main/java/de/mm20/launcher2/badges/providers/SuspendedAppsBadgeProvider.kt @@ -3,8 +3,8 @@ package de.mm20.launcher2.badges.providers import de.mm20.launcher2.applications.AppRepository import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.R -import de.mm20.launcher2.search.data.Application -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable +import de.mm20.launcher2.search.data.LauncherApp import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest @@ -15,7 +15,7 @@ class SuspendedAppsBadgeProvider : BadgeProvider, KoinComponent { private val appRepository: AppRepository by inject() override fun getBadge(searchable: Searchable): Flow = channelFlow { - if (searchable is Application) { + if (searchable is LauncherApp) { val packageName = searchable.`package` appRepository.getSuspendedPackages().collectLatest { if (it.contains(packageName)) { diff --git a/badges/src/main/java/de/mm20/launcher2/badges/providers/WorkProfileBadgeProvider.kt b/badges/src/main/java/de/mm20/launcher2/badges/providers/WorkProfileBadgeProvider.kt index b5160208..6a0eb08f 100644 --- a/badges/src/main/java/de/mm20/launcher2/badges/providers/WorkProfileBadgeProvider.kt +++ b/badges/src/main/java/de/mm20/launcher2/badges/providers/WorkProfileBadgeProvider.kt @@ -2,10 +2,9 @@ package de.mm20.launcher2.badges.providers import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.R -import de.mm20.launcher2.search.data.AppShortcut import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherShortcut -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/base/src/main/java/de/mm20/launcher2/search/PinnableSearchable.kt b/base/src/main/java/de/mm20/launcher2/search/PinnableSearchable.kt new file mode 100644 index 00000000..94a704be --- /dev/null +++ b/base/src/main/java/de/mm20/launcher2/search/PinnableSearchable.kt @@ -0,0 +1,41 @@ +package de.mm20.launcher2.search + +import android.content.Context +import android.os.Bundle +import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.StaticLauncherIcon +import de.mm20.launcher2.ktx.romanize +import java.text.Collator + +interface PinnableSearchable : Searchable, Comparable { + + val label: String + val labelOverride: String? + get() = null + + fun overrideLabel(label: String): PinnableSearchable + + fun launch(context: Context, options: Bundle?): Boolean + + /** + * If this is true, tapping the item will open the details popup instead of launching it + */ + val preferDetailsOverLaunch: Boolean + + fun getPlaceholderIcon(context: Context): StaticLauncherIcon + + suspend fun loadIcon( + context: Context, + size: Int, + themed: Boolean + ): LauncherIcon? = null + + + override fun compareTo(other: PinnableSearchable): Int { + val label1 = labelOverride ?: label + val label2 = other.labelOverride ?: other.label + return Collator.getInstance().apply { strength = Collator.SECONDARY } + .compare(label1.romanize(), label2.romanize()) + } + +} \ No newline at end of file diff --git a/base/src/main/java/de/mm20/launcher2/search/Searchable.kt b/base/src/main/java/de/mm20/launcher2/search/Searchable.kt new file mode 100644 index 00000000..4945955b --- /dev/null +++ b/base/src/main/java/de/mm20/launcher2/search/Searchable.kt @@ -0,0 +1,6 @@ +package de.mm20.launcher2.search + +interface Searchable { + val domain: String + val key: String +} \ No newline at end of file diff --git a/base/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt b/base/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt new file mode 100644 index 00000000..eb288ef8 --- /dev/null +++ b/base/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt @@ -0,0 +1,12 @@ +package de.mm20.launcher2.search + +interface SearchableDeserializer { + fun deserialize(serialized: String): PinnableSearchable? +} + +class NullDeserializer: SearchableDeserializer { + override fun deserialize(serialized: String): PinnableSearchable? { + return null + } + +} \ No newline at end of file diff --git a/base/src/main/java/de/mm20/launcher2/search/SearchableRepository.kt b/base/src/main/java/de/mm20/launcher2/search/SearchableRepository.kt new file mode 100644 index 00000000..38b075c4 --- /dev/null +++ b/base/src/main/java/de/mm20/launcher2/search/SearchableRepository.kt @@ -0,0 +1,8 @@ +package de.mm20.launcher2.search + +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.flow.Flow + +interface SearchableRepository { + fun search(query: String): Flow> +} \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt b/base/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt similarity index 58% rename from search/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt rename to base/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt index b3f174e2..e8e814ac 100644 --- a/search/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt +++ b/base/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt @@ -1,14 +1,12 @@ package de.mm20.launcher2.search -import de.mm20.launcher2.search.data.Searchable - interface SearchableSerializer { - fun serialize(searchable: Searchable): String? + fun serialize(searchable: PinnableSearchable): String? val typePrefix: String } class NullSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String? { + override fun serialize(searchable: PinnableSearchable): String? { return null } diff --git a/calculator/build.gradle.kts b/calculator/build.gradle.kts index b276590e..d71940d9 100644 --- a/calculator/build.gradle.kts +++ b/calculator/build.gradle.kts @@ -46,6 +46,6 @@ dependencies { implementation(libs.koin.android) implementation(project(":preferences")) - implementation(project(":search")) + implementation(project(":base")) } \ No newline at end of file diff --git a/calculator/src/main/java/de/mm20/launcher2/search/data/Calculator.kt b/calculator/src/main/java/de/mm20/launcher2/search/data/Calculator.kt index 0d77c057..d8daf2ab 100644 --- a/calculator/src/main/java/de/mm20/launcher2/search/data/Calculator.kt +++ b/calculator/src/main/java/de/mm20/launcher2/search/data/Calculator.kt @@ -1,14 +1,21 @@ package de.mm20.launcher2.search.data +import de.mm20.launcher2.search.Searchable import java.text.DecimalFormat import java.util.* import kotlin.math.abs import kotlin.math.roundToInt -class Calculator( +data class Calculator( val term: String, val solution: Double -) { +): Searchable { + + override val domain: String + get() = "calculator" + + override val key: String + get() = "calculator://$term" val formattedString: String val formattedBinaryString: String diff --git a/calendar/build.gradle.kts b/calendar/build.gradle.kts index bbcecef4..3ff6dbe4 100644 --- a/calendar/build.gradle.kts +++ b/calendar/build.gradle.kts @@ -42,7 +42,6 @@ dependencies { implementation(libs.koin.android) - api(project(":search")) implementation(project(":preferences")) implementation(project(":ktx")) implementation(project(":base")) diff --git a/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarRepository.kt b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarRepository.kt index f158e4ee..8c8cd042 100644 --- a/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarRepository.kt +++ b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarRepository.kt @@ -7,8 +7,12 @@ import androidx.core.database.getStringOrNull 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.CalendarEvent import de.mm20.launcher2.search.data.UserCalendar +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.withContext @@ -16,9 +20,7 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.* -interface CalendarRepository { - fun search(query: String): Flow> - +interface CalendarRepository: SearchableRepository { fun getUpcomingEvents(): Flow> suspend fun getCalendars(): List @@ -31,10 +33,10 @@ internal class CalendarRepositoryImpl( private val dataStore: LauncherDataStore by inject() private val permissionsManager: PermissionsManager by inject() - override fun search(query: String): Flow> { + override fun search(query: String): Flow> { if (query.isBlank() || query.length < 3) { return flow { - emit(emptyList()) + emit(persistentListOf()) } } @@ -49,9 +51,9 @@ internal class CalendarRepositoryImpl( query, intervalStart = now, intervalEnd = now + 14 * 24 * 60 * 60 * 1000L, - ) + ).toImmutableList() } else { - emptyList() + persistentListOf() } } diff --git a/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarSerialization.kt b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarSerialization.kt index 2c3132a3..6f8f8801 100644 --- a/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarSerialization.kt +++ b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarSerialization.kt @@ -7,15 +7,16 @@ import android.content.pm.PackageManager import android.provider.CalendarContract import androidx.core.content.ContextCompat import androidx.core.database.getStringOrNull +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.SearchableSerializer import de.mm20.launcher2.search.data.CalendarEvent -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import org.json.JSONObject import java.util.* class CalendarEventSerializer: SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as CalendarEvent val json = JSONObject() json.put("id", searchable.id) @@ -27,7 +28,7 @@ class CalendarEventSerializer: SearchableSerializer { } class CalendarEventDeserializer(val context: Context): SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable? { if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) return null val json = JSONObject(serialized) val id = json.getLong("id") diff --git a/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt b/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt index 6d3e2c40..421de09d 100644 --- a/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt +++ b/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt @@ -3,13 +3,17 @@ package de.mm20.launcher2.search.data import android.content.ContentUris import android.content.Context import android.content.Intent +import android.os.Bundle import android.provider.CalendarContract import de.mm20.launcher2.icons.ColorLayer import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.icons.TextLayer +import de.mm20.launcher2.ktx.tryStartActivity +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import java.text.SimpleDateFormat -class CalendarEvent( +data class CalendarEvent( override val label: String, val id: Long, val color: Int, @@ -19,12 +23,20 @@ class CalendarEvent( val location: String, val attendees: List, val description: String, - val calendar: Long -) : Searchable() { + val calendar: Long, + override val labelOverride: String? = null, +) : PinnableSearchable { + + override val domain: String = Domain override val key: String - get() = "calendar://$id" + get() = "$domain://$id" + override val preferDetailsOverLaunch: Boolean = true + + override fun overrideLabel(label: String): CalendarEvent { + return this.copy(labelOverride = label) + } override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { val df = SimpleDateFormat("dd") @@ -37,10 +49,18 @@ class CalendarEvent( ) } - override fun getLaunchIntent(context: Context): Intent { + private fun getLaunchIntent(): Intent { val uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, id) return Intent(Intent.ACTION_VIEW).setData(uri).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } + + override fun launch(context: Context, options: Bundle?): Boolean { + return context.tryStartActivity(getLaunchIntent(), options) + } + + companion object { + const val Domain = "calendar" + } } data class UserCalendar( diff --git a/contacts/build.gradle.kts b/contacts/build.gradle.kts index 449b23bc..66db85ae 100644 --- a/contacts/build.gradle.kts +++ b/contacts/build.gradle.kts @@ -42,7 +42,6 @@ dependencies { implementation(libs.koin.android) - implementation(project(":search")) implementation(project(":preferences")) implementation(project(":ktx")) implementation(project(":base")) diff --git a/contacts/src/main/java/de/mm20/launcher2/contacts/ContactRepository.kt b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactRepository.kt index 0623b793..75747382 100644 --- a/contacts/src/main/java/de/mm20/launcher2/contacts/ContactRepository.kt +++ b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactRepository.kt @@ -5,16 +5,18 @@ import android.provider.ContactsContract 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.Contact +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject -interface ContactRepository { - fun search(query: String): Flow> -} +interface ContactRepository: SearchableRepository internal class ContactRepositoryImpl( private val context: Context, @@ -23,13 +25,13 @@ internal class ContactRepositoryImpl( private val permissionsManager: PermissionsManager by inject() private val dataStore: LauncherDataStore by inject() - override fun search(query: String): Flow> { + override fun search(query: String): Flow> { val searchContacts = dataStore.data.map { it.contactsSearch.enabled } val hasPermission = permissionsManager.hasPermission(PermissionGroup.Contacts) if (query.length < 3) { return flow { - emit(emptyList()) + emit(persistentListOf()) } } @@ -39,12 +41,12 @@ internal class ContactRepositoryImpl( if (it) { queryContacts(query) } else { - emptyList() + persistentListOf() } } } - private suspend fun queryContacts(query: String): List { + private suspend fun queryContacts(query: String): ImmutableList { val results = withContext(Dispatchers.IO) { val proj = arrayOf( ContactsContract.RawContacts.CONTACT_ID, @@ -67,6 +69,6 @@ internal class ContactRepositoryImpl( } results } - return results + return results.toImmutableList() } } \ No newline at end of file diff --git a/contacts/src/main/java/de/mm20/launcher2/contacts/ContactSerialization.kt b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactSerialization.kt index 90da5ee3..8806b55f 100644 --- a/contacts/src/main/java/de/mm20/launcher2/contacts/ContactSerialization.kt +++ b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactSerialization.kt @@ -6,14 +6,15 @@ import android.content.pm.PackageManager import android.provider.ContactsContract import androidx.core.content.ContextCompat import de.mm20.launcher2.ktx.jsonObjectOf +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.SearchableSerializer import de.mm20.launcher2.search.data.Contact -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import org.json.JSONObject class ContactSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as Contact return jsonObjectOf( "id" to searchable.id @@ -25,7 +26,7 @@ class ContactSerializer : SearchableSerializer { } class ContactDeserializer(val context: Context) : SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable? { if (ContextCompat.checkSelfPermission( context, Manifest.permission.READ_CONTACTS diff --git a/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt b/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt index e1a047d6..e3b5e084 100644 --- a/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt +++ b/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt @@ -3,20 +3,21 @@ package de.mm20.launcher2.search.data import android.content.ContentUris import android.content.Context import android.content.Intent -import android.graphics.Color import android.net.Uri +import android.os.Bundle import android.provider.ContactsContract -import androidx.core.content.ContextCompat import androidx.core.database.getStringOrNull import androidx.core.graphics.drawable.toDrawable -import de.mm20.launcher2.contacts.R import de.mm20.launcher2.icons.* import de.mm20.launcher2.ktx.asBitmap +import de.mm20.launcher2.ktx.tryStartActivity +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.net.URLEncoder -class Contact( +data class Contact( val id: Long, val firstName: String, val lastName: String, @@ -27,12 +28,21 @@ class Contact( val telegram: Set, val whatsapp: Set, val postals: Set, -) : Searchable() { + override val labelOverride: String? = null +) : Searchable, PinnableSearchable { + + override val domain: String = Domain override val key: String - get() = "contact://$id" + get() = "${Domain}://$id" override val label: String get() = "$firstName $lastName" + override fun overrideLabel(label: String): Contact { + return this.copy(labelOverride = label) + } + + override val preferDetailsOverLaunch: Boolean = true + val summary: String get() { return phones.union(emails).joinToString(separator = ", ") { it.label } @@ -69,7 +79,12 @@ class Contact( ) } - override fun getLaunchIntent(context: Context): Intent { + override fun launch(context: Context, options: Bundle?): Boolean { + val intent = getLaunchIntent() + return context.tryStartActivity(intent, options) + } + + private fun getLaunchIntent(): Intent { val uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id) return Intent(Intent.ACTION_VIEW).setData(uri).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -202,7 +217,10 @@ class Contact( ) } + const val Domain = "contact" + } + } data class ContactInfo( diff --git a/customattrs/build.gradle.kts b/customattrs/build.gradle.kts index 25da3f2f..01e2bd0d 100644 --- a/customattrs/build.gradle.kts +++ b/customattrs/build.gradle.kts @@ -41,7 +41,7 @@ dependencies { implementation(libs.koin.android) implementation(project(":database")) - implementation(project(":search")) + implementation(project(":base")) implementation(project(":ktx")) implementation(project(":crashreporter")) implementation(project(":favorites")) diff --git a/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt b/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt index fefa61fe..b8f9ae4f 100644 --- a/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt +++ b/customattrs/src/main/java/de/mm20/launcher2/customattrs/CustomAttributesRepository.kt @@ -5,7 +5,7 @@ import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.database.entities.CustomAttributeEntity import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.ktx.jsonObjectOf -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -15,24 +15,24 @@ import org.json.JSONException import java.io.File interface CustomAttributesRepository { - fun getCustomIcon(searchable: Searchable): Flow - fun setCustomIcon(searchable: Searchable, icon: CustomIcon?) + fun getCustomIcon(searchable: PinnableSearchable): Flow + fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?) - fun getCustomLabels(items: List): Flow> - fun setCustomLabel(searchable: Searchable, label: String) - fun clearCustomLabel(searchable: Searchable) + fun getCustomLabels(items: List): Flow> + fun setCustomLabel(searchable: PinnableSearchable, label: String) + fun clearCustomLabel(searchable: PinnableSearchable) - fun setTags(searchable: Searchable, tags: List) - fun getTags(searchable: Searchable): Flow> + fun setTags(searchable: PinnableSearchable, tags: List) + fun getTags(searchable: PinnableSearchable): Flow> - suspend fun search(query: String): Flow> + suspend fun search(query: String): Flow> suspend fun export(toDir: File) suspend fun import(fromDir: File) suspend fun getAllTags(startsWith: String? = null): List - fun getItemsForTag(tag: String): Flow> - fun addTag(item: Searchable, tag: String) + fun getItemsForTag(tag: String): Flow> + fun addTag(item: PinnableSearchable, tag: String) suspend fun cleanupDatabase(): Int } @@ -42,7 +42,7 @@ internal class CustomAttributesRepositoryImpl( ) : CustomAttributesRepository { private val scope = CoroutineScope(Job() + Dispatchers.Default) - override fun getCustomIcon(searchable: Searchable): Flow { + override fun getCustomIcon(searchable: PinnableSearchable): Flow { val dao = appDatabase.customAttrsDao() return dao.getCustomAttribute(searchable.key, CustomAttributeType.Icon.value) .map { @@ -50,7 +50,7 @@ internal class CustomAttributesRepositoryImpl( } } - override fun setCustomIcon(searchable: Searchable, icon: CustomIcon?) { + override fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?) { val dao = appDatabase.customAttrsDao() scope.launch { dao.clearCustomAttribute(searchable.key, CustomAttributeType.Icon.value) @@ -60,7 +60,7 @@ internal class CustomAttributesRepositoryImpl( } } - override fun getCustomLabels(items: List): Flow> { + override fun getCustomLabels(items: List): Flow> { val dao = appDatabase.customAttrsDao() return dao.getCustomAttributes(items.map { it.key }, CustomAttributeType.Label.value) .map { list -> @@ -68,7 +68,7 @@ internal class CustomAttributesRepositoryImpl( } } - override fun setCustomLabel(searchable: Searchable, label: String) { + override fun setCustomLabel(searchable: PinnableSearchable, label: String) { val dao = appDatabase.customAttrsDao() scope.launch { favoritesRepository.save(searchable) @@ -84,14 +84,14 @@ internal class CustomAttributesRepositoryImpl( } } - override fun clearCustomLabel(searchable: Searchable) { + override fun clearCustomLabel(searchable: PinnableSearchable) { val dao = appDatabase.customAttrsDao() scope.launch { dao.clearCustomAttribute(searchable.key, CustomAttributeType.Label.value) } } - override fun setTags(searchable: Searchable, tags: List) { + override fun setTags(searchable: PinnableSearchable, tags: List) { val dao = appDatabase.customAttrsDao() scope.launch { favoritesRepository.save(searchable) @@ -101,7 +101,7 @@ internal class CustomAttributesRepositoryImpl( } } - override fun getTags(searchable: Searchable): Flow> { + override fun getTags(searchable: PinnableSearchable): Flow> { val dao = appDatabase.customAttrsDao() return dao.getCustomAttributes(listOf(searchable.key), CustomAttributeType.Tag.value).map { it.map { it.value } @@ -117,21 +117,21 @@ internal class CustomAttributesRepositoryImpl( } } - override fun getItemsForTag(tag: String): Flow> { + override fun getItemsForTag(tag: String): Flow> { val dao = appDatabase.customAttrsDao() return dao.getItemsWithTag(tag).map { favoritesRepository.getFromKeys(it) } } - override fun addTag(item: Searchable, tag: String) { + override fun addTag(item: PinnableSearchable, tag: String) { val dao = appDatabase.customAttrsDao() scope.launch { dao.addTag(item.key, tag) } } - override suspend fun search(query: String): Flow> { + override suspend fun search(query: String): Flow> { if (query.isBlank()) { return flow { emit(emptyList()) diff --git a/favorites/build.gradle.kts b/favorites/build.gradle.kts index b5030a44..49f46fd8 100644 --- a/favorites/build.gradle.kts +++ b/favorites/build.gradle.kts @@ -43,7 +43,6 @@ dependencies { implementation(libs.koin.android) implementation(project(":base")) - implementation(project(":search")) implementation(project(":calendar")) implementation(project(":database")) implementation(project(":preferences")) diff --git a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesItem.kt b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesItem.kt index fc9e14c0..9c7bf96f 100644 --- a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesItem.kt +++ b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesItem.kt @@ -1,15 +1,16 @@ package de.mm20.launcher2.favorites import de.mm20.launcher2.database.entities.FavoritesItemEntity +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SearchableSerializer -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable data class FavoritesItem( val key: String, /** * null if searchable could not be deserialized (i.e. the app has been uninstalled) */ - val searchable: Searchable?, + val searchable: PinnableSearchable?, var launchCount: Int, var pinPosition: Int, var hidden: Boolean diff --git a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesRepository.kt b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesRepository.kt index 62692de1..662d0316 100644 --- a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesRepository.kt +++ b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesRepository.kt @@ -5,11 +5,10 @@ import android.util.Log import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.database.entities.FavoritesItemEntity -import de.mm20.launcher2.ktx.ceilToInt import de.mm20.launcher2.ktx.jsonObjectOf +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.data.CalendarEvent -import de.mm20.launcher2.search.data.Searchable import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import org.json.JSONArray @@ -34,47 +33,47 @@ interface FavoritesRepository { automaticallySorted: Boolean = false, frequentlyUsed: Boolean = false, limit: Int = 100 - ): Flow> + ): Flow> - fun getPinnedCalendarEvents(): Flow> + fun getPinnedCalendarEvents(): Flow> fun getHiddenCalendarEventKeys(): Flow> - fun isPinned(searchable: Searchable): Flow - fun pinItem(searchable: Searchable) - fun unpinItem(searchable: Searchable) - fun isHidden(searchable: Searchable): Flow - fun hideItem(searchable: Searchable) - fun unhideItem(searchable: Searchable) - fun incrementLaunchCounter(searchable: Searchable) + fun isPinned(searchable: PinnableSearchable): Flow + fun pinItem(searchable: PinnableSearchable) + fun unpinItem(searchable: PinnableSearchable) + fun isHidden(searchable: PinnableSearchable): Flow + fun hideItem(searchable: PinnableSearchable) + fun unhideItem(searchable: PinnableSearchable) + fun incrementLaunchCounter(searchable: PinnableSearchable) fun updateFavorites( - manuallySorted: List, - automaticallySorted: List, + manuallySorted: List, + automaticallySorted: List, ) - fun getHiddenItems(): Flow> + fun getHiddenItems(): Flow> fun getHiddenItemKeys(): Flow> /** * Remove this item from the Searchable database */ - fun remove(searchable: Searchable) + fun remove(searchable: PinnableSearchable) /** * Remove this item from favorites and reset launch counter */ - fun removeFromFavorites(searchable: Searchable) + fun removeFromFavorites(searchable: PinnableSearchable) /** * Ensure that this searchable exists in the Favorites table. * If it doesn't exist, insert it with 0 launch count, not pinned and not hidden */ - fun save(searchable: Searchable) + fun save(searchable: PinnableSearchable) /** * Get items with the given keys from the favorites database. * Items that don't exist in the database will not be returned. */ - suspend fun getFromKeys(keys: List): List + suspend fun getFromKeys(keys: List): List suspend fun export(toDir: File) suspend fun import(fromDir: File) @@ -101,7 +100,7 @@ internal class FavoritesRepositoryImpl( automaticallySorted: Boolean, frequentlyUsed: Boolean, limit: Int - ): Flow> { + ): Flow> { val dao = database.searchDao() val entities = when { includeTypes == null && excludeTypes == null -> dao.getFavorites( @@ -150,11 +149,11 @@ internal class FavoritesRepositoryImpl( return database.searchDao().getHiddenCalendarEventKeys() } - override fun isPinned(searchable: Searchable): Flow { + override fun isPinned(searchable: PinnableSearchable): Flow { return AppDatabase.getInstance(context).searchDao().isPinned(searchable.key) } - override fun pinItem(searchable: Searchable) { + override fun pinItem(searchable: PinnableSearchable) { scope.launch { withContext(Dispatchers.IO) { val dao = AppDatabase.getInstance(context).searchDao() @@ -171,7 +170,7 @@ internal class FavoritesRepositoryImpl( } } - override fun unpinItem(searchable: Searchable) { + override fun unpinItem(searchable: PinnableSearchable) { scope.launch { withContext(Dispatchers.IO) { AppDatabase.getInstance(context).searchDao().unpinFavorite(searchable.key) @@ -179,11 +178,11 @@ internal class FavoritesRepositoryImpl( } } - override fun isHidden(searchable: Searchable): Flow { + override fun isHidden(searchable: PinnableSearchable): Flow { return AppDatabase.getInstance(context).searchDao().isHidden(searchable.key) } - override fun hideItem(searchable: Searchable) { + override fun hideItem(searchable: PinnableSearchable) { scope.launch { withContext(Dispatchers.IO) { val dao = AppDatabase.getInstance(context).searchDao() @@ -200,7 +199,7 @@ internal class FavoritesRepositoryImpl( } } - override fun unhideItem(searchable: Searchable) { + override fun unhideItem(searchable: PinnableSearchable) { scope.launch { withContext(Dispatchers.IO) { AppDatabase.getInstance(context).searchDao().unhideItem(searchable.key) @@ -208,7 +207,7 @@ internal class FavoritesRepositoryImpl( } } - override fun incrementLaunchCounter(searchable: Searchable) { + override fun incrementLaunchCounter(searchable: PinnableSearchable) { scope.launch { withContext(Dispatchers.IO) { val item = FavoritesItem(searchable.key, searchable, 0, 0, false) @@ -220,7 +219,7 @@ internal class FavoritesRepositoryImpl( } } - override fun getHiddenItems(): Flow> { + override fun getHiddenItems(): Flow> { return database.searchDao().getHiddenItems().map { it.mapNotNull { fromDatabaseEntity(it).searchable } } @@ -230,7 +229,7 @@ internal class FavoritesRepositoryImpl( return database.searchDao().getHiddenItemKeys() } - override fun remove(searchable: Searchable) { + override fun remove(searchable: PinnableSearchable) { scope.launch { withContext(Dispatchers.IO) { database.searchDao().deleteByKey(searchable.key) @@ -238,13 +237,13 @@ internal class FavoritesRepositoryImpl( } } - override fun removeFromFavorites(searchable: Searchable) { + override fun removeFromFavorites(searchable: PinnableSearchable) { scope.launch { database.searchDao().resetPinStatusAndLaunchCounter(searchable.key) } } - override fun save(searchable: Searchable) { + override fun save(searchable: PinnableSearchable) { scope.launch { withContext(Dispatchers.IO) { val entity = FavoritesItem( @@ -260,8 +259,8 @@ internal class FavoritesRepositoryImpl( } override fun updateFavorites( - manuallySorted: List, - automaticallySorted: List + manuallySorted: List, + automaticallySorted: List ) { val dao = database.searchDao() scope.launch { @@ -321,7 +320,7 @@ internal class FavoritesRepositoryImpl( } } - override suspend fun getFromKeys(keys: List): List { + override suspend fun getFromKeys(keys: List): List { val dao = database.searchDao() return dao.getFromKeys(keys) .mapNotNull { fromDatabaseEntity(it).searchable } diff --git a/favorites/src/main/java/de/mm20/launcher2/favorites/Serialization.kt b/favorites/src/main/java/de/mm20/launcher2/favorites/Serialization.kt index 3fcc628f..3e441461 100644 --- a/favorites/src/main/java/de/mm20/launcher2/favorites/Serialization.kt +++ b/favorites/src/main/java/de/mm20/launcher2/favorites/Serialization.kt @@ -12,6 +12,7 @@ import de.mm20.launcher2.contacts.ContactSerializer import de.mm20.launcher2.files.* import de.mm20.launcher2.search.NullDeserializer import de.mm20.launcher2.search.NullSerializer +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.SearchableSerializer import de.mm20.launcher2.search.data.* diff --git a/favorites/src/main/java/de/mm20/launcher2/favorites/TagSerialization.kt b/favorites/src/main/java/de/mm20/launcher2/favorites/TagSerialization.kt index 6ef74007..fd2b7209 100644 --- a/favorites/src/main/java/de/mm20/launcher2/favorites/TagSerialization.kt +++ b/favorites/src/main/java/de/mm20/launcher2/favorites/TagSerialization.kt @@ -1,14 +1,14 @@ package de.mm20.launcher2.favorites -import android.content.Context +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.SearchableSerializer -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.Tag import org.json.JSONObject class TagSerializer: SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as Tag val json = JSONObject() json.put("tag", searchable.tag) @@ -20,7 +20,7 @@ class TagSerializer: SearchableSerializer { } class TagDeserializer: SearchableDeserializer { - override fun deserialize(serialized: String): Searchable { + override fun deserialize(serialized: String): PinnableSearchable { val json = JSONObject(serialized) return Tag(json.getString("tag")) diff --git a/favorites/src/main/java/de/mm20/launcher2/search/data/Tag.kt b/favorites/src/main/java/de/mm20/launcher2/search/data/Tag.kt index 25765382..eea2f66c 100644 --- a/favorites/src/main/java/de/mm20/launcher2/search/data/Tag.kt +++ b/favorites/src/main/java/de/mm20/launcher2/search/data/Tag.kt @@ -1,20 +1,40 @@ package de.mm20.launcher2.search.data import android.content.Context +import android.os.Bundle import de.mm20.launcher2.icons.ColorLayer import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.icons.TextLayer +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable -class Tag( +data class Tag( val tag: String, -): Searchable() { - override val key: String = "tag://$tag" + override val labelOverride: String? = null +): PinnableSearchable { + + override val domain: String = Domain + + override val key: String = "$domain://$tag" override val label: String = tag + override val preferDetailsOverLaunch: Boolean = true + + override fun launch(context: Context, options: Bundle?): Boolean { + return false + } + override fun overrideLabel(label: String): PinnableSearchable { + return this.copy(labelOverride = label) + } + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { return StaticLauncherIcon( foregroundLayer = TextLayer("#"), backgroundLayer = ColorLayer() ) } + + companion object { + const val Domain = "tag" + } } \ No newline at end of file diff --git a/files/build.gradle.kts b/files/build.gradle.kts index 3abf2408..8dc0323d 100644 --- a/files/build.gradle.kts +++ b/files/build.gradle.kts @@ -44,7 +44,6 @@ dependencies { implementation(libs.koin.android) - implementation(project(":search")) implementation(project(":preferences")) implementation(project(":base")) implementation(project(":ktx")) diff --git a/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt b/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt index f177cede..c2d03661 100644 --- a/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt +++ b/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt @@ -1,12 +1,13 @@ package de.mm20.launcher2.files import android.content.Context -import android.provider.DocumentsContract import android.provider.MediaStore import androidx.core.database.getStringOrNull import de.mm20.launcher2.ktx.jsonObjectOf import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.SearchableSerializer import de.mm20.launcher2.search.data.* @@ -15,7 +16,7 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.get class LocalFileSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as LocalFile return jsonObjectOf( "id" to searchable.id @@ -29,7 +30,7 @@ class LocalFileSerializer : SearchableSerializer { class LocalFileDeserializer( val context: Context ) : SearchableDeserializer, KoinComponent { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable? { val permissionsManager: PermissionsManager = get() if (!permissionsManager.checkPermissionOnce( PermissionGroup.ExternalStorage @@ -74,7 +75,7 @@ class LocalFileDeserializer( } class GDriveFileSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as GDriveFile return jsonObjectOf( "id" to searchable.fileId, @@ -103,7 +104,7 @@ class GDriveFileSerializer : SearchableSerializer { } class GDriveFileDeserializer : SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable { val json = JSONObject(serialized) val id = json.getString("id") val label = json.getString("label") @@ -134,7 +135,7 @@ class GDriveFileDeserializer : SearchableDeserializer { } class OneDriveFileSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as OneDriveFile return jsonObjectOf( "id" to searchable.fileId, @@ -161,7 +162,7 @@ class OneDriveFileSerializer : SearchableSerializer { } class OneDriveFileDeserializer : SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable { val json = JSONObject(serialized) val fileId = json.getString("id") val label = json.getString("label") @@ -189,10 +190,10 @@ class OneDriveFileDeserializer : SearchableDeserializer { } class NextcloudFileSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as NextcloudFile return jsonObjectOf( - "id" to searchable.id, + "id" to searchable.fileId, "label" to searchable.label, "path" to searchable.path, "mimeType" to searchable.mimeType, @@ -216,7 +217,7 @@ class NextcloudFileSerializer : SearchableSerializer { } class NextcloudFileDeserializer : SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable { val json = JSONObject(serialized) val id = json.getLong("id") val label = json.getString("label") @@ -242,10 +243,10 @@ class NextcloudFileDeserializer : SearchableDeserializer { } class OwncloudFileSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as OwncloudFile return jsonObjectOf( - "id" to searchable.id, + "id" to searchable.fileId, "label" to searchable.label, "path" to searchable.path, "mimeType" to searchable.mimeType, @@ -269,7 +270,7 @@ class OwncloudFileSerializer : SearchableSerializer { } class OwncloudFileDeserializer : SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable { val json = JSONObject(serialized) val id = json.getLong("id") val label = json.getString("label") diff --git a/files/src/main/java/de/mm20/launcher2/files/FilesRepository.kt b/files/src/main/java/de/mm20/launcher2/files/FilesRepository.kt index cbfb6b2c..2db7a14d 100644 --- a/files/src/main/java/de/mm20/launcher2/files/FilesRepository.kt +++ b/files/src/main/java/de/mm20/launcher2/files/FilesRepository.kt @@ -6,15 +6,18 @@ import de.mm20.launcher2.nextcloud.NextcloudApiHelper import de.mm20.launcher2.owncloud.OwncloudClient import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.preferences.LauncherDataStore +import de.mm20.launcher2.search.SearchableRepository import de.mm20.launcher2.search.data.File +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 import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -interface FileRepository { - fun search(query: String): Flow> +interface FileRepository: SearchableRepository { fun deleteFile(file: File) } @@ -60,21 +63,21 @@ internal class FileRepositoryImpl( } } - override fun search(query: String): Flow> = channelFlow { + override fun search(query: String): Flow> = channelFlow { if (query.isBlank()) { - send(emptyList()) + send(persistentListOf()) return@channelFlow } providers.collectLatest { providers -> if (providers.isEmpty()) { - send(emptyList()) + send(persistentListOf()) return@collectLatest } val results = mutableListOf() for (provider in providers) { results.addAll(provider.search(query)) - send(results.toList()) + send(results.toImmutableList()) } } } diff --git a/files/src/main/java/de/mm20/launcher2/search/data/File.kt b/files/src/main/java/de/mm20/launcher2/search/data/File.kt index 43570b78..ddea7e23 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/File.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/File.kt @@ -1,25 +1,29 @@ package de.mm20.launcher2.search.data import android.content.Context -import android.graphics.Color import androidx.core.content.ContextCompat import de.mm20.launcher2.files.R import de.mm20.launcher2.icons.ColorLayer import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.icons.TintedIconLayer +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import java.util.* -abstract class File( - val id: Long, - val path: String, - val mimeType: String, - val size: Long, - val isDirectory: Boolean, +interface File : PinnableSearchable { + val path: String + val mimeType: String + val size: Long + val isDirectory: Boolean val metaData: List> -) : Searchable() { - abstract val isStoredInCloud: Boolean - open val providerIconRes: Int? = null + val isStoredInCloud: Boolean + + override val preferDetailsOverLaunch: Boolean + get() = false + + open val providerIconRes: Int? + get() = null override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { val (resId, bgColor) = when { @@ -124,7 +128,8 @@ abstract class File( return context.getString(resource) } - open val isDeletable: Boolean = false - open suspend fun delete(context: Context) {} + val isDeletable: Boolean + get() = false + suspend fun delete(context: Context) {} } \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/search/data/GDriveFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/GDriveFile.kt index b47fd6ee..08157875 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/GDriveFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/GDriveFile.kt @@ -3,30 +3,47 @@ package de.mm20.launcher2.search.data import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Bundle import de.mm20.launcher2.files.R +import de.mm20.launcher2.ktx.tryStartActivity -class GDriveFile( +data class GDriveFile( val fileId: String, override val label: String, - path: String, - mimeType: String, - size: Long, - isDirectory: Boolean, - metaData: List>, + override val path: String, + override val mimeType: String, + override val size: Long, + override val isDirectory: Boolean, + override val metaData: List>, val directoryColor: String?, - val viewUri: String -) : File(0, path, mimeType, size, isDirectory, metaData) { + val viewUri: String, + override val labelOverride: String? = null, +) : File { - override val key: String = "gdrive://$fileId" + override fun overrideLabel(label: String): GDriveFile { + return this.copy(labelOverride = label) + } + + override val domain: String = Domain + + override val key: String = "$domain://$fileId" override val isStoredInCloud = true override val providerIconRes = R.drawable.ic_badge_gdrive - override fun getLaunchIntent(context: Context): Intent? { + private fun getLaunchIntent(): Intent { return Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(viewUri) flags = Intent.FLAG_ACTIVITY_NEW_TASK } } + + override fun launch(context: Context, options: Bundle?): Boolean { + return context.tryStartActivity(getLaunchIntent(), options) + } + + companion object { + const val Domain = "gdrive" + } } \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt index 29e3d734..8a5d882d 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt @@ -9,6 +9,7 @@ import android.location.Geocoder import android.media.MediaMetadataRetriever import android.media.ThumbnailUtils import android.net.Uri +import android.os.Bundle import android.provider.MediaStore import android.text.format.DateUtils import android.util.Size @@ -17,25 +18,34 @@ import androidx.exifinterface.media.ExifInterface import de.mm20.launcher2.files.R import de.mm20.launcher2.icons.* import de.mm20.launcher2.ktx.formatToString +import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.media.ThumbnailUtilsCompat +import de.mm20.launcher2.search.PinnableSearchable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import java.io.IOException import java.io.File as JavaIOFile -open class LocalFile( - id: Long, - path: String, - mimeType: String, - size: Long, - isDirectory: Boolean, - metaData: List> -) : File(id, path, mimeType, size, isDirectory, metaData) { +data class LocalFile( + val id: Long, + override val path: String, + override val mimeType: String, + override val size: Long, + override val isDirectory: Boolean, + override val metaData: List>, + override val labelOverride: String? = null +) : File { override val label = path.substringAfterLast('/') - override val key = "file://$path" + override fun overrideLabel(label: String): LocalFile { + return this.copy(labelOverride = label) + } + + override val domain: String = Domain + + override val key = "$domain://$path" override val isStoredInCloud = false @@ -148,7 +158,7 @@ open class LocalFile( } - override fun getLaunchIntent(context: Context): Intent? { + private fun getLaunchIntent(context: Context): Intent { val uri = if (isDirectory) { Uri.parse(path) } else { @@ -162,6 +172,10 @@ open class LocalFile( .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION) } + override fun launch(context: Context, options: Bundle?): Boolean { + return context.tryStartActivity(getLaunchIntent(context), options) + } + override val isDeletable: Boolean get() { val file = java.io.File(path) @@ -186,6 +200,9 @@ open class LocalFile( companion object { + + const val Domain = "file" + internal fun getMimetypeByFileExtension(extension: String): String { return when (extension) { "apk" -> "application/vnd.android.package-archive" diff --git a/files/src/main/java/de/mm20/launcher2/search/data/NextcloudFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/NextcloudFile.kt index 53eb09a8..cb2a7d90 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/NextcloudFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/NextcloudFile.kt @@ -4,34 +4,50 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri +import android.os.Bundle import de.mm20.launcher2.files.R +import de.mm20.launcher2.ktx.tryStartActivity -class NextcloudFile( - fileId: Long, +data class NextcloudFile( + val fileId: Long, override val label: String, - path: String, - mimeType: String, - size: Long, - isDirectory: Boolean, + override val path: String, + override val mimeType: String, + override val size: Long, + override val isDirectory: Boolean, val server: String, - metaData: List> -) : File(fileId, path, mimeType, size, isDirectory, metaData) { - override val key: String = "nextcloud://$server/$fileId" + override val metaData: List>, + override val labelOverride: String? = null, +) : File { + + override fun overrideLabel(label: String): NextcloudFile { + return this.copy(labelOverride = label) + } + + override val domain: String = Domain + + override val key: String = "$domain://$server/$fileId" override val isStoredInCloud: Boolean get() = true override val providerIconRes = R.drawable.ic_badge_nextcloud - override fun getLaunchIntent(context: Context): Intent? { + private fun getLaunchIntent(context: Context): Intent { return Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse("$server/f/$id") + data = Uri.parse("$server/f/$fileId") flags = Intent.FLAG_ACTIVITY_NEW_TASK `package` = getNextcloudAppPackage(context) } } + override fun launch(context: Context, options: Bundle?): Boolean { + return context.tryStartActivity(getLaunchIntent(context), options) + } + companion object { + + const val Domain = "nextcloud" private fun getNextcloudAppPackage(context: Context): String? { val candidates = listOf("com.nextcloud.client", "com.nextcloud.android.beta") diff --git a/files/src/main/java/de/mm20/launcher2/search/data/OneDriveFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/OneDriveFile.kt index b667796e..42b40dbb 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/OneDriveFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/OneDriveFile.kt @@ -3,29 +3,46 @@ package de.mm20.launcher2.search.data import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Bundle import de.mm20.launcher2.files.R +import de.mm20.launcher2.ktx.tryStartActivity -class OneDriveFile( +data class OneDriveFile( val fileId: String, override val label: String, - path: String, - mimeType: String, - size: Long, - isDirectory: Boolean, - metaData: List>, - val webUrl: String -) : File(0, path, mimeType, size, isDirectory, metaData) { + override val path: String, + override val mimeType: String, + override val size: Long, + override val isDirectory: Boolean, + override val metaData: List>, + val webUrl: String, + override val labelOverride: String? = null, +) : File { - override val key: String = "onedrive://$fileId" + override fun overrideLabel(label: String): OneDriveFile { + return this.copy(labelOverride = label) + } + + override val domain: String = Domain + + override val key: String = "$domain://$fileId" override val providerIconRes = R.drawable.ic_badge_onedrive override val isStoredInCloud = true - override fun getLaunchIntent(context: Context): Intent? { + private fun getLaunchIntent(): Intent { return Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(webUrl) flags = Intent.FLAG_ACTIVITY_NEW_TASK } } + + override fun launch(context: Context, options: Bundle?): Boolean { + return context.tryStartActivity(getLaunchIntent(), options) + } + + companion object { + const val Domain = "onedrive" + } } \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/search/data/OwncloudFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/OwncloudFile.kt index d9adf20b..75bf48db 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/OwncloudFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/OwncloudFile.kt @@ -3,29 +3,46 @@ package de.mm20.launcher2.search.data import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Bundle import de.mm20.launcher2.files.R +import de.mm20.launcher2.ktx.tryStartActivity -class OwncloudFile( - fileId: Long, +data class OwncloudFile( + val fileId: Long, override val label: String, - path: String, - mimeType: String, - size: Long, - isDirectory: Boolean, + override val path: String, + override val mimeType: String, + override val size: Long, + override val isDirectory: Boolean, val server: String, - metaData: List> -) : File(fileId, path, mimeType, size, isDirectory, metaData) { - override val key: String = "owncloud://$server/$fileId" + override val metaData: List>, + override val labelOverride: String? = null, +) : File { + + override fun overrideLabel(label: String): OwncloudFile { + return this.copy(labelOverride = label) + } + + override val domain: String = Domain + + override val key: String = "$domain://$server/$fileId" override val isStoredInCloud: Boolean get() = true override val providerIconRes = R.drawable.ic_badge_owncloud - override fun getLaunchIntent(context: Context): Intent? { + private fun getLaunchIntent(): Intent { return Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse("$server/f/$id") + data = Uri.parse("$server/f/$fileId") flags = Intent.FLAG_ACTIVITY_NEW_TASK } } + override fun launch(context: Context, options: Bundle?): Boolean { + return context.tryStartActivity(getLaunchIntent(), options) + } + + companion object { + const val Domain = "owncloud" + } } \ No newline at end of file diff --git a/icons/build.gradle.kts b/icons/build.gradle.kts index a475d2be..b62e2e02 100644 --- a/icons/build.gradle.kts +++ b/icons/build.gradle.kts @@ -50,7 +50,6 @@ dependencies { implementation(project(":preferences")) implementation(project(":ktx")) implementation(project(":base")) - implementation(project(":search")) implementation(project(":applications")) implementation(project(":crashreporter")) api(project(":customattrs")) diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt index c54a4c4e..db852bae 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt @@ -13,8 +13,9 @@ import de.mm20.launcher2.icons.transformations.LauncherIconTransformation import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation import de.mm20.launcher2.icons.transformations.transform import de.mm20.launcher2.preferences.LauncherDataStore +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.data.LauncherApp -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -98,7 +99,7 @@ class IconRepository( } - fun getIcon(searchable: Searchable, size: Int): Flow = channelFlow { + fun getIcon(searchable: PinnableSearchable, size: Int): Flow = channelFlow { iconProviders.collectLatest { providers -> transformations.collectLatest { transformations -> customAttributesRepository.getCustomIcon(searchable).collectLatest { customIcon -> @@ -189,7 +190,7 @@ class IconRepository( } suspend fun getCustomIconSuggestions( - searchable: Searchable, + searchable: PinnableSearchable, size: Int ): List { val suggestions = mutableListOf() @@ -301,7 +302,7 @@ class IconRepository( } - suspend fun getUncustomizedDefaultIcon(searchable: Searchable, size: Int): CustomIconWithPreview? { + suspend fun getUncustomizedDefaultIcon(searchable: PinnableSearchable, size: Int): CustomIconWithPreview? { val icon = iconProviders.first().getFirstIcon(searchable, size) ?.transform(transformations.first()) ?: return null return CustomIconWithPreview( @@ -338,7 +339,7 @@ class IconRepository( return iconPackIcons + themedIcons } - fun setCustomIcon(searchable: Searchable, icon: CustomIcon?) { + fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?) { customAttributesRepository.setCustomIcon(searchable, icon) } diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt index 8c77aefe..d75c8a98 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt @@ -3,16 +3,16 @@ package de.mm20.launcher2.icons.providers import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager -import android.graphics.drawable.ColorDrawable import de.mm20.launcher2.icons.DynamicCalendarIcon import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.ktx.obtainTypedArrayOrNull -import de.mm20.launcher2.search.data.Application -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable +import de.mm20.launcher2.search.data.LauncherApp class CalendarIconProvider(val context: Context): IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { - if(searchable !is Application) return null + override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? { + if(searchable !is LauncherApp) return null val component = ComponentName(searchable.`package`, searchable.activity) val pm = context.packageManager val ai = try { diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomIconPackIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomIconPackIconProvider.kt index b6db2607..7c4be7d4 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomIconPackIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomIconPackIconProvider.kt @@ -4,14 +4,14 @@ import android.content.ComponentName import de.mm20.launcher2.customattrs.CustomIconPackIcon import de.mm20.launcher2.icons.IconPackManager import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.search.data.LauncherApp -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable class CustomIconPackIconProvider( private val customIcon: CustomIconPackIcon, private val iconPackManager: IconPackManager, ) : IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? { return iconPackManager.getIcon( customIcon.iconPackPackage, ComponentName.unflattenFromString(customIcon.iconComponentName) ?: return null diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomThemedIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomThemedIconProvider.kt index 54d82181..a0178f77 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomThemedIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomThemedIconProvider.kt @@ -3,13 +3,14 @@ package de.mm20.launcher2.icons.providers import de.mm20.launcher2.customattrs.CustomThemedIcon import de.mm20.launcher2.icons.IconPackManager import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable class CustomThemedIconProvider( private val customIcon: CustomThemedIcon, private val iconPackManager: IconPackManager, ): IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? { return iconPackManager.getThemedIcon(customIcon.iconPackageName) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt index ed33a978..c86e9177 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt @@ -8,13 +8,13 @@ import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RotateDrawable import androidx.core.content.res.ResourcesCompat import de.mm20.launcher2.icons.* -import de.mm20.launcher2.search.data.Application -import de.mm20.launcher2.search.data.Searchable -import kotlin.math.roundToInt +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable +import de.mm20.launcher2.search.data.LauncherApp class GoogleClockIconProvider(val context: Context) : IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { - if (searchable !is Application) return null + override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? { + if (searchable !is LauncherApp) return null if (searchable.`package` != "com.google.android.deskclock") return null val pm = context.packageManager val appInfo = try { diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt index b400f14e..d722025e 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt @@ -3,8 +3,9 @@ package de.mm20.launcher2.icons.providers import android.content.ComponentName import android.content.Context import de.mm20.launcher2.icons.* +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.data.LauncherApp -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -13,7 +14,7 @@ class IconPackIconProvider( private val iconPack: String, private val iconPackManager: IconPackManager, ): IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? { if (searchable !is LauncherApp) return null val component = ComponentName(searchable.`package`, searchable.activity) diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt index d8f0a767..9c31ca01 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt @@ -1,14 +1,15 @@ package de.mm20.launcher2.icons.providers import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable interface IconProvider { - suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? + suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? } internal suspend fun Iterable.getFirstIcon( - searchable: Searchable, + searchable: PinnableSearchable, size: Int ): LauncherIcon? { for (provider in this) { diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt index c396c87a..fd27b717 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt @@ -2,10 +2,11 @@ package de.mm20.launcher2.icons.providers import android.content.Context import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable class PlaceholderIconProvider(val context: Context) : IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon { + override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon { return searchable.getPlaceholderIcon(context) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt index 06edc24d..56381ab1 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt @@ -2,13 +2,14 @@ package de.mm20.launcher2.icons.providers import android.content.Context import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable class SystemIconProvider( private val context: Context, private val themedIcons: Boolean, ) : IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? { return searchable.loadIcon(context, size, themedIcons) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedIconProvider.kt index 5f0ef52c..dbb05553 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedIconProvider.kt @@ -1,25 +1,16 @@ package de.mm20.launcher2.icons.providers -import android.content.ComponentName -import android.content.Context -import android.content.pm.PackageManager -import android.content.res.Resources -import android.graphics.drawable.LayerDrawable -import android.graphics.drawable.RotateDrawable -import androidx.core.content.res.ResourcesCompat -import de.mm20.launcher2.crashreporter.CrashReporter -import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.icons.* -import de.mm20.launcher2.ktx.obtainTypedArrayOrNull -import de.mm20.launcher2.search.data.Application -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable +import de.mm20.launcher2.search.data.LauncherApp internal class ThemedIconProvider( private val iconPackManager: IconPackManager, ) : IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { - if (searchable !is Application) return null + override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon? { + if (searchable !is LauncherApp) return null return iconPackManager.getThemedIcon(searchable.`package`) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedPlaceholderIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedPlaceholderIconProvider.kt index f896493a..7798f447 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedPlaceholderIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedPlaceholderIconProvider.kt @@ -2,13 +2,14 @@ package de.mm20.launcher2.icons.providers import android.content.Context import de.mm20.launcher2.icons.* -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable internal class ThemedPlaceholderIconProvider( private val context: Context, ) : IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon { + override suspend fun getIcon(searchable: PinnableSearchable, size: Int): LauncherIcon { val icon = searchable.getPlaceholderIcon(context) return StaticLauncherIcon( diff --git a/permissions/build.gradle.kts b/permissions/build.gradle.kts index f0906394..325f8571 100644 --- a/permissions/build.gradle.kts +++ b/permissions/build.gradle.kts @@ -43,6 +43,5 @@ dependencies { implementation(project(":ktx")) implementation(project(":base")) - implementation(project(":search")) implementation(project(":crashreporter")) } \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt b/search/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt deleted file mode 100644 index 8e8059be..00000000 --- a/search/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt +++ /dev/null @@ -1,14 +0,0 @@ -package de.mm20.launcher2.search - -import de.mm20.launcher2.search.data.Searchable - -interface SearchableDeserializer { - fun deserialize(serialized: String): Searchable? -} - -class NullDeserializer: SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { - return null - } - -} \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/data/Searchable.kt b/search/src/main/java/de/mm20/launcher2/search/data/Searchable.kt deleted file mode 100644 index a8ef36b3..00000000 --- a/search/src/main/java/de/mm20/launcher2/search/data/Searchable.kt +++ /dev/null @@ -1,68 +0,0 @@ -package de.mm20.launcher2.search.data - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.widget.Toast -import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.icons.StaticLauncherIcon -import de.mm20.launcher2.ktx.romanize -import de.mm20.launcher2.ktx.tryStartActivity -import de.mm20.launcher2.search.R -import java.text.Collator - -abstract class Searchable : Comparable { - - abstract val key: String - abstract val label: String - - var labelOverride: String? = null - - open fun serialize(): String = "" - - open fun getLaunchIntent(context: Context): Intent? = null - - open fun launch(context: Context, options: Bundle?): Boolean { - val intent = getLaunchIntent(context) ?: return false - return if (context.tryStartActivity(intent, options)) { - true - } else { - Toast.makeText( - context, - context.getString(R.string.error_activity_not_found, label), - Toast.LENGTH_SHORT - ).show() - false - } - } - - open suspend fun loadIcon( - context: Context, - size: Int, - themed: Boolean, - ): LauncherIcon? = null - - abstract fun getPlaceholderIcon(context: Context): StaticLauncherIcon - - override fun compareTo(other: Searchable): Int { - val label1 = labelOverride ?: label - val label2 = other.labelOverride ?: other.label - return Collator.getInstance().apply { strength = Collator.SECONDARY } - .compare(label1.romanize(), label2.romanize()) - } - - override fun equals(other: Any?): Boolean { - return if (other is Searchable) key == other.key && label == other.label && labelOverride == other.labelOverride - else super.equals(other) - } - - override fun hashCode(): Int { - var result = key.hashCode() - result = 31 * result + label.hashCode() - return result - } - - override fun toString(): String { - return "$label ($key)" - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt index fb966556..348ee6ae 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/common/FavoritesVM.kt @@ -5,7 +5,8 @@ import androidx.lifecycle.viewModelScope import de.mm20.launcher2.customattrs.CustomAttributesRepository import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.preferences.LauncherDataStore -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.Tag import de.mm20.launcher2.ui.utils.withCustomLabels import de.mm20.launcher2.widgets.WidgetRepository @@ -32,7 +33,7 @@ open class FavoritesVM : ViewModel(), KoinComponent { it.filterIsInstance() } - val favorites: Flow> = selectedTag.flatMapLatest { tag -> + val favorites: Flow> = selectedTag.flatMapLatest { tag -> if (tag == null) { val columns = dataStore.data.map { it.grid.columnCount } val excludeCalendar = widgetRepository.isCalendarWidgetEnabled() diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheet.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheet.kt index 86cd27cf..310198fa 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheet.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheet.kt @@ -71,7 +71,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.drawOutline @@ -90,7 +89,8 @@ import androidx.compose.ui.unit.toSize import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.MissingPermissionBanner @@ -739,7 +739,7 @@ fun ShortcutPicker(viewModel: EditFavoritesSheetVM) { } sealed interface FavoritesSheetGridItem { - class Favorite(val item: Searchable) : FavoritesSheetGridItem + class Favorite(val item: PinnableSearchable) : FavoritesSheetGridItem class Divider(val section: FavoritesSheetSection) : FavoritesSheetGridItem class Spacer(val span: Int = 1) : FavoritesSheetGridItem object EmptySection : FavoritesSheetGridItem diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheetVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheetVM.kt index f3d47f92..dfe1cb9e 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheetVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/EditFavoritesSheetVM.kt @@ -17,12 +17,12 @@ import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.icons.IconRepository import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.ktx.normalize -import de.mm20.launcher2.ktx.romanize import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.preferences.LauncherDataStore +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.data.AppShortcut -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.Tag import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first @@ -48,9 +48,9 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent { val createShortcutTarget = MutableLiveData(null) - private var manuallySorted: MutableList = mutableListOf() - private var automaticallySorted: MutableList = mutableListOf() - private var frequentlyUsed: MutableList = mutableListOf() + private var manuallySorted: MutableList = mutableListOf() + private var automaticallySorted: MutableList = mutableListOf() + private var frequentlyUsed: MutableList = mutableListOf() val pinnedTags = MutableLiveData>(emptyList()) val availableTags = MutableLiveData>(emptyList()) @@ -179,7 +179,7 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent { ) } - fun getIcon(searchable: Searchable, size: Int): Flow { + fun getIcon(searchable: PinnableSearchable, size: Int): Flow { return iconRepository.getIcon(searchable, size) } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsSheet.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsSheet.kt index 27bdaf29..8d214c11 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsSheet.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/modals/HiddenItemsSheet.kt @@ -16,14 +16,15 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid @Composable fun HiddenItemsSheet( - items: List, + items: List, onDismiss: () -> Unit ) { val viewModel: HiddenItemsSheetVM = viewModel() diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt index 356e6932..f9f95089 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchColumn.kt @@ -43,7 +43,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.LauncherCard import de.mm20.launcher2.ui.component.MissingPermissionBanner @@ -381,7 +382,7 @@ fun SearchColumn( } fun LazyListScope.GridResults( - items: ImmutableList, + items: ImmutableList, columns: Int, reverse: Boolean, showLabels: Boolean, @@ -446,7 +447,7 @@ fun LazyListScope.GridResults( @Composable fun GridRow( modifier: Modifier = Modifier, - items: ImmutableList, + items: ImmutableList, columns: Int, showLabels: Boolean, ) { @@ -470,7 +471,7 @@ fun GridRow( } fun LazyListScope.ListResults( - items: ImmutableList, + items: ImmutableList, reverse: Boolean, key: String, before: (@Composable () -> Unit)? = null, @@ -523,7 +524,7 @@ fun LazyListScope.ListResults( @Composable fun ListRow( modifier: Modifier = Modifier, - item: Searchable, + item: PinnableSearchable, ) { Box( modifier = modifier.padding( diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt index 2bfa1cf5..7f767130 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt @@ -16,12 +16,13 @@ import de.mm20.launcher2.files.FileRepository import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.preferences.LauncherDataStore +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.WebsearchRepository import de.mm20.launcher2.search.data.* import de.mm20.launcher2.ui.utils.withCustomLabels import de.mm20.launcher2.unitconverter.UnitConverterRepository import de.mm20.launcher2.websites.WebsiteRepository -import de.mm20.launcher2.widgets.WidgetRepository import de.mm20.launcher2.wikipedia.WikipediaRepository import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -52,8 +53,8 @@ class SearchVM : ViewModel(), KoinComponent { val showLabels = dataStore.data.map { it.grid.showLabels }.asLiveData() - val appResults = MutableLiveData>(emptyList()) - val workAppResults = MutableLiveData>(emptyList()) + val appResults = MutableLiveData>(emptyList()) + val workAppResults = MutableLiveData>(emptyList()) val appShortcutResults = MutableLiveData>(emptyList()) val fileResults = MutableLiveData>(emptyList()) val contactResults = MutableLiveData>(emptyList()) @@ -64,7 +65,7 @@ class SearchVM : ViewModel(), KoinComponent { val unitConverterResult = MutableLiveData(null) val websearchResults = MutableLiveData>(emptyList()) - val hiddenResults = MutableLiveData>(emptyList()) + val hiddenResults = MutableLiveData>(emptyList()) val favoritesEnabled = dataStore.data.map { it.favorites.enabled } val hideFavorites = MutableLiveData(false) @@ -95,7 +96,7 @@ class SearchVM : ViewModel(), KoinComponent { val customAttrResults = customAttributesRepository.search(query) .combine(dataStore.data) { items, settings -> items.filter { - it is Application + it is LauncherApp || it is Contact && settings.contactsSearch.enabled || it is CalendarEvent && settings.calendarSearch.enabled || it is AppShortcut && settings.appShortcutSearch.enabled @@ -285,15 +286,15 @@ class SearchVM : ViewModel(), KoinComponent { } - private inline fun Flow>.withCustomAttributeResults( - customAttributeResults: Flow> + private inline fun Flow>.withCustomAttributeResults( + customAttributeResults: Flow> ): Flow> { return this.combine(customAttributeResults) { items, items2 -> (items + items2.filterIsInstance()).distinctBy { it.key } } } - private suspend fun Flow>.collectWithHiddenItems( + private suspend fun Flow>.collectWithHiddenItems( hiddenItemKeys: Flow>, action: (items: List, hidden: List) -> Unit ) { @@ -305,18 +306,18 @@ class SearchVM : ViewModel(), KoinComponent { } } - private fun Flow>.sorted(): Flow> = this.map { it.sorted() } + private fun Flow>.sorted(): Flow> = this.map { it.sorted() } } private data class HiddenItemResults( - val apps: List = emptyList(), + val apps: List = emptyList(), val contacts: List = emptyList(), val calendarEvents: List = emptyList(), val files: List = emptyList(), val appShortcuts: List = emptyList(), ) { - fun joinToList(): List { + fun joinToList(): List { return apps + contacts + calendarEvents + files + appShortcuts } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt index 5c1ce1b3..fdfa9587 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt @@ -23,7 +23,7 @@ import androidx.core.app.NotificationCompat import androidx.lifecycle.lifecycleScope import coil.compose.rememberAsyncImagePainter import com.google.accompanist.flowlayout.FlowRow -import de.mm20.launcher2.search.data.Application +import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.* import de.mm20.launcher2.ui.ktx.toDp @@ -40,7 +40,7 @@ import kotlin.math.roundToInt @Composable fun AppItem( modifier: Modifier = Modifier, - app: Application, + app: LauncherApp, onBack: () -> Unit ) { val viewModel = remember { AppItemVM(app) } @@ -336,7 +336,7 @@ fun AppItem( @Composable fun AppItemGridPopup( - app: Application, + app: LauncherApp, show: Boolean, animationProgress: Float, origin: Rect, diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItemVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItemVM.kt index cc1bd43b..29398130 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItemVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItemVM.kt @@ -8,16 +8,13 @@ import android.content.pm.* import android.graphics.drawable.Drawable import android.net.Uri import android.os.Process -import android.provider.Settings import android.service.notification.StatusBarNotification import androidx.core.content.FileProvider import androidx.core.content.getSystemService import de.mm20.launcher2.appshortcuts.AppShortcutRepository import de.mm20.launcher2.crashreporter.CrashReporter -import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.notifications.NotificationRepository import de.mm20.launcher2.search.data.AppShortcut -import de.mm20.launcher2.search.data.Application import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM import kotlinx.coroutines.Dispatchers @@ -28,7 +25,7 @@ import kotlinx.coroutines.withContext import org.koin.core.component.inject class AppItemVM( - private val app: Application + private val app: LauncherApp ) : SearchableItemVM(app) { private val notificationRepository: NotificationRepository by inject() private val appShortcutRepository: AppShortcutRepository by inject() @@ -44,21 +41,12 @@ class AppItemVM( fun openAppInfo(context: Context) { val launcherApps = context.getSystemService()!! - if (app is LauncherApp) { - launcherApps.startAppDetailsActivity( - ComponentName(app.`package`, app.activity), - app.getUser(), - null, - null - ) - } else { - context.tryStartActivity( - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { - data = Uri.parse("package:${app.`package`}") - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - ) - } + launcherApps.startAppDetailsActivity( + ComponentName(app.`package`, app.activity), + app.getUser(), + null, + null + ) } suspend fun shareApkFile(context: Context) { @@ -128,9 +116,7 @@ class AppItemVM( } val shortcuts = flow { - if (app is LauncherApp) { - emit(appShortcutRepository.getShortcutsForActivity(app.launcherActivityInfo, 5)) - } + emit(appShortcutRepository.getShortcutsForActivity(app.launcherActivityInfo, 5)) } fun isShortcutPinned(shortcut: AppShortcut): Flow { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt index 4b5f2e35..7e3482bf 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt @@ -10,15 +10,15 @@ import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.icons.IconRepository import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.ktx.isAtLeastApiLevel +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.data.AppShortcut -import de.mm20.launcher2.search.data.Application -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.data.LauncherApp import kotlinx.coroutines.flow.Flow import org.koin.core.component.KoinComponent import org.koin.core.component.inject abstract class SearchableItemVM( - private val searchable: Searchable + private val searchable: PinnableSearchable ) : KoinComponent { protected val favoritesRepository: FavoritesRepository by inject() protected val badgeRepository: BadgeRepository by inject() @@ -74,7 +74,7 @@ abstract class SearchableItemVM( if (searchable.launch(context, bundle)) { favoritesRepository.incrementLaunchCounter(searchable) return true - } else if (searchable is Application || searchable is AppShortcut) { + } else if (searchable is LauncherApp || searchable is AppShortcut) { favoritesRepository.remove(searchable) } return false diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt index 0e5939e3..d17f63aa 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt @@ -23,7 +23,8 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.icons.CustomIconWithPreview -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.ShapedLauncherIcon @@ -35,7 +36,7 @@ import kotlinx.coroutines.launch @Composable fun CustomizeSearchableSheet( - searchable: Searchable, + searchable: PinnableSearchable, onDismiss: () -> Unit, ) { val viewModel: CustomizeSearchableSheetVM = diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt index 7af52c9c..9fc47dc3 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt @@ -4,11 +4,11 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.liveData import de.mm20.launcher2.customattrs.CustomAttributesRepository import de.mm20.launcher2.customattrs.CustomIcon -import de.mm20.launcher2.customattrs.customAttrsModule import de.mm20.launcher2.icons.CustomIconWithPreview import de.mm20.launcher2.icons.IconRepository import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import org.koin.core.component.KoinComponent @@ -16,7 +16,7 @@ import org.koin.core.component.inject import kotlin.coroutines.coroutineContext class CustomizeSearchableSheetVM( - private val searchable: Searchable + private val searchable: PinnableSearchable ) : KoinComponent { private val iconRepository: IconRepository by inject() private val customAttributesRepository: CustomAttributesRepository by inject() diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt index f46b7716..9e7c90d8 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt @@ -2,19 +2,15 @@ package de.mm20.launcher2.ui.launcher.search.common.grid import android.content.ComponentName import androidx.activity.compose.BackHandler -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.graphics.Color import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned @@ -26,6 +22,8 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.* import de.mm20.launcher2.ui.component.LauncherCard import de.mm20.launcher2.ui.component.ShapedLauncherIcon @@ -47,7 +45,7 @@ import kotlinx.coroutines.delay @Composable -fun GridItem(modifier: Modifier = Modifier, item: Searchable, showLabels: Boolean = true) { +fun GridItem(modifier: Modifier = Modifier, item: PinnableSearchable, showLabels: Boolean = true) { val viewModel = remember(item.key) { GridItemVM(item) } val context = LocalContext.current @@ -59,13 +57,11 @@ fun GridItem(modifier: Modifier = Modifier, item: Searchable, showLabels: Boolea val iconSize = LocalGridIconSize.current.toPixels() val icon by remember(item.key) { viewModel.getIcon(iconSize.toInt()) }.collectAsState(null) - // If item is one of these types, try to launch them on click; show details otherwise - val launchOnPress = - item is File || item is Application || item is AppShortcut || item is Website || item is Wikipedia + val launchOnPress = !item.preferDetailsOverLaunch val windowSize = LocalWindowSize.current - if (item is Application) { + if (item is LauncherApp) { HandleHomeTransition { val cn = ComponentName(item.`package`, item.activity) if ( @@ -164,7 +160,7 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit .wrapContentSize() ) { when (searchable) { - is Application -> { + is LauncherApp -> { AppItemGridPopup( app = searchable, show = show, diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItemVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItemVM.kt index 0d49a6c6..bf5913ac 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItemVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItemVM.kt @@ -1,8 +1,9 @@ package de.mm20.launcher2.ui.launcher.search.common.grid -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM class GridItemVM( - searchable: Searchable + searchable: PinnableSearchable ): SearchableItemVM(searchable) \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/SearchResultGrid.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/SearchResultGrid.kt index c81fb1f4..94277ac7 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/SearchResultGrid.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/SearchResultGrid.kt @@ -5,14 +5,15 @@ import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.ui.layout.BottomReversed import de.mm20.launcher2.ui.locals.LocalGridColumns import kotlin.math.ceil @Composable fun SearchResultGrid( - items: List, + items: List, modifier: Modifier = Modifier, showLabels: Boolean = true, columns: Int = LocalGridColumns.current, diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItem.kt index 1a94f0fd..c929895c 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItem.kt @@ -2,13 +2,14 @@ package de.mm20.launcher2.ui.launcher.search.common.list import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.* import de.mm20.launcher2.ui.component.InnerCard import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItem @@ -17,7 +18,7 @@ import de.mm20.launcher2.ui.launcher.search.files.FileItem import de.mm20.launcher2.ui.launcher.search.shortcut.AppShortcutItem @Composable -fun ListItem(modifier: Modifier = Modifier, item: Searchable) { +fun ListItem(modifier: Modifier = Modifier, item: PinnableSearchable) { var showDetails by remember { mutableStateOf(false) } val context = LocalContext.current diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItemVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItemVM.kt index c3bb237c..0b511671 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItemVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/ListItemVM.kt @@ -1,8 +1,9 @@ package de.mm20.launcher2.ui.launcher.search.common.list -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM class ListItemVM( - searchable: Searchable + searchable: PinnableSearchable ): SearchableItemVM(searchable) \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/SearchResultList.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/SearchResultList.kt index 9edde484..5a2cc1d9 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/SearchResultList.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/list/SearchResultList.kt @@ -8,12 +8,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.ui.layout.BottomReversed @Composable fun SearchResultList( - items: List, + items: List, modifier: Modifier = Modifier, reverse: Boolean = false ) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/website/WebsiteItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/website/WebsiteItem.kt index a63cb530..127dad61 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/website/WebsiteItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/website/WebsiteItem.kt @@ -57,7 +57,7 @@ fun WebsiteItem( modifier = Modifier.padding(16.dp), ) { Text( - text = website.label, + text = website.labelOverride ?: website.label, style = MaterialTheme.typography.titleLarge ) val tags by remember(viewModel) { viewModel.getTags() }.collectAsState(emptyList()) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidgetVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidgetVM.kt index c8eb6b35..de2b3815 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidgetVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/favorites/FavoritesWidgetVM.kt @@ -1,17 +1,5 @@ package de.mm20.launcher2.ui.launcher.widgets.favorites -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import de.mm20.launcher2.favorites.FavoritesRepository -import de.mm20.launcher2.preferences.LauncherDataStore -import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.ui.common.FavoritesVM -import de.mm20.launcher2.widgets.WidgetRepository -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject class FavoritesWidgetVM: FavoritesVM() \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt index 6be6eb20..186738fa 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreen.kt @@ -1,6 +1,5 @@ package de.mm20.launcher2.ui.settings.hiddenitems -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* @@ -21,7 +20,6 @@ import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.search.data.Application import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.ShapedLauncherIcon import de.mm20.launcher2.ui.component.preferences.PreferenceScreen diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt index bcc704d8..650cc2be 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/hiddenitems/HiddenItemsSettingsScreenVM.kt @@ -2,11 +2,8 @@ package de.mm20.launcher2.ui.settings.hiddenitems import android.content.ComponentName import android.content.Context -import android.content.Intent import android.content.pm.LauncherApps -import android.net.Uri import android.os.Bundle -import android.provider.Settings import androidx.core.content.getSystemService import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel @@ -17,10 +14,9 @@ import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.icons.IconRepository import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.ktx.isAtLeastApiLevel -import de.mm20.launcher2.ktx.tryStartActivity -import de.mm20.launcher2.search.data.Application +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.data.LauncherApp -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first @@ -37,18 +33,18 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent { val allApps = appRepository.getAllInstalledApps().map { withContext(Dispatchers.Default) { it.sorted() } }.asLiveData() - val hiddenItems: LiveData> = liveData { + val hiddenItems: LiveData> = liveData { val hidden = withContext(Dispatchers.Default) { - favoritesRepository.getHiddenItems().first().filter { it !is Application }.sorted() + favoritesRepository.getHiddenItems().first().filter { it !is LauncherApp }.sorted() } emit(hidden) } - fun isHidden(searchable: Searchable): Flow { + fun isHidden(searchable: PinnableSearchable): Flow { return favoritesRepository.isHidden(searchable) } - fun setHidden(searchable: Searchable, hidden: Boolean) { + fun setHidden(searchable: PinnableSearchable, hidden: Boolean) { if(hidden) { favoritesRepository.hideItem(searchable) } else { @@ -56,11 +52,11 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent { } } - fun getIcon(searchable: Searchable, size: Int): Flow { + fun getIcon(searchable: PinnableSearchable, size: Int): Flow { return iconRepository.getIcon(searchable, size) } - fun launch(context: Context, searchable: Searchable) { + fun launch(context: Context, searchable: PinnableSearchable) { val bundle = Bundle() if (isAtLeastApiLevel(31)) { bundle.putInt("android.activity.splashScreenStyle", 1) @@ -68,23 +64,14 @@ class HiddenItemsSettingsScreenVM : ViewModel(), KoinComponent { searchable.launch(context, bundle) } - fun openAppInfo(context: Context, app: Application) { + fun openAppInfo(context: Context, app: LauncherApp) { val launcherApps = context.getSystemService()!! - if (app is LauncherApp) { - launcherApps.startAppDetailsActivity( - ComponentName(app.`package`, app.activity), - app.getUser(), - null, - null - ) - } else { - context.tryStartActivity( - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { - data = Uri.parse("package:${app.`package`}") - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - ) - } + launcherApps.startAppDetailsActivity( + ComponentName(app.`package`, app.activity), + app.getUser(), + null, + null + ) } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/utils/CustomLabels.kt b/ui/src/main/java/de/mm20/launcher2/ui/utils/CustomLabels.kt index 8edbe2d2..89585f60 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/utils/CustomLabels.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/utils/CustomLabels.kt @@ -1,23 +1,26 @@ package de.mm20.launcher2.ui.utils import de.mm20.launcher2.customattrs.CustomAttributesRepository -import de.mm20.launcher2.customattrs.CustomLabel -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest -fun Flow>.withCustomLabels( +fun Flow>.withCustomLabels( customAttributesRepository: CustomAttributesRepository, ): Flow> = channelFlow { this@withCustomLabels.collectLatest { items -> val customLabels = customAttributesRepository.getCustomLabels(items) customLabels.collectLatest { labels -> - for (item in items) { + send(items.map { item -> val customLabel = labels.find { it.key == item.key } - item.labelOverride = customLabel?.label - } - send(items) + if (customLabel != null) { + item.overrideLabel(customLabel.label) as T + } else { + item + } + }) } } } \ No newline at end of file diff --git a/unitconverter/build.gradle.kts b/unitconverter/build.gradle.kts index f369f2f6..288e5c5a 100644 --- a/unitconverter/build.gradle.kts +++ b/unitconverter/build.gradle.kts @@ -45,7 +45,7 @@ dependencies { implementation(project(":preferences")) implementation(project(":currencies")) - implementation(project(":search")) + implementation(project(":base")) implementation(project(":i18n")) } \ No newline at end of file diff --git a/websites/build.gradle.kts b/websites/build.gradle.kts index f1275a97..e940b2c6 100644 --- a/websites/build.gradle.kts +++ b/websites/build.gradle.kts @@ -51,7 +51,6 @@ dependencies { implementation(libs.coil.core) implementation(project(":preferences")) - implementation(project(":search")) implementation(project(":base")) implementation(project(":ktx")) diff --git a/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt b/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt index 7b37c2b2..9fdb1ca1 100644 --- a/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt +++ b/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt @@ -2,26 +2,38 @@ package de.mm20.launcher2.search.data import android.content.Context import android.content.Intent -import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.net.Uri +import android.os.Bundle import androidx.core.content.ContextCompat import coil.imageLoader import coil.request.ImageRequest import de.mm20.launcher2.icons.* +import de.mm20.launcher2.ktx.tryStartActivity +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.websites.R import java.util.concurrent.ExecutionException -class Website( +data class Website( override val label: String, val url: String, val description: String, val image: String, val favicon: String, - val color: Int -) : Searchable() { + val color: Int, + override val labelOverride: String? = null, +) : PinnableSearchable { + + override val domain: String = Domain + + override val key = "$domain://$url" + + override val preferDetailsOverLaunch: Boolean = false + + override fun overrideLabel(label: String): Website { + return this.copy(labelOverride = label) + } - override val key = "web://$url" override suspend fun loadIcon( context: Context, size: Int, @@ -68,10 +80,18 @@ class Website( ) } - override fun getLaunchIntent(context: Context): Intent? { + private fun getLaunchIntent(): Intent { val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse(url) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK return intent } + + override fun launch(context: Context, options: Bundle?): Boolean { + return context.tryStartActivity(getLaunchIntent(), options) + } + + companion object { + const val Domain = "web" + } } \ No newline at end of file diff --git a/websites/src/main/java/de/mm20/launcher2/websites/WebsiteSerialization.kt b/websites/src/main/java/de/mm20/launcher2/websites/WebsiteSerialization.kt index 2b58b360..a4ed2a56 100644 --- a/websites/src/main/java/de/mm20/launcher2/websites/WebsiteSerialization.kt +++ b/websites/src/main/java/de/mm20/launcher2/websites/WebsiteSerialization.kt @@ -1,14 +1,15 @@ package de.mm20.launcher2.websites 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.Searchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.Website import org.json.JSONObject class WebsiteSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as Website return jsonObjectOf( "label" to searchable.label, @@ -25,7 +26,7 @@ class WebsiteSerializer : SearchableSerializer { } class WebsiteDeserializer: SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable? { val json = JSONObject(serialized) return Website( label = json.getString("label"), diff --git a/wikipedia/build.gradle.kts b/wikipedia/build.gradle.kts index 4feb9e68..9c818d7f 100644 --- a/wikipedia/build.gradle.kts +++ b/wikipedia/build.gradle.kts @@ -48,8 +48,8 @@ dependencies { implementation(libs.koin.android) implementation(project(":preferences")) - implementation(project(":search")) implementation(project(":base")) + implementation(project(":ktx")) implementation(project(":crashreporter")) } \ No newline at end of file diff --git a/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt b/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt index 8e1b2804..656e6024 100644 --- a/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt +++ b/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt @@ -4,21 +4,35 @@ import android.content.Context import android.content.Intent import android.graphics.Color import android.net.Uri +import android.os.Bundle import androidx.browser.customtabs.CustomTabsIntent import androidx.core.content.ContextCompat import de.mm20.launcher2.icons.ColorLayer import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.icons.TintedIconLayer +import de.mm20.launcher2.ktx.tryStartActivity +import de.mm20.launcher2.search.PinnableSearchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.wikipedia.R -class Wikipedia( +data class Wikipedia( override val label: String, val id: Long, val text: String, val image: String?, val wikipediaUrl: String, -) : Searchable() { - override val key = "wikipedia://$wikipediaUrl:$id" + override val labelOverride: String? = null, +) : PinnableSearchable { + + override val domain: String = Domain + + override val preferDetailsOverLaunch: Boolean = false + + override fun overrideLabel(label: String): Wikipedia { + return this.copy(labelOverride = label) + } + + override val key = "$domain://$wikipediaUrl:$id" override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { return StaticLauncherIcon( @@ -31,7 +45,7 @@ class Wikipedia( ) } - override fun getLaunchIntent(context: Context): Intent? { + private fun getLaunchIntent(): Intent { val intent = CustomTabsIntent .Builder() .setToolbarColor(Color.BLACK) @@ -42,4 +56,12 @@ class Wikipedia( intent.intent.data = Uri.parse(uri) return intent.intent } + + override fun launch(context: Context, options: Bundle?): Boolean { + return context.tryStartActivity(getLaunchIntent(), options) + } + + companion object { + const val Domain = "wikipedia" + } } \ No newline at end of file diff --git a/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaSerialization.kt b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaSerialization.kt index 0746f9a9..5aa78e63 100644 --- a/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaSerialization.kt +++ b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaSerialization.kt @@ -1,14 +1,15 @@ package de.mm20.launcher2.wikipedia import android.content.Context +import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.SearchableSerializer -import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.Wikipedia import org.json.JSONObject class WikipediaSerializer : SearchableSerializer { - override fun serialize(searchable: Searchable): String { + override fun serialize(searchable: PinnableSearchable): String { searchable as Wikipedia val json = JSONObject() json.put("label", searchable.label) @@ -24,7 +25,7 @@ class WikipediaSerializer : SearchableSerializer { } class WikipediaDeserializer(val context: Context) : SearchableDeserializer { - override fun deserialize(serialized: String): Searchable? { + override fun deserialize(serialized: String): PinnableSearchable? { val json = JSONObject(serialized) return Wikipedia( label = json.getString("label"),