fix: Don't exceed the maximum number of created shortcuts (#771)

Previous code created one shortcut per account, which could exceed the
maximum number of shortcuts allowed, causing a crash.

Fix this by creating no more than the max number of shortcuts while
ensuring that the active account is always included.

Fixes #752
This commit is contained in:
Nik Clayton 2024-06-20 13:26:31 +02:00 committed by GitHub
parent 33e1b418cf
commit 597833d660
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 59 additions and 59 deletions

View File

@ -2625,8 +2625,8 @@
<issue <issue
id="ReportShortcutUsage" id="ReportShortcutUsage"
message="Calling this method indicates use of dynamic shortcuts, but there are no calls to methods that track shortcut usage, such as `pushDynamicShortcut` or `reportShortcutUsed`. Calling these methods is recommended, as they track shortcut usage and allow launchers to adjust which shortcuts appear based on activation history. Please see https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#track-usage" message="Calling this method indicates use of dynamic shortcuts, but there are no calls to methods that track shortcut usage, such as `pushDynamicShortcut` or `reportShortcutUsed`. Calling these methods is recommended, as they track shortcut usage and allow launchers to adjust which shortcuts appear based on activation history. Please see https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#track-usage"
errorLine1=" ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcutInfo))" errorLine1=" ShortcutManagerCompat.setDynamicShortcuts(context, shortcuts)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/java/app/pachli/util/ShareShortcutHelper.kt" file="src/main/java/app/pachli/util/ShareShortcutHelper.kt"
line="96" line="96"

View File

@ -120,7 +120,7 @@ import app.pachli.updatecheck.UpdateCheck
import app.pachli.usecase.DeveloperToolsUseCase import app.pachli.usecase.DeveloperToolsUseCase
import app.pachli.usecase.LogoutUsecase import app.pachli.usecase.LogoutUsecase
import app.pachli.util.getDimension import app.pachli.util.getDimension
import app.pachli.util.updateShortcut import app.pachli.util.updateShortcuts
import at.connyduck.calladapter.networkresult.fold import at.connyduck.calladapter.networkresult.fold
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
@ -1085,7 +1085,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
updateProfiles() updateProfiles()
externalScope.launch { externalScope.launch {
updateShortcut(applicationContext, accountManager.activeAccount!!) updateShortcuts(applicationContext, accountManager)
} }
} }

View File

@ -21,10 +21,12 @@ import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.text.TextUtils import android.text.TextUtils
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.app.Person import androidx.core.app.Person
import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity import app.pachli.core.database.model.AccountEntity
import app.pachli.core.designsystem.R as DR import app.pachli.core.designsystem.R as DR
import app.pachli.core.navigation.MainActivityIntent import app.pachli.core.navigation.MainActivityIntent
@ -33,67 +35,65 @@ import java.util.concurrent.ExecutionException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
suspend fun updateShortcut(context: Context, account: AccountEntity) = withContext(Dispatchers.IO) { suspend fun updateShortcuts(context: Context, accountManager: AccountManager) = withContext(Dispatchers.IO) {
val innerSize = context.resources.getDimensionPixelSize(DR.dimen.adaptive_bitmap_inner_size) val innerSize = context.resources.getDimensionPixelSize(DR.dimen.adaptive_bitmap_inner_size)
val outerSize = context.resources.getDimensionPixelSize(DR.dimen.adaptive_bitmap_outer_size) val outerSize = context.resources.getDimensionPixelSize(DR.dimen.adaptive_bitmap_outer_size)
val bmp = try { val maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context)
if (TextUtils.isEmpty(account.profilePictureUrl)) {
Glide.with(context) val shortcuts = accountManager.getAllAccountsOrderedByActive().take(maxShortcuts).mapNotNull { account ->
.asBitmap() val drawable = try {
.load(DR.drawable.avatar_default) if (TextUtils.isEmpty(account.profilePictureUrl)) {
.submit(innerSize, innerSize) AppCompatResources.getDrawable(context, DR.drawable.avatar_default)
.get() } else {
} else { Glide.with(context)
Glide.with(context) .asDrawable()
.asBitmap() .load(account.profilePictureUrl)
.load(account.profilePictureUrl) .error(DR.drawable.avatar_default)
.error(DR.drawable.avatar_default) .submit(innerSize, innerSize)
.submit(innerSize, innerSize) .get()
.get() }
} catch (e: ExecutionException) {
// The `.error` handler isn't always used. For example, Glide throws
// ExecutionException if the URL does not point at an image. Fallback to
// the default avatar (https://github.com/bumptech/glide/issues/4672).
AppCompatResources.getDrawable(context, DR.drawable.avatar_default)
} ?: return@mapNotNull null
// inset the loaded bitmap inside a 108dp transparent canvas so it looks good as adaptive icon
val outBmp = Bitmap.createBitmap(outerSize, outerSize, Bitmap.Config.ARGB_8888)
val canvas = Canvas(outBmp)
val border = (outerSize - innerSize) / 2
drawable.setBounds(border, border, border + innerSize, border + innerSize)
drawable.draw(canvas)
val icon = IconCompat.createWithAdaptiveBitmap(outBmp)
val person = Person.Builder()
.setIcon(icon)
.setName(account.displayName)
.setKey(account.identifier)
.build()
// This intent will be sent when the user clicks on one of the launcher shortcuts. Intent from share sheet will be different
val intent = MainActivityIntent(context).apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID, account.id.toString())
} }
} catch (e: ExecutionException) {
// The `.error` handler isn't always used. For example, Glide throws ShortcutInfoCompat.Builder(context, account.id.toString())
// ExecutionException if the URL does not point at an image. Fallback to .setIntent(intent)
// the default avatar (https://github.com/bumptech/glide/issues/4672). .setCategories(setOf("app.pachli.Share"))
Glide.with(context) .setShortLabel(account.displayName)
.asBitmap() .setPerson(person)
.load(DR.drawable.avatar_default) .setLongLived(true)
.submit(innerSize, innerSize) .setIcon(icon)
.get() .build()
} }
// inset the loaded bitmap inside a 108dp transparent canvas so it looks good as adaptive icon ShortcutManagerCompat.setDynamicShortcuts(context, shortcuts)
val outBmp = Bitmap.createBitmap(outerSize, outerSize, Bitmap.Config.ARGB_8888)
val canvas = Canvas(outBmp)
canvas.drawBitmap(bmp, (outerSize - innerSize).toFloat() / 2f, (outerSize - innerSize).toFloat() / 2f, null)
val icon = IconCompat.createWithAdaptiveBitmap(outBmp)
val person = Person.Builder()
.setIcon(icon)
.setName(account.displayName)
.setKey(account.identifier)
.build()
// This intent will be sent when the user clicks on one of the launcher shortcuts. Intent from share sheet will be different
val intent = MainActivityIntent(context).apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID, account.id.toString())
}
val shortcutInfo = ShortcutInfoCompat.Builder(context, account.id.toString())
.setIntent(intent)
.setCategories(setOf("app.pachli.Share"))
.setShortLabel(account.displayName)
.setPerson(person)
.setLongLived(true)
.setIcon(icon)
.build()
ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcutInfo))
} }
fun removeShortcut(context: Context, account: AccountEntity) { fun removeShortcut(context: Context, account: AccountEntity) {