Add support for legacy app shortcuts

This commit is contained in:
MM20 2022-09-18 23:12:26 +02:00
parent b068e4d6fd
commit 5fcb6ceb2c
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
12 changed files with 394 additions and 152 deletions

View File

@ -6,6 +6,7 @@ import android.content.pm.LauncherApps
import android.os.Bundle
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.LauncherShortcut
import org.koin.android.ext.android.inject
class AddItemActivity : Activity() {
@ -14,15 +15,8 @@ class AddItemActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val launcherApps = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val pinRequest = launcherApps.getPinItemRequest(intent) ?: return run { finish() }
val shortcutInfo = pinRequest.shortcutInfo ?: return run { finish() }
val shortcut = AppShortcut(
this.applicationContext, shortcutInfo,
packageManager.getApplicationInfo(shortcutInfo.`package`, 0)
.loadLabel(packageManager).toString()
)
if (pinRequest.accept()) {
val shortcut = AppShortcut.fromPinRequestIntent(this, intent)
if (shortcut != null) {
favoritesRepository.pinItem(shortcut)
}
finish()

View File

@ -17,6 +17,7 @@ import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.LauncherShortcut
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -30,13 +31,13 @@ interface AppShortcutRepository {
suspend fun getShortcutsForActivity(
launcherActivityInfo: LauncherActivityInfo,
count: Int = 5
): List<AppShortcut>
): List<LauncherShortcut>
suspend fun getShortcutsConfigActivities(): List<LauncherApp>
fun search(query: String): Flow<List<AppShortcut>>
fun removePinnedShortcut(shortcut: AppShortcut)
fun removePinnedShortcut(shortcut: LauncherShortcut)
}
internal class AppShortcutRepositoryImpl(
@ -61,14 +62,14 @@ internal class AppShortcutRepositoryImpl(
} catch (e: IllegalStateException) {
emptyList()
}
val appShortcuts = mutableListOf<AppShortcut>()
val appShortcuts = mutableListOf<LauncherShortcut>()
appShortcuts.addAll(shortcuts
?.let {
if (it.size > count) it.subList(0, count)
else it
}
?.map {
AppShortcut(
LauncherShortcut(
context,
it,
launcherActivityInfo.label.toString()
@ -128,7 +129,7 @@ internal class AppShortcutRepositoryImpl(
} catch (e: PackageManager.NameNotFoundException) {
""
}
AppShortcut(
LauncherShortcut(
context,
it,
label
@ -186,7 +187,7 @@ internal class AppShortcutRepositoryImpl(
}
}.shareIn(scope, SharingStarted.WhileSubscribed(500), 1)
override fun removePinnedShortcut(shortcut: AppShortcut) {
override fun removePinnedShortcut(shortcut: LauncherShortcut) {
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
if (!launcherApps.hasShortcutHostPermission()) return
val pinnedShortcutsQuery = LauncherApps.ShortcutQuery().apply {

View File

@ -1,23 +1,27 @@
package de.mm20.launcher2.appshortcuts
import android.content.Context
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.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.LauncherShortcut
import de.mm20.launcher2.search.data.LegacyShortcut
import de.mm20.launcher2.search.data.Searchable
import org.json.JSONObject
import org.koin.core.component.KoinComponent
class AppShortcutSerializer : SearchableSerializer {
class LauncherShortcutSerializer : SearchableSerializer {
override fun serialize(searchable: Searchable): String {
searchable as AppShortcut
searchable as LauncherShortcut
return jsonObjectOf(
"packagename" to searchable.launcherShortcut.`package`,
"id" to searchable.launcherShortcut.id,
@ -30,7 +34,7 @@ class AppShortcutSerializer : SearchableSerializer {
}
class AppShortcutDeserializer(
class LauncherShortcutDeserializer(
val context: Context
) : SearchableDeserializer, KoinComponent {
@ -68,7 +72,7 @@ class AppShortcutDeserializer(
return null
} else {
val activity = shortcuts[0].activity
return AppShortcut(
return LauncherShortcut(
context = context,
launcherShortcut = shortcuts[0],
appName = appName
@ -77,3 +81,61 @@ class AppShortcutDeserializer(
}
}
}
class LegacyShortcutSerializer: SearchableSerializer {
override fun serialize(searchable: Searchable): String? {
searchable as LegacyShortcut
return jsonObjectOf(
"label" to searchable.label,
"intent" to searchable.intent.toUri(0),
"iconResource" to searchable.iconResource?.let {
jsonObjectOf(
"package" to it.packageName,
"resource" to it.resourceName,
)
}
).toString()
}
override val typePrefix: String
get() = "legacyshortcut"
}
class LegacyShortcutDeserializer(
val context: Context
): SearchableDeserializer {
override fun deserialize(serialized: String): Searchable? {
val json = JSONObject(serialized)
val label = json.getString("label")
val intent = Intent.parseUri(json.getString("intent"), 0)
val iconResourceObj = json.optJSONObject("iconResource")
val iconResource = iconResourceObj?.let {
ShortcutIconResource().apply {
packageName = iconResourceObj.getString("package")
resourceName = iconResourceObj.getString("resource")
}
}
val packageName = intent.`package` ?: intent.component?.packageName
val appName = try {
packageName?.let {
context
.packageManager
.getApplicationInfo(it, 0)
.loadLabel(context.packageManager)
.toString()
}
} catch (e: PackageManager.NameNotFoundException) {
null
}
return LegacyShortcut(
intent = intent,
label = label,
iconResource = iconResource,
appName = appName,
)
}
}

View File

@ -1,60 +1,17 @@
package de.mm20.launcher2.search.data
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
import android.content.pm.ShortcutInfo
import android.graphics.Color
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.Process
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import de.mm20.launcher2.appshortcuts.R
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
class AppShortcut(
context: Context,
val launcherShortcut: ShortcutInfo,
val appName: String
abstract class AppShortcut(
val appName: String?
) : Searchable() {
override val label: String
get() = launcherShortcut.shortLabel?.toString() ?: ""
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}"
} else {
"shortcut://${launcherShortcut.`package`}/${launcherShortcut.id}:${userSerialNumber}"
}
override fun getLaunchIntent(context: Context): Intent? {
return launcherShortcut.intent
}
override fun launch(context: Context, options: Bundle?): Boolean {
val launcherApps = context.getSystemService<LauncherApps>()!!
try {
launcherApps.startShortcut(launcherShortcut, null, options)
} catch (e: IllegalStateException) {
return false
} catch (e: ActivityNotFoundException) {
return false
}
return true
}
override fun getPlaceholderIcon(context: Context): StaticLauncherIcon {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
@ -66,49 +23,10 @@ class AppShortcut(
)
}
override suspend fun loadIcon(
context: Context,
size: Int,
themed: Boolean,
): LauncherIcon? {
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val icon = withContext(Dispatchers.IO) {
launcherApps.getShortcutIconDrawable(
launcherShortcut,
context.resources.displayMetrics.densityDpi
)
} ?: return null
if (icon is AdaptiveIconDrawable) {
if (themed && isAtLeastApiLevel(33) && icon.monochrome != null) {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
scale = 1f,
icon = icon.monochrome!!,
),
backgroundLayer = ColorLayer()
)
}
return StaticLauncherIcon(
foregroundLayer = icon.foreground?.let {
StaticIconLayer(
icon = it,
scale = 1.5f,
)
} ?: TransparentLayer,
backgroundLayer = icon.background?.let {
StaticIconLayer(
icon = it,
scale = 1.5f,
)
} ?: TransparentLayer,
)
}
return StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = icon,
scale = 1f
),
backgroundLayer = TransparentLayer
)
companion object {
fun fromPinRequestIntent(context: Context, data: Intent): AppShortcut? {
return LauncherShortcut.fromPinRequestIntent(context, data)
?: LegacyShortcut.fromPinRequestIntent(context, data)
}
}
}

View File

@ -0,0 +1,132 @@
package de.mm20.launcher2.search.data
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
import android.content.pm.ShortcutInfo
import android.graphics.drawable.AdaptiveIconDrawable
import android.os.Bundle
import android.os.Process
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import de.mm20.launcher2.appshortcuts.R
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
* Represents a modern (Android O+) launcher shortcut
*/
class LauncherShortcut(
context: Context,
val launcherShortcut: ShortcutInfo,
appName: String
) : AppShortcut(appName) {
override val label: String
get() = launcherShortcut.shortLabel?.toString() ?: ""
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}"
} else {
"shortcut://${launcherShortcut.`package`}/${launcherShortcut.id}:${userSerialNumber}"
}
override fun getLaunchIntent(context: Context): Intent? {
return launcherShortcut.intent
}
override fun launch(context: Context, options: Bundle?): Boolean {
val launcherApps = context.getSystemService<LauncherApps>()!!
try {
launcherApps.startShortcut(launcherShortcut, null, options)
} catch (e: IllegalStateException) {
return false
} catch (e: ActivityNotFoundException) {
return false
}
return true
}
override fun getPlaceholderIcon(context: Context): StaticLauncherIcon {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
color = 0xFF3DDA84.toInt(),
icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!,
scale = 0.5f,
),
backgroundLayer = ColorLayer(0xFF3DDA84.toInt()),
)
}
override suspend fun loadIcon(
context: Context,
size: Int,
themed: Boolean,
): LauncherIcon? {
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val icon = withContext(Dispatchers.IO) {
launcherApps.getShortcutIconDrawable(
launcherShortcut,
context.resources.displayMetrics.densityDpi
)
} ?: return null
if (icon is AdaptiveIconDrawable) {
if (themed && isAtLeastApiLevel(33) && icon.monochrome != null) {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
scale = 1f,
icon = icon.monochrome!!,
),
backgroundLayer = ColorLayer()
)
}
return StaticLauncherIcon(
foregroundLayer = icon.foreground?.let {
StaticIconLayer(
icon = it,
scale = 1.5f,
)
} ?: TransparentLayer,
backgroundLayer = icon.background?.let {
StaticIconLayer(
icon = it,
scale = 1.5f,
)
} ?: TransparentLayer,
)
}
return StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = icon,
scale = 1f
),
backgroundLayer = TransparentLayer
)
}
companion object {
fun fromPinRequestIntent(context: Context, data: Intent): LauncherShortcut? {
val launcherApps =
context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val pinRequest = launcherApps.getPinItemRequest(data)
val shortcutInfo = pinRequest?.shortcutInfo ?: return null
if (!pinRequest.accept()) return null
return LauncherShortcut(
context,
shortcutInfo,
context.packageManager.getApplicationInfo(shortcutInfo.`package`, 0)
.loadLabel(context.packageManager).toString()
)
}
}
}

View File

@ -0,0 +1,94 @@
package de.mm20.launcher2.search.data
import android.content.Context
import android.content.Intent
import android.content.Intent.ShortcutIconResource
import android.graphics.drawable.AdaptiveIconDrawable
import android.util.Log
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getDrawableOrNull
import de.mm20.launcher2.ktx.isAtLeastApiLevel
class LegacyShortcut(
val intent: Intent,
override val label: String,
appName: String?,
val iconResource: ShortcutIconResource?,
) : AppShortcut(appName) {
override val key: String
get() = "legacyshortcut://${intent.toUri(0)}"
override fun getLaunchIntent(context: Context): Intent {
return intent
}
val packageName: String?
get() = intent.`package` ?: intent.component?.packageName
override suspend fun loadIcon(context: Context, size: Int, themed: Boolean): LauncherIcon? {
if (iconResource == null) return null
val resources = context.packageManager.getResourcesForApplication(iconResource.packageName)
val drawableId =
resources.getIdentifier(iconResource.resourceName, "drawable", iconResource.packageName)
if (drawableId == 0) return null
val icon = resources.getDrawableOrNull(drawableId) ?: return null
if (icon is AdaptiveIconDrawable) {
if (themed && isAtLeastApiLevel(33) && icon.monochrome != null) {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
scale = 1f,
icon = icon.monochrome!!,
),
backgroundLayer = ColorLayer()
)
}
return StaticLauncherIcon(
foregroundLayer = icon.foreground?.let {
StaticIconLayer(
icon = it,
scale = 1.5f,
)
} ?: TransparentLayer,
backgroundLayer = icon.background?.let {
StaticIconLayer(
icon = it,
scale = 1.5f,
)
} ?: TransparentLayer,
)
}
return StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = icon,
scale = 1f
),
backgroundLayer = TransparentLayer
)
}
companion object {
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)
val iconResource: ShortcutIconResource? =
data.extras?.getParcelable(Intent.EXTRA_SHORTCUT_ICON_RESOURCE)
if (intent == null || name == null) {
return null
}
val packageName = intent.`package` ?: intent.component?.packageName
return LegacyShortcut(
intent = intent,
appName = packageName?.let {
context.packageManager.getApplicationInfo(
it, 0
).loadLabel(context.packageManager).toString()
},
label = name,
iconResource = iconResource
)
}
}
}

