diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/NotificationPermissionScreen.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/NotificationPermissionScreen.kt deleted file mode 100644 index 272274e..0000000 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/NotificationPermissionScreen.kt +++ /dev/null @@ -1,245 +0,0 @@ -package com.bitcointxoko.gudariwallet.ui - -import android.Manifest -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.provider.Settings -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Notifications -import androidx.compose.material.icons.filled.NotificationsOff -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp - -// ─── Permission state model ─────────────────────────────────────────────────── - -sealed class NotificationPermissionState { - data object NeverAsked : NotificationPermissionState() - data object Rationale : NotificationPermissionState() // asked once, denied - data object PermanentlyDenied: NotificationPermissionState() // "don't ask again" - data object Granted : NotificationPermissionState() -} - -// ─── Prefs helper ───────────────────────────────────────────────────────────── - -/** - * Persists a flag so we can distinguish between: - * - "never asked" → show the first-time request screen - * - "asked and denied" → show rationale - * - "permanently denied"→ send user to system settings - */ -object PermissionPrefs { - private const val PREFS_NAME = "lnbits_prefs" - private const val KEY = "notification_permission_requested" - - fun hasRequestedBefore(context: Context): Boolean = - context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - .getBoolean(KEY, false) - - fun markRequested(context: Context) = - context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - .edit().putBoolean(KEY, true).apply() -} - -// ─── Main composable ────────────────────────────────────────────────────────── - -/** - * Handles the full POST_NOTIFICATIONS runtime permission flow. - * - * On Android 12 and below this permission doesn't exist — we skip straight - * to onPermissionGranted since notifications are always allowed. - * - * @param hasRequestedBefore Read from PermissionPrefs — distinguishes - * "never asked" from "permanently denied" - * @param onPermissionGranted Called when permission is granted - * @param onPermissionDenied Called when user skips or permanently denies - * @param onRequestedFirstTime Called after the first system prompt fires — - * persists the flag via PermissionPrefs - */ -@Composable -fun NotificationPermissionScreen( - hasRequestedBefore: Boolean, - onPermissionGranted: () -> Unit, - onPermissionDenied: () -> Unit, - onRequestedFirstTime: () -> Unit -) { - // Android 12 and below — POST_NOTIFICATIONS doesn't exist, skip entirely - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - LaunchedEffect(Unit) { onPermissionGranted() } - return - } - - val context = LocalContext.current - - // Tracks whether the system already granted the permission - // (e.g. user granted it in a previous session) - var permissionState by remember { - mutableStateOf( - when { - !hasRequestedBefore -> NotificationPermissionState.NeverAsked - else -> NotificationPermissionState.Rationale - } - ) - } - - // The system permission launcher - val launcher = rememberLauncherForActivityResult( - ActivityResultContracts.RequestPermission() - ) { granted -> - permissionState = if (granted) { - NotificationPermissionState.Granted - } else { - // If we've already asked before and they denied again → permanent denial - if (hasRequestedBefore) NotificationPermissionState.PermanentlyDenied - else NotificationPermissionState.Rationale - } - onRequestedFirstTime() - } - - // React to state changes - LaunchedEffect(permissionState) { - when (permissionState) { - is NotificationPermissionState.Granted -> onPermissionGranted() - else -> { /* handled by UI below */ } - } - } - - // ── UI ──────────────────────────────────────────────────────────────────── - when (permissionState) { - - is NotificationPermissionState.NeverAsked, - is NotificationPermissionState.Rationale -> { - PermissionRequestCard( - onAllow = { - launcher.launch(Manifest.permission.POST_NOTIFICATIONS) - }, - onSkip = onPermissionDenied - ) - } - - is NotificationPermissionState.PermanentlyDenied -> { - PermanentlyDeniedCard( - onOpenSettings = { - // Send user to the app's system notification settings - context.startActivity( - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { - data = Uri.fromParts("package", context.packageName, null) - } - ) - }, - onSkip = onPermissionDenied - ) - } - - is NotificationPermissionState.Granted -> { - // LaunchedEffect above handles this — show nothing - } - } -} - -// ─── Sub-composables ────────────────────────────────────────────────────────── - -@Composable -private fun PermissionRequestCard( - onAllow: () -> Unit, - onSkip: () -> Unit -) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(32.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = Icons.Default.Notifications, - contentDescription = null, - modifier = Modifier.size(72.dp), - tint = MaterialTheme.colorScheme.primary - ) - Spacer(Modifier.height(24.dp)) - Text( - text = "Stay notified of payments", - style = MaterialTheme.typography.headlineSmall, - textAlign = TextAlign.Center - ) - Spacer(Modifier.height(12.dp)) - Text( - text = "Allow notifications so you know the moment a Lightning payment arrives, even when the app is in the background.", - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(Modifier.height(40.dp)) - Button( - onClick = onAllow, - modifier = Modifier.fillMaxWidth() - ) { - Text("Allow Notifications") - } - Spacer(Modifier.height(12.dp)) - TextButton( - onClick = onSkip, - modifier = Modifier.fillMaxWidth() - ) { - Text("Not Now") - } - } -} - -@Composable -private fun PermanentlyDeniedCard( - onOpenSettings: () -> Unit, - onSkip: () -> Unit -) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(32.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = Icons.Default.NotificationsOff, - contentDescription = null, - modifier = Modifier.size(72.dp), - tint = MaterialTheme.colorScheme.error - ) - Spacer(Modifier.height(24.dp)) - Text( - text = "Notifications are blocked", - style = MaterialTheme.typography.headlineSmall, - textAlign = TextAlign.Center - ) - Spacer(Modifier.height(12.dp)) - Text( - text = "You've permanently denied notifications. To receive payment alerts, enable them in system settings.", - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(Modifier.height(40.dp)) - Button( - onClick = onOpenSettings, - modifier = Modifier.fillMaxWidth() - ) { - Text("Open Settings") - } - Spacer(Modifier.height(12.dp)) - TextButton( - onClick = onSkip, - modifier = Modifier.fillMaxWidth() - ) { - Text("Skip") - } - } -} diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/PaymentSuccessScreen.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/PaymentSuccessScreen.kt deleted file mode 100644 index 1fe0849..0000000 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/PaymentSuccessScreen.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.bitcointxoko.gudariwallet.ui - -import androidx.compose.animation.core.* -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.CheckCircle -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp - -/** - * Shown when an invoice has been paid. - * Displays a pulsing checkmark, the amount received, and the memo. - * - * @param amountSats Amount received in satoshis - * @param memo Invoice memo - * @param onDone Called when the user taps "Done" — navigates back to Home - * @param onReceiveMore Called when the user taps "Receive More" — resets the screen - */ -@Composable -fun PaymentSuccessScreen( - amountSats: Long, - memo: String, - onDone: () -> Unit, - onReceiveMore: () -> Unit -) { - // Pulsing scale animation on the checkmark icon - val infiniteTransition = rememberInfiniteTransition(label = "pulse") - val scale by infiniteTransition.animateFloat( - initialValue = 1f, - targetValue = 1.08f, - animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 700, easing = EaseInOut), - repeatMode = RepeatMode.Reverse - ), - label = "checkScale" - ) - - Column( - modifier = Modifier - .fillMaxSize() - .padding(32.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - // ── Animated checkmark ──────────────────────────────────────────── - Icon( - imageVector = Icons.Default.CheckCircle, - contentDescription = "Payment received", - tint = MaterialTheme.colorScheme.tertiary, // green role - modifier = Modifier - .size(96.dp) - .scale(scale) - ) - - Spacer(Modifier.height(32.dp)) - - // ── Title ───────────────────────────────────────────────────────── - Text( - text = "Payment Received!", - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onBackground - ) - - Spacer(Modifier.height(16.dp)) - - // ── Amount ──────────────────────────────────────────────────────── - Text( - text = "+$amountSats sats", - style = MaterialTheme.typography.displaySmall, - color = MaterialTheme.colorScheme.tertiary, - textAlign = TextAlign.Center - ) - - // ── Memo ────────────────────────────────────────────────────────── - if (memo.isNotBlank()) { - Spacer(Modifier.height(8.dp)) - Text( - text = memo, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center - ) - } - - Spacer(Modifier.height(48.dp)) - - // ── Actions ─────────────────────────────────────────────────────── - Button( - onClick = onDone, - modifier = Modifier.fillMaxWidth() - ) { - Text("Done") - } - - Spacer(Modifier.height(12.dp)) - - OutlinedButton( - onClick = onReceiveMore, - modifier = Modifier.fillMaxWidth() - ) { - Text("Receive More") - } - } -}