refactor: delete unused files
This commit is contained in:
@@ -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<NotificationPermissionState>(
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user