View File

@ -4,7 +4,8 @@ import android.content.Context
import android.content.pm.PackageManager
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.graphics.BadgeDrawable
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.LauncherShortcut
import de.mm20.launcher2.search.data.LegacyShortcut
import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
@ -15,7 +16,7 @@ class AppShortcutBadgeProvider(
private val context: Context
) : BadgeProvider {
override fun getBadge(searchable: Searchable): Flow<Badge?> = channelFlow {
if (searchable is AppShortcut) {
if (searchable is LauncherShortcut) {
val componentName = searchable.launcherShortcut.activity
if (componentName == null) {
send(null)
@ -32,6 +33,23 @@ class AppShortcutBadgeProvider(
val badge = Badge(icon = BadgeDrawable(context, icon))
send(badge)
}
} else if (searchable is LegacyShortcut) {
val packageName = searchable.packageName
if (packageName == null) {
send(null)
return@channelFlow
}
withContext(Dispatchers.IO) {
val icon = try {
context.packageManager.getApplicationIcon(
packageName
)
} catch (e: PackageManager.NameNotFoundException) {
return@withContext
}
val badge = Badge(icon = BadgeDrawable(context, icon))
send(badge)
}
} else {
send(null)
}

View File

@ -4,13 +4,14 @@ 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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class WorkProfileBadgeProvider : BadgeProvider {
override fun getBadge(searchable: Searchable): Flow<Badge?> = flow {
if (searchable is LauncherApp && !searchable.isMainProfile || searchable is AppShortcut && !searchable.isMainProfile) {
if (searchable is LauncherApp && !searchable.isMainProfile || searchable is LauncherShortcut && !searchable.isMainProfile) {
emit(
Badge(
iconRes = R.drawable.ic_badge_workprofile

View File

@ -1,8 +1,10 @@
package de.mm20.launcher2.favorites
import android.content.Context
import de.mm20.launcher2.appshortcuts.AppShortcutDeserializer
import de.mm20.launcher2.appshortcuts.AppShortcutSerializer
import de.mm20.launcher2.appshortcuts.LauncherShortcutDeserializer
import de.mm20.launcher2.appshortcuts.LauncherShortcutSerializer
import de.mm20.launcher2.appshortcuts.LegacyShortcutDeserializer
import de.mm20.launcher2.appshortcuts.LegacyShortcutSerializer
import de.mm20.launcher2.calendar.CalendarEventDeserializer
import de.mm20.launcher2.calendar.CalendarEventSerializer
import de.mm20.launcher2.contacts.ContactDeserializer
@ -23,8 +25,11 @@ internal fun getSerializer(searchable: Searchable?): SearchableSerializer {
if (searchable is LauncherApp) {
return LauncherAppSerializer()
}
if (searchable is AppShortcut) {
return AppShortcutSerializer()
if (searchable is LauncherShortcut) {
return LauncherShortcutSerializer()
}
if (searchable is LegacyShortcut) {
return LegacyShortcutSerializer()
}
if (searchable is CalendarEvent) {
return CalendarEventSerializer()
@ -62,7 +67,10 @@ internal fun getDeserializer(context: Context, serialized: String): SearchableDe
return LauncherAppDeserializer(context)
}
if (type == "shortcut") {
return AppShortcutDeserializer(context)
return LauncherShortcutDeserializer(context)
}
if (type == "legacyshortcut") {
return LegacyShortcutDeserializer(context)
}
if (type == "calendar") {
return CalendarEventDeserializer(context)

View File

@ -2,13 +2,12 @@ package de.mm20.launcher2.ui.launcher.modals
import android.content.Context
import android.content.Intent
import android.content.Intent.ShortcutIconResource
import android.content.pm.LauncherApps
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.BadgeRepository
@ -18,13 +17,12 @@ import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
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.LegacyShortcut
import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.ui.R
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -164,6 +162,7 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
fun pickShortcut(section: FavoritesSheetSection) {
createShortcutTarget.value = section
}
fun cancelPickShortcut() {
createShortcutTarget.value = null
}
@ -180,24 +179,28 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
fun createShortcut(context: Context, data: Intent?) {
data ?: return cancelPickShortcut()
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val pinRequest = launcherApps.getPinItemRequest(data) ?: return cancelPickShortcut()
val shortcutInfo = pinRequest.shortcutInfo ?: return cancelPickShortcut()
pinRequest.accept()
val shortcut = AppShortcut(
context,
shortcutInfo,
context.packageManager.getApplicationInfo(shortcutInfo.`package`, 0)
.loadLabel(context.packageManager).toString()
)
val shortcut = AppShortcut.fromPinRequestIntent(context, data)
if (shortcut == null) {
cancelPickShortcut()
return
}
if (!manuallySorted.any { it.key == shortcut.key }
&& !automaticallySorted.any { it.key == shortcut.key }
&& !frequentlyUsed.any { it.key == shortcut.key }
) {
if (createShortcutTarget.value == FavoritesSheetSection.ManuallySorted) {
manuallySorted.add(shortcut)
} else {
automaticallySorted.add(shortcut)
}
}
save()
buildItemList()
createShortcutTarget.value = null
}
}

View File

@ -4,7 +4,6 @@ package de.mm20.launcher2.ui.launcher.search.shortcut
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.*
import androidx.compose.material3.*
@ -73,14 +72,16 @@ fun AppShortcutItem(
val textSpace by transition.animateDp(label = "textSpace") {
if (it) 4.dp else 2.dp
}
shortcut.appName?.let {
Text(
text = stringResource(R.string.shortcut_summary, shortcut.appName),
text = stringResource(R.string.shortcut_summary, it),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = textSpace),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
val badge by viewModel.badge.collectAsState(null)
val size by animateDpAsState(if (showDetails) 84.dp else 48.dp)
val iconSize = 84.dp.toPixels().toInt()
@ -168,7 +169,10 @@ fun AppShortcutItem(
lifecycleOwner.lifecycleScope.launch {
val result = snackbarHostState.showSnackbar(
message = context.getString(R.string.msg_item_hidden, shortcut.label),
message = context.getString(
R.string.msg_item_hidden,
shortcut.label
),
actionLabel = context.getString(R.string.action_undo),
duration = SnackbarDuration.Short,
)

View File

@ -8,6 +8,8 @@ import android.util.Log
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.LauncherShortcut
import de.mm20.launcher2.search.data.LegacyShortcut
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -16,12 +18,17 @@ class ShortcutItemVM(private val shortcut: AppShortcut) : SearchableItemVM(short
private val shortcutRepository: AppShortcutRepository by inject()
val canDelete = shortcut.launcherShortcut.isPinned
val canDelete = shortcut is LauncherShortcut && shortcut.launcherShortcut.isPinned
fun openAppInfo(context: Context) {
val packageName = when(shortcut) {
is LegacyShortcut -> shortcut.intent.`package` ?: return
is LauncherShortcut -> shortcut.launcherShortcut.`package`
else -> return
}
context.tryStartActivity(
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:${shortcut.launcherShortcut.`package`}")
data = Uri.parse("package:$packageName")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
)
@ -29,7 +36,7 @@ class ShortcutItemVM(private val shortcut: AppShortcut) : SearchableItemVM(short
fun deleteShortcut() {
if (!canDelete) return
shortcutRepository.removePinnedShortcut(shortcut)
if (shortcut is LauncherShortcut) shortcutRepository.removePinnedShortcut(shortcut)
favoritesRepository.unpinItem(shortcut)
}
}