fix: tapping on sync completed notification navigates to history
This commit is contained in:
@@ -38,6 +38,7 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import cafe.adriel.lyricist.ProvideStrings
|
import cafe.adriel.lyricist.ProvideStrings
|
||||||
import com.bitcointxoko.gudariwallet.data.HistoricalSyncStore
|
import com.bitcointxoko.gudariwallet.data.HistoricalSyncStore
|
||||||
|
import com.bitcointxoko.gudariwallet.util.NotificationConstants
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// WalletScreen observes this and navigates to the detail screen.
|
// WalletScreen observes this and navigates to the detail screen.
|
||||||
// MutableState so Compose recomposes when it changes.
|
// MutableState so Compose recomposes when it changes.
|
||||||
private val pendingPaymentHash = mutableStateOf<String?>(null)
|
private val pendingPaymentHash = mutableStateOf<String?>(null)
|
||||||
|
val pendingNavigateToHistory = mutableStateOf(false)
|
||||||
|
|
||||||
// Holds a payment URI that arrived via deep link / intent (BTCPay, external apps).
|
// Holds a payment URI that arrived via deep link / intent (BTCPay, external apps).
|
||||||
private val pendingPaymentUri = mutableStateOf<String?>(null)
|
private val pendingPaymentUri = mutableStateOf<String?>(null)
|
||||||
@@ -102,6 +104,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// Read hash from the intent that launched the activity (cold start from notification)
|
// Read hash from the intent that launched the activity (cold start from notification)
|
||||||
pendingPaymentHash.value =
|
pendingPaymentHash.value =
|
||||||
intent.getStringExtra(NotificationHelper.EXTRA_PAYMENT_HASH)
|
intent.getStringExtra(NotificationHelper.EXTRA_PAYMENT_HASH)
|
||||||
|
|
||||||
|
if (intent.getBooleanExtra(NotificationConstants.NAVIGATE_TO_HISTORY, false)) {
|
||||||
|
pendingNavigateToHistory.value = true
|
||||||
|
}
|
||||||
// Read payment URI from the intent that launched the activity (cold start from deep link)
|
// Read payment URI from the intent that launched the activity (cold start from deep link)
|
||||||
pendingPaymentUri.value = IntentUtils.extractPaymentUri(intent)
|
pendingPaymentUri.value = IntentUtils.extractPaymentUri(intent)
|
||||||
|
|
||||||
@@ -132,6 +138,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
vm = vm,
|
vm = vm,
|
||||||
nfcVm = nfcVm,
|
nfcVm = nfcVm,
|
||||||
pendingPaymentHash = pendingPaymentHash,
|
pendingPaymentHash = pendingPaymentHash,
|
||||||
|
pendingNavigateToHistory = pendingNavigateToHistory,
|
||||||
pendingPaymentUri = pendingPaymentUri,
|
pendingPaymentUri = pendingPaymentUri,
|
||||||
lyricist = lyricist
|
lyricist = lyricist
|
||||||
)
|
)
|
||||||
@@ -187,6 +194,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
pendingPaymentHash.value = hash
|
pendingPaymentHash.value = hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (intent.getBooleanExtra(NotificationConstants.NAVIGATE_TO_HISTORY, false)) {
|
||||||
|
pendingNavigateToHistory.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// Deep link / external app URI
|
// Deep link / external app URI
|
||||||
val uri = IntentUtils.extractPaymentUri(intent)
|
val uri = IntentUtils.extractPaymentUri(intent)
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ fun WalletScreen(
|
|||||||
vm : WalletViewModel,
|
vm : WalletViewModel,
|
||||||
nfcVm : NfcViewModel,
|
nfcVm : NfcViewModel,
|
||||||
pendingPaymentHash: MutableState<String?>,
|
pendingPaymentHash: MutableState<String?>,
|
||||||
|
pendingNavigateToHistory : MutableState<Boolean>,
|
||||||
pendingPaymentUri : MutableState<String?>,
|
pendingPaymentUri : MutableState<String?>,
|
||||||
lyricist : Lyricist<AppStrings>
|
lyricist : Lyricist<AppStrings>
|
||||||
) {
|
) {
|
||||||
@@ -141,6 +142,15 @@ fun WalletScreen(
|
|||||||
pendingPaymentHash.value = null
|
pendingPaymentHash.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val navigateToHistory = pendingNavigateToHistory.value
|
||||||
|
LaunchedEffect(navigateToHistory) {
|
||||||
|
if (navigateToHistory) {
|
||||||
|
navController.navigate(ROUTE_HISTORY) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
pendingNavigateToHistory.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val activity = context as MainActivity
|
val activity = context as MainActivity
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.os.PowerManager
|
|||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -17,6 +18,7 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.BatteryFull
|
import androidx.compose.material.icons.filled.BatteryFull
|
||||||
|
import androidx.compose.material.icons.filled.ContentPaste
|
||||||
import androidx.compose.material.icons.filled.History
|
import androidx.compose.material.icons.filled.History
|
||||||
import androidx.compose.material.icons.filled.Notifications
|
import androidx.compose.material.icons.filled.Notifications
|
||||||
import androidx.compose.material.icons.filled.Visibility
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
@@ -28,7 +30,6 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@@ -39,9 +40,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalClipboard
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
@@ -52,6 +55,7 @@ import androidx.core.net.toUri
|
|||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
import com.bitcointxoko.gudariwallet.i18n.AppStrings
|
import com.bitcointxoko.gudariwallet.i18n.AppStrings
|
||||||
import com.bitcointxoko.gudariwallet.sync.HistoricalSyncWorker
|
import com.bitcointxoko.gudariwallet.sync.HistoricalSyncWorker
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
// ── Page 0: Welcome ────────────────────────────────────────────────────────────
|
// ── Page 0: Welcome ────────────────────────────────────────────────────────────
|
||||||
@Composable
|
@Composable
|
||||||
@@ -81,7 +85,7 @@ internal fun WelcomePage(strings: AppStrings) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Page 1: Server URL ─────────────────────────────────────────────────────────
|
// ── Page 1: Server URL ───────────────────────────────────────────────────────
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ServerUrlPage(
|
internal fun ServerUrlPage(
|
||||||
strings: AppStrings,
|
strings: AppStrings,
|
||||||
@@ -89,9 +93,12 @@ internal fun ServerUrlPage(
|
|||||||
onBaseUrl: (String) -> Unit,
|
onBaseUrl: (String) -> Unit,
|
||||||
error: String?
|
error: String?
|
||||||
) {
|
) {
|
||||||
|
val clipboard = LocalClipboard.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
OnboardingPageScaffold(
|
OnboardingPageScaffold(
|
||||||
title = strings.onboarding.serverUrl, // ← "Server URL"
|
title = strings.onboarding.serverUrl,
|
||||||
subtitle = strings.onboarding.serverUrlHint // ← "https://bitcointxoko.org"
|
subtitle = strings.onboarding.serverUrlHint
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = baseUrl,
|
value = baseUrl,
|
||||||
@@ -101,13 +108,26 @@ internal fun ServerUrlPage(
|
|||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||||
isError = error != null
|
isError = error != null,
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
scope.launch {
|
||||||
|
clipboard.getClipEntry()
|
||||||
|
?.clipData?.getItemAt(0)?.text?.toString()
|
||||||
|
?.let { onBaseUrl(it) }}
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ContentPaste,
|
||||||
|
contentDescription = "Paste server URL"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
ErrorText(error)
|
ErrorText(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Page 2: Invoice Key ────────────────────────────────────────────────────────
|
// ── Page 2: Invoice Key ──────────────────────────────────────────────────────
|
||||||
@Composable
|
@Composable
|
||||||
internal fun InvoiceKeyPage(
|
internal fun InvoiceKeyPage(
|
||||||
strings: AppStrings,
|
strings: AppStrings,
|
||||||
@@ -115,8 +135,11 @@ internal fun InvoiceKeyPage(
|
|||||||
onInvoiceKey: (String) -> Unit,
|
onInvoiceKey: (String) -> Unit,
|
||||||
error: String?
|
error: String?
|
||||||
) {
|
) {
|
||||||
|
val clipboard = LocalClipboard.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
OnboardingPageScaffold(
|
OnboardingPageScaffold(
|
||||||
title = strings.onboarding.invoiceKey, // ← "Invoice Key (read-only)"
|
title = strings.onboarding.invoiceKey,
|
||||||
subtitle = "Used to generate payment requests."
|
subtitle = "Used to generate payment requests."
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -127,13 +150,27 @@ internal fun InvoiceKeyPage(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
isError = error != null
|
isError = error != null,
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
scope.launch {
|
||||||
|
clipboard.getClipEntry()
|
||||||
|
?.clipData?.getItemAt(0)?.text?.toString()
|
||||||
|
?.let { onInvoiceKey(it) }
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ContentPaste,
|
||||||
|
contentDescription = "Paste invoice key"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
ErrorText(error)
|
ErrorText(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Page 3: Admin Key ──────────────────────────────────────────────────────────
|
// ── Page 3: Admin Key ────────────────────────────────────────────────────────
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AdminKeyPage(
|
internal fun AdminKeyPage(
|
||||||
strings: AppStrings,
|
strings: AppStrings,
|
||||||
@@ -143,8 +180,11 @@ internal fun AdminKeyPage(
|
|||||||
onToggleVis: () -> Unit,
|
onToggleVis: () -> Unit,
|
||||||
error: String?
|
error: String?
|
||||||
) {
|
) {
|
||||||
|
val clipboard = LocalClipboard.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
OnboardingPageScaffold(
|
OnboardingPageScaffold(
|
||||||
title = strings.onboarding.adminKey, // ← "Admin Key (for sending)"
|
title = strings.onboarding.adminKey,
|
||||||
subtitle = "Required to send payments."
|
subtitle = "Required to send payments."
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -158,6 +198,20 @@ internal fun AdminKeyPage(
|
|||||||
else PasswordVisualTransformation(),
|
else PasswordVisualTransformation(),
|
||||||
isError = error != null,
|
isError = error != null,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
|
// Two icons: paste + visibility toggle
|
||||||
|
Row {
|
||||||
|
IconButton(onClick = {
|
||||||
|
scope.launch {
|
||||||
|
clipboard.getClipEntry()
|
||||||
|
?.clipData?.getItemAt(0)?.text?.toString()
|
||||||
|
?.let { onAdminKey(it) }
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ContentPaste,
|
||||||
|
contentDescription = "Paste admin key"
|
||||||
|
)
|
||||||
|
}
|
||||||
IconButton(onClick = onToggleVis) {
|
IconButton(onClick = onToggleVis) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (adminVisible) Icons.Default.VisibilityOff
|
imageVector = if (adminVisible) Icons.Default.VisibilityOff
|
||||||
@@ -167,6 +221,7 @@ internal fun AdminKeyPage(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
ErrorText(error)
|
ErrorText(error)
|
||||||
}
|
}
|
||||||
@@ -348,7 +403,7 @@ fun SyncHistoryPage(
|
|||||||
// ── Scheduled (deferred, waiting for delay + network) ─────────────
|
// ── Scheduled (deferred, waiting for delay + network) ─────────────
|
||||||
SyncUiState.Scheduled -> {
|
SyncUiState.Scheduled -> {
|
||||||
Text(
|
Text(
|
||||||
text = "Sync scheduled — will run when your device is online.",
|
text = "Sync scheduled — will run when your device is online. You will get a notification when the sync is done. You can already start using the app.",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
|
|||||||
@@ -64,6 +64,21 @@ fun OnboardingScreen(
|
|||||||
|
|
||||||
LaunchedEffect(pagerState.currentPage) { error = null }
|
LaunchedEffect(pagerState.currentPage) { error = null }
|
||||||
|
|
||||||
|
var previousPage by remember { mutableIntStateOf(0) }
|
||||||
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
|
val current = pagerState.currentPage
|
||||||
|
if (current > previousPage) {
|
||||||
|
// User moved forward — save whatever the previous page owned
|
||||||
|
when (previousPage) {
|
||||||
|
PAGE_SERVER_URL -> secretStore.saveBaseUrl(baseUrl)
|
||||||
|
PAGE_INVOICE -> secretStore.saveInvoiceKey(invoiceKey)
|
||||||
|
PAGE_ADMIN -> secretStore.saveAdminKey(adminKey)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previousPage = current
|
||||||
|
}
|
||||||
|
|
||||||
fun validateCurrentPage(): Boolean {
|
fun validateCurrentPage(): Boolean {
|
||||||
error = when (pagerState.currentPage) {
|
error = when (pagerState.currentPage) {
|
||||||
PAGE_SERVER_URL -> if (baseUrl.isBlank()) strings.onboarding.fieldsRequired else null
|
PAGE_SERVER_URL -> if (baseUrl.isBlank()) strings.onboarding.fieldsRequired else null
|
||||||
@@ -83,7 +98,9 @@ fun OnboardingScreen(
|
|||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
userScrollEnabled = true
|
userScrollEnabled = pagerState.currentPage !in listOf(
|
||||||
|
PAGE_SERVER_URL, PAGE_INVOICE, PAGE_ADMIN
|
||||||
|
)
|
||||||
) { page ->
|
) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
PAGE_WELCOME -> WelcomePage(strings)
|
PAGE_WELCOME -> WelcomePage(strings)
|
||||||
@@ -152,6 +169,7 @@ fun OnboardingScreen(
|
|||||||
val isLastPage = pagerState.currentPage == PAGE_BATTERY
|
val isLastPage = pagerState.currentPage == PAGE_BATTERY
|
||||||
|
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
|
if (!validateCurrentPage()) return@Button
|
||||||
scope.launch {
|
scope.launch {
|
||||||
// Save each field as soon as the user advances past it
|
// Save each field as soon as the user advances past it
|
||||||
when (pagerState.currentPage) {
|
when (pagerState.currentPage) {
|
||||||
@@ -163,7 +181,7 @@ fun OnboardingScreen(
|
|||||||
pagerState.animateScrollToPage(pagerState.currentPage + 1)
|
pagerState.animateScrollToPage(pagerState.currentPage + 1)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Text(if (isLastPage) strings.onboarding.next else strings.onboarding.next)
|
Text(strings.onboarding.next)
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ object NotificationConstants {
|
|||||||
const val CHANNEL_PAYMENTS = "gudari_payments"
|
const val CHANNEL_PAYMENTS = "gudari_payments"
|
||||||
const val CHANNEL_SERVICE_STATUS = "gudari_service"
|
const val CHANNEL_SERVICE_STATUS = "gudari_service"
|
||||||
const val CHANNEL_SYNC = "gudari_sync"
|
const val CHANNEL_SYNC = "gudari_sync"
|
||||||
|
const val NAVIGATE_TO_HISTORY = "navigate_to_history"
|
||||||
|
|
||||||
// ── Notification IDs ──────────────────────────────────────────────────────
|
// ── Notification IDs ──────────────────────────────────────────────────────
|
||||||
// ID 1 is reserved for the foreground service — Android requires a stable,
|
// ID 1 is reserved for the foreground service — Android requires a stable,
|
||||||
|
|||||||
@@ -106,8 +106,6 @@ object NotificationHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts a notification when the one-time historical payment sync completes.
|
* Posts a notification when the one-time historical payment sync completes.
|
||||||
* No tap destination needed — the user is already in the app or will land
|
|
||||||
* on the main screen via the default MainActivity launch.
|
|
||||||
*/
|
*/
|
||||||
fun notifySyncComplete(context: Context, paymentCount: Int) {
|
fun notifySyncComplete(context: Context, paymentCount: Int) {
|
||||||
val title = context.getString(R.string.notif_sync_complete_title)
|
val title = context.getString(R.string.notif_sync_complete_title)
|
||||||
@@ -120,10 +118,12 @@ object NotificationHelper {
|
|||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(text)
|
.setContentText(text)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
|
.setContentIntent(syncCompletePendingIntent(context))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── Foreground service notification ───────────────────────────────────────
|
// ── Foreground service notification ───────────────────────────────────────
|
||||||
|
|
||||||
fun buildServiceNotification(context: Context): Notification {
|
fun buildServiceNotification(context: Context): Notification {
|
||||||
@@ -194,6 +194,23 @@ object NotificationHelper {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a PendingIntent that opens MainActivity and signals it to navigate
|
||||||
|
* to the payment history screen via [NotificationConstants.NAVIGATE_TO_HISTORY].
|
||||||
|
*/
|
||||||
|
private fun syncCompletePendingIntent(context: Context): PendingIntent {
|
||||||
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
putExtra(NotificationConstants.NAVIGATE_TO_HISTORY, true)
|
||||||
|
}
|
||||||
|
return PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
NotificationConstants.NOTIF_ID_SYNC_COMPLETE, // stable, unique requestCode
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun notify(context: Context, id: Int, notification: Notification) {
|
private fun notify(context: Context, id: Int, notification: Notification) {
|
||||||
runCatching {
|
runCatching {
|
||||||
NotificationManagerCompat.from(context).notify(id, notification)
|
NotificationManagerCompat.from(context).notify(id, notification)
|
||||||
|
|||||||
Reference in New Issue
Block a user