From ae3f4b3dcdc6ccec1469bd4e48b371a8fd05eef3 Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Thu, 19 Feb 2026 19:48:15 +0000
Subject: [PATCH 1/2] Initial plan
From bf48b30a11817f12b297a3245e5e514a8669688c Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Thu, 19 Feb 2026 19:55:46 +0000
Subject: [PATCH 2/2] feat: add dynamic app shortcuts and overlay card view
Add ShortcutManager to manage top 4 cards as app shortcuts.
Add CardShortcutActivity to display single card as overlay.
Integrate biometric authentication in overlay activity.
Update shortcuts automatically when cards or sort order changes.
Co-authored-by: pawcoding <78467484+pawcoding@users.noreply.github.com>
---
app/src/main/AndroidManifest.xml | 9 ++
.../pawcode/cardstore/CardShortcutActivity.kt | 113 ++++++++++++++++++
.../data/managers/ShortcutManager.kt | 54 +++++++++
.../cardstore/ui/screens/CardListScreen.kt | 10 ++
4 files changed, 186 insertions(+)
create mode 100644 app/src/main/java/de/pawcode/cardstore/CardShortcutActivity.kt
create mode 100644 app/src/main/java/de/pawcode/cardstore/data/managers/ShortcutManager.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 67fa63e..626fc93 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,6 +44,15 @@
+
+
+
diff --git a/app/src/main/java/de/pawcode/cardstore/CardShortcutActivity.kt b/app/src/main/java/de/pawcode/cardstore/CardShortcutActivity.kt
new file mode 100644
index 0000000..1f0722f
--- /dev/null
+++ b/app/src/main/java/de/pawcode/cardstore/CardShortcutActivity.kt
@@ -0,0 +1,113 @@
+package de.pawcode.cardstore
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.unit.dp
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.lifecycleScope
+import de.pawcode.cardstore.data.database.entities.CardEntity
+import de.pawcode.cardstore.data.database.repositories.CardRepository
+import de.pawcode.cardstore.data.managers.PreferencesManager
+import de.pawcode.cardstore.data.managers.ShortcutManager
+import de.pawcode.cardstore.data.services.BiometricAuthService
+import de.pawcode.cardstore.ui.components.BiometricPlaceholder
+import de.pawcode.cardstore.ui.sheets.ViewCardSheet
+import de.pawcode.cardstore.ui.theme.CardStoreTheme
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+class CardShortcutActivity : FragmentActivity() {
+ private var cardRepository: CardRepository? = null
+ private var preferencesManager: PreferencesManager? = null
+ private var card by mutableStateOf(null)
+ private var isAuthenticated by mutableStateOf(false)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ preferencesManager = PreferencesManager(applicationContext)
+ cardRepository = CardRepository(applicationContext)
+
+ val cardId = intent.getStringExtra(ShortcutManager.EXTRA_CARD_ID)
+
+ if (cardId == null) {
+ finish()
+ return
+ }
+
+ lifecycleScope.launch {
+ val cardWithLabels = cardRepository?.getCardById(cardId)?.first()
+ card = cardWithLabels?.card
+ if (card == null) {
+ finish()
+ return@launch
+ }
+
+ // Increment usage count
+ card?.let { currentCard ->
+ val updatedCard =
+ currentCard.copy(useCount = currentCard.useCount + 1, lastUsed = System.currentTimeMillis())
+ cardRepository?.updateCard(updatedCard)
+ }
+ }
+
+ checkAuthentication()
+
+ setContent {
+ CardStoreTheme {
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .background(Color.Black.copy(alpha = 0.5f))
+ .pointerInput(Unit) { detectTapGestures(onTap = { finish() }) },
+ contentAlignment = Alignment.Center,
+ ) {
+ Box(
+ modifier =
+ Modifier.padding(16.dp).pointerInput(Unit) {
+ detectTapGestures(onTap = {
+ // Prevent tap from propagating to parent
+ })
+ }
+ ) {
+ if (isAuthenticated) {
+ card?.let { ViewCardSheet(it) }
+ } else {
+ BiometricPlaceholder(onRetry = { checkAuthentication() })
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun checkAuthentication() {
+ lifecycleScope.launch {
+ val biometricEnabled = preferencesManager?.biometricEnabled?.first() ?: false
+ if (biometricEnabled && BiometricAuthService.isBiometricAvailable(this@CardShortcutActivity)) {
+ BiometricAuthService.authenticate(
+ activity = this@CardShortcutActivity,
+ title = getString(R.string.biometric_auth_title),
+ subtitle = getString(R.string.biometric_auth_subtitle),
+ onSuccess = { isAuthenticated = true },
+ onError = { finish() },
+ )
+ } else {
+ isAuthenticated = true
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/de/pawcode/cardstore/data/managers/ShortcutManager.kt b/app/src/main/java/de/pawcode/cardstore/data/managers/ShortcutManager.kt
new file mode 100644
index 0000000..0c8e742
--- /dev/null
+++ b/app/src/main/java/de/pawcode/cardstore/data/managers/ShortcutManager.kt
@@ -0,0 +1,54 @@
+package de.pawcode.cardstore.data.managers
+
+import android.content.Context
+import android.content.Intent
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import de.pawcode.cardstore.CardShortcutActivity
+import de.pawcode.cardstore.R
+import de.pawcode.cardstore.data.database.entities.CardEntity
+import de.pawcode.cardstore.data.enums.SortAttribute
+import de.pawcode.cardstore.utils.calculateCardScore
+
+object ShortcutManager {
+ private const val MAX_SHORTCUTS = 4
+ const val EXTRA_CARD_ID = "card_id"
+
+ fun updateShortcuts(context: Context, cards: List, sortAttribute: SortAttribute) {
+ val topCards = getTopCards(cards, sortAttribute).take(MAX_SHORTCUTS)
+
+ val shortcuts =
+ topCards.mapIndexed { index, card ->
+ val intent =
+ Intent(context, CardShortcutActivity::class.java).apply {
+ action = Intent.ACTION_VIEW
+ putExtra(EXTRA_CARD_ID, card.cardId)
+ }
+
+ ShortcutInfoCompat.Builder(context, "card_${card.cardId}")
+ .setShortLabel(card.storeName)
+ .setLongLabel(card.storeName)
+ .setIcon(IconCompat.createWithResource(context, R.mipmap.ic_launcher))
+ .setIntent(intent)
+ .setRank(index)
+ .build()
+ }
+
+ ShortcutManagerCompat.removeAllDynamicShortcuts(context)
+ ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)
+ }
+
+ fun clearShortcuts(context: Context) {
+ ShortcutManagerCompat.removeAllDynamicShortcuts(context)
+ }
+
+ private fun getTopCards(cards: List, sortAttribute: SortAttribute): List {
+ return when (sortAttribute) {
+ SortAttribute.INTELLIGENT -> cards.sortedByDescending { calculateCardScore(it) }
+ SortAttribute.ALPHABETICALLY -> cards.sortedBy { it.storeName }
+ SortAttribute.RECENTLY_USED -> cards.sortedByDescending { it.lastUsed }
+ SortAttribute.MOST_USED -> cards.sortedByDescending { it.useCount }
+ }
+ }
+}
diff --git a/app/src/main/java/de/pawcode/cardstore/ui/screens/CardListScreen.kt b/app/src/main/java/de/pawcode/cardstore/ui/screens/CardListScreen.kt
index 863b35e..d1d3183 100644
--- a/app/src/main/java/de/pawcode/cardstore/ui/screens/CardListScreen.kt
+++ b/app/src/main/java/de/pawcode/cardstore/ui/screens/CardListScreen.kt
@@ -43,6 +43,7 @@ import de.pawcode.cardstore.data.database.entities.EXAMPLE_LABEL_LIST
import de.pawcode.cardstore.data.database.entities.LabelEntity
import de.pawcode.cardstore.data.enums.SortAttribute
import de.pawcode.cardstore.data.managers.PreferencesManager
+import de.pawcode.cardstore.data.managers.ShortcutManager
import de.pawcode.cardstore.data.services.DeeplinkService
import de.pawcode.cardstore.data.services.ReviewService
import de.pawcode.cardstore.data.services.SnackbarService
@@ -137,6 +138,7 @@ fun CardListScreenComponent(
onSortChange: (SortAttribute) -> Unit,
onShowAbout: () -> Unit,
) {
+ val context = LocalContext.current
val cards by cardsFlow.collectAsState(initial = emptyList())
val labels by labelsFlow.collectAsState(initial = emptyList())
val sortBy by sortByFlow.collectAsState(initial = null)
@@ -183,6 +185,14 @@ fun CardListScreenComponent(
LaunchedEffect(sortBy, selectedLabel) { listState.scrollToItem(0) }
+ // Update app shortcuts when cards or sort order changes
+ val currentContext = context
+ LaunchedEffect(cardsFiltered, sortBy) {
+ sortBy?.let { currentSortBy ->
+ ShortcutManager.updateShortcuts(currentContext, cardsFiltered, currentSortBy)
+ }
+ }
+
Scaffold(
topBar = {
AppBar(