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
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"
errorLine1=" ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcutInfo))"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
errorLine1=" ShortcutManagerCompat.setDynamicShortcuts(context, shortcuts)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/util/ShareShortcutHelper.kt"
line="96"

View File

@ -120,7 +120,7 @@ import app.pachli.updatecheck.UpdateCheck
import app.pachli.usecase.DeveloperToolsUseCase
import app.pachli.usecase.LogoutUsecase
import app.pachli.util.getDimension
import app.pachli.util.updateShortcut
import app.pachli.util.updateShortcuts
import at.connyduck.calladapter.networkresult.fold
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
@ -1085,7 +1085,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
updateProfiles()
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.Canvas
import android.text.TextUtils
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.app.Person
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.designsystem.R as DR
import app.pachli.core.navigation.MainActivityIntent
@ -33,67 +35,65 @@ import java.util.concurrent.ExecutionException
import kotlinx.coroutines.Dispatchers
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 outerSize = context.resources.getDimensionPixelSize(DR.dimen.adaptive_bitmap_outer_size)
val bmp = try {
if (TextUtils.isEmpty(account.profilePictureUrl)) {
Glide.with(context)
.asBitmap()
.load(DR.drawable.avatar_default)
.submit(innerSize, innerSize)
.get()
} else {
Glide.with(context)
.asBitmap()
.load(account.profilePictureUrl)
.error(DR.drawable.avatar_default)
.submit(innerSize, innerSize)
.get()
val maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context)
val shortcuts = accountManager.getAllAccountsOrderedByActive().take(maxShortcuts).mapNotNull { account ->
val drawable = try {
if (TextUtils.isEmpty(account.profilePictureUrl)) {
AppCompatResources.getDrawable(context, DR.drawable.avatar_default)
} else {
Glide.with(context)
.asDrawable()
.load(account.profilePictureUrl)
.error(DR.drawable.avatar_default)
.submit(innerSize, innerSize)
.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
// ExecutionException if the URL does not point at an image. Fallback to
// the default avatar (https://github.com/bumptech/glide/issues/4672).
Glide.with(context)
.asBitmap()
.load(DR.drawable.avatar_default)
.submit(innerSize, innerSize)
.get()
ShortcutInfoCompat.Builder(context, account.id.toString())
.setIntent(intent)
.setCategories(setOf("app.pachli.Share"))
.setShortLabel(account.displayName)
.setPerson(person)
.setLongLived(true)
.setIcon(icon)
.build()
}
// 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)
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))
ShortcutManagerCompat.setDynamicShortcuts(context, shortcuts)
}
fun removeShortcut(context: Context, account: AccountEntity) {