package com.bitcointxoko.gudariwallet.util import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.bitcointxoko.gudariwallet.MainActivity import com.bitcointxoko.gudariwallet.R object NotificationHelper { // ── Intent extra key — read by MainActivity ─────────────────────────────── const val EXTRA_PAYMENT_HASH = "extra_payment_hash" // ── Channel setup ───────────────────────────────────────────────────────── fun createChannels(context: Context) { val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager nm.createNotificationChannel( NotificationChannel( NotificationConstants.CHANNEL_PAYMENTS, context.getString(R.string.notif_channel_payments_name), NotificationManager.IMPORTANCE_HIGH ).apply { description = context.getString(R.string.notif_channel_payments_desc) enableVibration(true) enableLights(true) } ) nm.createNotificationChannel( NotificationChannel( NotificationConstants.CHANNEL_SERVICE_STATUS, context.getString(R.string.notif_channel_service_name), NotificationManager.IMPORTANCE_LOW ).apply { description = context.getString(R.string.notif_channel_service_desc) setShowBadge(false) enableVibration(false) setSound(null, null) } ) nm.createNotificationChannel( NotificationChannel( NotificationConstants.CHANNEL_SYNC, context.getString(R.string.notif_channel_sync_name), NotificationManager.IMPORTANCE_DEFAULT ).apply { description = context.getString(R.string.notif_channel_sync_desc) enableVibration(false) setSound(null, null) } ) } // ── User-facing notifications ───────────────────────────────────────────── /** * Posts a high-priority notification for an incoming payment. * Tapping opens MainActivity and navigates directly to the payment detail * screen for [paymentHash]. */ fun notifyPaymentReceived(context: Context, amountSats: Long, memo: String?, paymentHash: String) { val title = context.getString(R.string.notif_payment_received_title, amountSats) val text = if (!memo.isNullOrBlank()) memo else context.getString(R.string.notif_payment_received_no_memo) val notification = NotificationCompat.Builder(context, NotificationConstants.CHANNEL_PAYMENTS) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(title) .setContentText(text) .setAutoCancel(true) .setContentIntent(mainActivityPendingIntent(context, paymentHash)) // BigTextStyle shows the full memo when the notification is expanded, // in case it is long enough to be truncated in the compact view. .setStyle( NotificationCompat.BigTextStyle() .bigText(text) .setSummaryText(context.getString(R.string.notif_payment_received_summary, amountSats)) ) .build() notify(context, NotificationConstants.NOTIF_ID_PAYMENT_RECEIVED, notification) } /** * Posts a default-priority notification for a successfully sent payment. * Tapping opens MainActivity and navigates directly to the payment detail * screen for [paymentHash]. */ fun notifyPaymentSent(context: Context, amountSats: Long, paymentHash: String) { val title = context.getString(R.string.notif_payment_sent_title) val text = context.getString(R.string.notif_payment_sent_text, amountSats) notify( context, NotificationConstants.NOTIF_ID_PAYMENT_SENT, buildPaymentNotification(context, title, text, paymentHash) ) } /** * 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) { val title = context.getString(R.string.notif_sync_complete_title) val text = context.getString(R.string.notif_sync_complete_text, paymentCount) notify( context, NotificationConstants.NOTIF_ID_SYNC_COMPLETE, NotificationCompat.Builder(context, NotificationConstants.CHANNEL_SYNC) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(title) .setContentText(text) .setAutoCancel(true) .build() ) } // ── Foreground service notification ─────────────────────────────────────── fun buildServiceNotification(context: Context): Notification { return NotificationCompat.Builder(context, NotificationConstants.CHANNEL_SERVICE_STATUS) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(context.getString(R.string.notif_service_title)) .setContentText(context.getString(R.string.notif_service_text)) .setOngoing(true) .setSilent(true) .setShowWhen(false) .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) // Service notification taps open the app with no specific destination .setContentIntent(mainActivityPendingIntent(context, paymentHash = null)) .build() } // ── Dismiss ─────────────────────────────────────────────────────────────── fun cancel(context: Context, notificationId: Int) { NotificationManagerCompat.from(context).cancel(notificationId) } // ── Private helpers ─────────────────────────────────────────────────────── private fun buildPaymentNotification( context : Context, title : String, text : String, paymentHash: String ): Notification { return NotificationCompat.Builder(context, NotificationConstants.CHANNEL_PAYMENTS) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(title) .setContentText(text) .setAutoCancel(true) .setContentIntent(mainActivityPendingIntent(context, paymentHash)) .build() } /** * Builds a PendingIntent that opens MainActivity. * * If [paymentHash] is non-null, it is attached as [EXTRA_PAYMENT_HASH]. * MainActivity reads this in onCreate / onNewIntent and navigates to the * payment detail screen. * * FLAG_UPDATE_CURRENT ensures that if a second notification arrives before * the first is tapped, the hash is updated to the latest payment. * Each notification ID gets its own requestCode so they don't overwrite * each other's extras. */ private fun mainActivityPendingIntent(context: Context, paymentHash: String?): PendingIntent { val intent = Intent(context, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP if (paymentHash != null) { putExtra(EXTRA_PAYMENT_HASH, paymentHash) } } // Use the hash's hashCode as requestCode so each unique payment gets // its own PendingIntent — prevents one notification's tap from // overwriting another's extras. val requestCode = paymentHash?.hashCode() ?: 0 return PendingIntent.getActivity( context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } private fun notify(context: Context, id: Int, notification: Notification) { runCatching { NotificationManagerCompat.from(context).notify(id, notification) } } }