localisation: nwc
This commit is contained in:
@@ -379,6 +379,9 @@ val EnHomeStrings = AppStrings(
|
|||||||
connectionRowExpires = "Expires",
|
connectionRowExpires = "Expires",
|
||||||
connectionRowPubkey = "Public key",
|
connectionRowPubkey = "Public key",
|
||||||
connectionNeverUsed = "Never",
|
connectionNeverUsed = "Never",
|
||||||
|
lastUsedToday = "Last used today",
|
||||||
|
lastUsedYesterday = "Last used yesterday",
|
||||||
|
lastUsedDate = "Last used %s",
|
||||||
connectionNoExpiry = "Never",
|
connectionNoExpiry = "Never",
|
||||||
connectionExpired = { date -> "Expired · $date" },
|
connectionExpired = { date -> "Expired · $date" },
|
||||||
connectionSectionPermissions = "Permissions",
|
connectionSectionPermissions = "Permissions",
|
||||||
@@ -400,6 +403,10 @@ val EnHomeStrings = AppStrings(
|
|||||||
permSignMessage = "Sign messages",
|
permSignMessage = "Sign messages",
|
||||||
permPay = "Send payments",
|
permPay = "Send payments",
|
||||||
permInvoice = "Create invoices",
|
permInvoice = "Create invoices",
|
||||||
|
permLookup = "Lookup invoice status",
|
||||||
|
permHistory = "Read transaction history",
|
||||||
|
permBalance = "Read wallet balance",
|
||||||
|
permInfo = "Read account info",
|
||||||
permLookupHistory = "Lookup status & transaction history",
|
permLookupHistory = "Lookup status & transaction history",
|
||||||
permBalanceInfo = "Read balance & account info",
|
permBalanceInfo = "Read balance & account info",
|
||||||
permSendPayments = "Send payments",
|
permSendPayments = "Send payments",
|
||||||
@@ -412,6 +419,57 @@ val EnHomeStrings = AppStrings(
|
|||||||
revokeDialogBody = "This will permanently revoke this Nostr Wallet Connect key. Any app using it will lose access.",
|
revokeDialogBody = "This will permanently revoke this Nostr Wallet Connect key. Any app using it will lose access.",
|
||||||
revokeDialogConfirm = "Revoke",
|
revokeDialogConfirm = "Revoke",
|
||||||
revokeDialogDismiss = "Cancel",
|
revokeDialogDismiss = "Cancel",
|
||||||
|
refreshDaily = "Daily",
|
||||||
|
refreshWeekly = "Weekly",
|
||||||
|
refreshMonthly = "Monthly",
|
||||||
|
refreshYearly = "Yearly",
|
||||||
|
refreshNever = "Never",
|
||||||
|
|
||||||
|
newConnection = "New connection",
|
||||||
|
labelHint = "Label (e.g. Amethyst, Bitrefill)",
|
||||||
|
expires = "Expires",
|
||||||
|
pickDate = "Pick date",
|
||||||
|
pickTime = "Pick time",
|
||||||
|
never = "Never",
|
||||||
|
budgets = "Budgets",
|
||||||
|
addBudget = "Add budget",
|
||||||
|
removeBudget = "Remove budget",
|
||||||
|
permissions = "Permissions",
|
||||||
|
unnamed = "Unnamed",
|
||||||
|
creating = "Creating…",
|
||||||
|
createConnection = "Create connection",
|
||||||
|
ok = "OK",
|
||||||
|
sats = "Sats",
|
||||||
|
resets = "Resets",
|
||||||
|
|
||||||
|
deleteConnection = "Delete connection",
|
||||||
|
unnamedConnection = "Unnamed connection",
|
||||||
|
neverUsed = "Never used",
|
||||||
|
expired = "Expired",
|
||||||
|
|
||||||
|
connectionCreated = "Connection created",
|
||||||
|
scanOrCopyHint = "Scan or copy this connection string into your app. It will not be shown again.",
|
||||||
|
nwcPairingQrCode = "NWC pairing QR code",
|
||||||
|
nwcConnectionString = "NWC connection string",
|
||||||
|
shareNwcConnectionString = "Share NWC connection string",
|
||||||
|
openInApp = "Open in app",
|
||||||
|
|
||||||
|
walletConnect = "Wallet Connect",
|
||||||
|
addConnection = "Add connection",
|
||||||
|
noCompatibleAppFound = "No compatible app found",
|
||||||
|
removeConnection = "Remove connection?",
|
||||||
|
removeConnectionDetail = "This will permanently revoke this Nostr Wallet Connect key. Any app using it will lose access.",
|
||||||
|
remove = "Remove",
|
||||||
|
|
||||||
|
noConnectionsYet = "No connections yet",
|
||||||
|
tapToAddHint = "Tap + to connect an app via Nostr Wallet Connect.",
|
||||||
|
somethingWentWrong = "Something went wrong",
|
||||||
|
retry = "Retry",
|
||||||
|
|
||||||
|
failedToLoadConnections = "Failed to load connections",
|
||||||
|
failedToCreateConnection = "Failed to create connection",
|
||||||
|
failedToDeleteConnection = "Failed to delete connection",
|
||||||
|
failedToLoadConnection = "Failed to load connection",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -380,6 +380,9 @@ val EsHomeStrings = AppStrings(
|
|||||||
connectionRowExpires = "Vence",
|
connectionRowExpires = "Vence",
|
||||||
connectionRowPubkey = "Clave pública",
|
connectionRowPubkey = "Clave pública",
|
||||||
connectionNeverUsed = "Nunca",
|
connectionNeverUsed = "Nunca",
|
||||||
|
lastUsedToday = "Usada hoy",
|
||||||
|
lastUsedYesterday = "Usada ayer",
|
||||||
|
lastUsedDate = "Usada %s",
|
||||||
connectionNoExpiry = "Sin vencimiento",
|
connectionNoExpiry = "Sin vencimiento",
|
||||||
connectionExpired = { date -> "Vencido · $date" },
|
connectionExpired = { date -> "Vencido · $date" },
|
||||||
connectionSectionPermissions = "Permisos",
|
connectionSectionPermissions = "Permisos",
|
||||||
@@ -401,6 +404,10 @@ val EsHomeStrings = AppStrings(
|
|||||||
permSignMessage = "Firmar mensajes",
|
permSignMessage = "Firmar mensajes",
|
||||||
permPay = "Enviar pagos",
|
permPay = "Enviar pagos",
|
||||||
permInvoice = "Crear facturas",
|
permInvoice = "Crear facturas",
|
||||||
|
permLookup = "Consultar estado de facturas",
|
||||||
|
permHistory = "Leer historial de transacciones",
|
||||||
|
permBalance = "Leer saldo del monedero",
|
||||||
|
permInfo = "Leer información de cuenta",
|
||||||
permLookupHistory = "Consultar estado e historial de transacciones",
|
permLookupHistory = "Consultar estado e historial de transacciones",
|
||||||
permBalanceInfo = "Ver saldo e información de cuenta",
|
permBalanceInfo = "Ver saldo e información de cuenta",
|
||||||
permSendPayments = "Enviar pagos",
|
permSendPayments = "Enviar pagos",
|
||||||
@@ -413,5 +420,56 @@ val EsHomeStrings = AppStrings(
|
|||||||
revokeDialogBody = "Esto revocará permanentemente esta clave Nostr Wallet Connect. Cualquier app que la use perderá el acceso.",
|
revokeDialogBody = "Esto revocará permanentemente esta clave Nostr Wallet Connect. Cualquier app que la use perderá el acceso.",
|
||||||
revokeDialogConfirm = "Revocar",
|
revokeDialogConfirm = "Revocar",
|
||||||
revokeDialogDismiss = "Cancelar",
|
revokeDialogDismiss = "Cancelar",
|
||||||
|
refreshDaily = "Diario",
|
||||||
|
refreshWeekly = "Semanal",
|
||||||
|
refreshMonthly = "Mensual",
|
||||||
|
refreshYearly = "Anual",
|
||||||
|
refreshNever = "Nunca",
|
||||||
|
|
||||||
|
newConnection = "Nueva conexión",
|
||||||
|
labelHint = "Etiqueta (p.ej. Amethyst, Bitrefill)",
|
||||||
|
expires = "Caduca",
|
||||||
|
pickDate = "Elegir fecha",
|
||||||
|
pickTime = "Elegir hora",
|
||||||
|
never = "Nunca",
|
||||||
|
budgets = "Presupuestos",
|
||||||
|
addBudget = "Añadir presupuesto",
|
||||||
|
removeBudget = "Eliminar presupuesto",
|
||||||
|
permissions = "Permisos",
|
||||||
|
unnamed = "Sin nombre",
|
||||||
|
creating = "Creando…",
|
||||||
|
createConnection = "Crear conexión",
|
||||||
|
ok = "OK",
|
||||||
|
sats = "Sats",
|
||||||
|
resets = "Se reinicia",
|
||||||
|
|
||||||
|
deleteConnection = "Eliminar conexión",
|
||||||
|
unnamedConnection = "Conexión sin nombre",
|
||||||
|
neverUsed = "Nunca usada",
|
||||||
|
expired = "Caducada",
|
||||||
|
|
||||||
|
connectionCreated = "Conexión creada",
|
||||||
|
scanOrCopyHint = "Escanea o copia esta cadena de conexión en tu app. No se volverá a mostrar.",
|
||||||
|
nwcPairingQrCode = "Código QR de emparejamiento NWC",
|
||||||
|
nwcConnectionString = "Cadena de conexión NWC",
|
||||||
|
shareNwcConnectionString = "Compartir cadena de conexión NWC",
|
||||||
|
openInApp = "Abrir en app",
|
||||||
|
|
||||||
|
walletConnect = "Wallet Connect",
|
||||||
|
addConnection = "Añadir conexión",
|
||||||
|
noCompatibleAppFound = "No se encontró una app compatible",
|
||||||
|
removeConnection = "¿Eliminar conexión?",
|
||||||
|
removeConnectionDetail = "Esto revocará permanentemente esta clave de Nostr Wallet Connect. Cualquier app que la use perderá el acceso.",
|
||||||
|
remove = "Eliminar",
|
||||||
|
|
||||||
|
noConnectionsYet = "Aún no hay conexiones",
|
||||||
|
tapToAddHint = "Toca + para conectar una app vía Nostr Wallet Connect.",
|
||||||
|
somethingWentWrong = "Algo salió mal",
|
||||||
|
retry = "Reintentar",
|
||||||
|
|
||||||
|
failedToLoadConnections = "Error al cargar las conexiones",
|
||||||
|
failedToCreateConnection = "Error al crear la conexión",
|
||||||
|
failedToDeleteConnection = "Error al eliminar la conexión",
|
||||||
|
failedToLoadConnection = "Error al cargar la conexión",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -376,6 +376,9 @@ val EuStrings = AppStrings(
|
|||||||
connectionSectionDetails = "Xehetasunak",
|
connectionSectionDetails = "Xehetasunak",
|
||||||
connectionRowCreated = "Konektatuta",
|
connectionRowCreated = "Konektatuta",
|
||||||
connectionRowLastUsed = "Azken erabilera",
|
connectionRowLastUsed = "Azken erabilera",
|
||||||
|
lastUsedToday = "Duela gutxi erabilia", // "used recently" (idiomatic for today)
|
||||||
|
lastUsedYesterday = "Atzo erabilia", // "used yesterday"
|
||||||
|
lastUsedDate = "Duela %s erabilia", // "used [date] ago"
|
||||||
connectionRowExpires = "Iraungitze-data",
|
connectionRowExpires = "Iraungitze-data",
|
||||||
connectionRowPubkey = "Gako publikoa",
|
connectionRowPubkey = "Gako publikoa",
|
||||||
connectionNeverUsed = "Inoiz ez",
|
connectionNeverUsed = "Inoiz ez",
|
||||||
@@ -400,6 +403,10 @@ val EuStrings = AppStrings(
|
|||||||
permSignMessage = "Mezuak sinatu",
|
permSignMessage = "Mezuak sinatu",
|
||||||
permPay = "Ordainketak bidali",
|
permPay = "Ordainketak bidali",
|
||||||
permInvoice = "Fakturak sortu",
|
permInvoice = "Fakturak sortu",
|
||||||
|
permLookup = "Kontsultatu faktura-egoera",
|
||||||
|
permHistory = "Irakurri transakzio-historiala",
|
||||||
|
permBalance = "Irakurri zorroaren saldoa",
|
||||||
|
permInfo = "Irakurri kontu-informazioa",
|
||||||
permLookupHistory = "Egoera eta transakzio-historia kontsultatu",
|
permLookupHistory = "Egoera eta transakzio-historia kontsultatu",
|
||||||
permBalanceInfo = "Saldoa eta kontuaren informazioa ikusi",
|
permBalanceInfo = "Saldoa eta kontuaren informazioa ikusi",
|
||||||
permSendPayments = "Ordainketak bidali",
|
permSendPayments = "Ordainketak bidali",
|
||||||
@@ -412,5 +419,56 @@ val EuStrings = AppStrings(
|
|||||||
revokeDialogBody = "Honek Nostr Wallet Connect gako hau behin betiko baliogabetuko du. Erabiltzen duen edozein aplikaziok sarbidea galduko du.",
|
revokeDialogBody = "Honek Nostr Wallet Connect gako hau behin betiko baliogabetuko du. Erabiltzen duen edozein aplikaziok sarbidea galduko du.",
|
||||||
revokeDialogConfirm = "Baliogabetu",
|
revokeDialogConfirm = "Baliogabetu",
|
||||||
revokeDialogDismiss = "Utzi",
|
revokeDialogDismiss = "Utzi",
|
||||||
|
refreshDaily = "Egunero",
|
||||||
|
refreshWeekly = "Astero",
|
||||||
|
refreshMonthly = "Hilabetero",
|
||||||
|
refreshYearly = "Urtero",
|
||||||
|
refreshNever = "Inoiz ez",
|
||||||
|
|
||||||
|
newConnection = "Konexio berria",
|
||||||
|
labelHint = "Etiketa (adb. Amethyst, Bitrefill)",
|
||||||
|
expires = "Iraungitzen da",
|
||||||
|
pickDate = "Aukeratu data",
|
||||||
|
pickTime = "Aukeratu ordua",
|
||||||
|
never = "Inoiz ez",
|
||||||
|
budgets = "Aurrekontuak",
|
||||||
|
addBudget = "Gehitu aurrekontua",
|
||||||
|
removeBudget = "Kendu aurrekontua",
|
||||||
|
permissions = "Baimenak",
|
||||||
|
unnamed = "Izengabea",
|
||||||
|
creating = "Sortzen…",
|
||||||
|
createConnection = "Sortu konexioa",
|
||||||
|
ok = "Ados",
|
||||||
|
sats = "Sats",
|
||||||
|
resets = "Berrezarri",
|
||||||
|
|
||||||
|
deleteConnection = "Ezabatu konexioa",
|
||||||
|
unnamedConnection = "Izengabeko konexioa",
|
||||||
|
neverUsed = "Inoiz erabili gabea",
|
||||||
|
expired = "Iraungitua",
|
||||||
|
|
||||||
|
connectionCreated = "Konexioa sortuta",
|
||||||
|
scanOrCopyHint = "Eskaneatu edo kopiatu konexio-kate hau zure app-ean. Ez da berriro erakutsiko.",
|
||||||
|
nwcPairingQrCode = "NWC parekatzeko QR kodea",
|
||||||
|
nwcConnectionString = "NWC konexio-katea",
|
||||||
|
shareNwcConnectionString = "Partekatu NWC konexio-katea",
|
||||||
|
openInApp = "Ireki app-ean",
|
||||||
|
|
||||||
|
walletConnect = "Wallet Connect",
|
||||||
|
addConnection = "Gehitu konexioa",
|
||||||
|
noCompatibleAppFound = "Ez da app bateragarririk aurkitu",
|
||||||
|
removeConnection = "Kendu konexioa?",
|
||||||
|
removeConnectionDetail = "Honek Nostr Wallet Connect gako hau behin betiko indargabetuko du. Erabiltzen duen edozein app-ek sarbidea galduko du.",
|
||||||
|
remove = "Kendu",
|
||||||
|
|
||||||
|
noConnectionsYet = "Oraindik konexiorik ez",
|
||||||
|
tapToAddHint = "Sakatu + app bat Nostr Wallet Connect bidez konektatzeko.",
|
||||||
|
somethingWentWrong = "Zerbait gaizki atera da",
|
||||||
|
retry = "Saiatu berriro",
|
||||||
|
|
||||||
|
failedToLoadConnections = "Huts egin du konexioak kargatzean",
|
||||||
|
failedToCreateConnection = "Huts egin du konexioa sortzean",
|
||||||
|
failedToDeleteConnection = "Huts egin du konexioa ezabatzean",
|
||||||
|
failedToLoadConnection = "Huts egin du konexioa kargatzean",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -383,6 +383,9 @@ data class NwcStrings(
|
|||||||
val connectionRowExpires : String,
|
val connectionRowExpires : String,
|
||||||
val connectionRowPubkey : String,
|
val connectionRowPubkey : String,
|
||||||
val connectionNeverUsed : String,
|
val connectionNeverUsed : String,
|
||||||
|
val lastUsedToday : String, // e.g. "Last used today"
|
||||||
|
val lastUsedYesterday : String, // e.g. "Last used yesterday"
|
||||||
|
val lastUsedDate : String, // e.g. "Last used %s" — %s is a formatted date
|
||||||
val connectionNoExpiry : String,
|
val connectionNoExpiry : String,
|
||||||
/** "Expired · {date}" */
|
/** "Expired · {date}" */
|
||||||
val connectionExpired : (date: String) -> String,
|
val connectionExpired : (date: String) -> String,
|
||||||
@@ -408,10 +411,14 @@ data class NwcStrings(
|
|||||||
val permSignMessage : String,
|
val permSignMessage : String,
|
||||||
val permPay : String, // "Send payments"
|
val permPay : String, // "Send payments"
|
||||||
val permInvoice : String, // "Create invoices"
|
val permInvoice : String, // "Create invoices"
|
||||||
val permLookupHistory: String, // "Lookup status & transaction history"
|
val permLookup : String,
|
||||||
|
val permHistory : String,
|
||||||
|
val permBalance : String,
|
||||||
|
val permInfo : String,
|
||||||
|
val permLookupHistory : String, // "Lookup status & transaction history"
|
||||||
val permBalanceInfo : String, // "Read balance & account info"
|
val permBalanceInfo : String, // "Read balance & account info"
|
||||||
val permSendPayments: String,
|
val permSendPayments : String,
|
||||||
val permCreateInvoices: String,
|
val permCreateInvoices : String,
|
||||||
val permLookupInvoiceStatus: String,
|
val permLookupInvoiceStatus: String,
|
||||||
val permReadTransactionHistory: String,
|
val permReadTransactionHistory: String,
|
||||||
val permReadWalletBalance: String,
|
val permReadWalletBalance: String,
|
||||||
@@ -420,4 +427,56 @@ data class NwcStrings(
|
|||||||
val revokeDialogBody : String,
|
val revokeDialogBody : String,
|
||||||
val revokeDialogConfirm : String,
|
val revokeDialogConfirm : String,
|
||||||
val revokeDialogDismiss : String,
|
val revokeDialogDismiss : String,
|
||||||
)
|
val refreshDaily : String,
|
||||||
|
val refreshWeekly : String,
|
||||||
|
val refreshMonthly : String,
|
||||||
|
val refreshYearly : String,
|
||||||
|
val refreshNever : String,
|
||||||
|
|
||||||
|
// New Connection
|
||||||
|
val newConnection : String,
|
||||||
|
val labelHint : String,
|
||||||
|
val expires : String,
|
||||||
|
val pickDate : String,
|
||||||
|
val pickTime : String,
|
||||||
|
val never : String,
|
||||||
|
val budgets : String,
|
||||||
|
val addBudget : String,
|
||||||
|
val removeBudget : String,
|
||||||
|
val permissions : String,
|
||||||
|
val unnamed : String,
|
||||||
|
val creating : String,
|
||||||
|
val createConnection : String,
|
||||||
|
val ok : String,
|
||||||
|
val sats : String,
|
||||||
|
val resets : String,
|
||||||
|
|
||||||
|
val deleteConnection : String,
|
||||||
|
val unnamedConnection : String,
|
||||||
|
val neverUsed : String,
|
||||||
|
val expired : String,
|
||||||
|
|
||||||
|
val connectionCreated : String,
|
||||||
|
val scanOrCopyHint : String,
|
||||||
|
val nwcPairingQrCode : String,
|
||||||
|
val nwcConnectionString : String,
|
||||||
|
val shareNwcConnectionString : String,
|
||||||
|
val openInApp : String,
|
||||||
|
|
||||||
|
val walletConnect : String,
|
||||||
|
val addConnection : String,
|
||||||
|
val noCompatibleAppFound : String,
|
||||||
|
val removeConnection : String,
|
||||||
|
val removeConnectionDetail: String,
|
||||||
|
val remove : String,
|
||||||
|
|
||||||
|
val noConnectionsYet : String,
|
||||||
|
val tapToAddHint : String,
|
||||||
|
val somethingWentWrong : String,
|
||||||
|
val retry : String,
|
||||||
|
|
||||||
|
val failedToLoadConnections : String,
|
||||||
|
val failedToCreateConnection : String,
|
||||||
|
val failedToDeleteConnection : String,
|
||||||
|
val failedToLoadConnection : String,
|
||||||
|
)
|
||||||
@@ -461,7 +461,7 @@ fun WalletScreen(
|
|||||||
|
|
||||||
val connectionState by nwcVm.connectionDetailState.collectAsStateWithLifecycle()
|
val connectionState by nwcVm.connectionDetailState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
LaunchedEffect(pubkey) { nwcVm.loadConnectionDetail(pubkey) }
|
LaunchedEffect(pubkey) { nwcVm.loadConnectionDetail(pubkey, strings) }
|
||||||
DisposableEffect(Unit) { onDispose { nwcVm.clearConnectionDetail() } }
|
DisposableEffect(Unit) { onDispose { nwcVm.clearConnectionDetail() } }
|
||||||
|
|
||||||
when (val state = connectionState) {
|
when (val state = connectionState) {
|
||||||
@@ -491,7 +491,7 @@ fun WalletScreen(
|
|||||||
budgets = state.budgets,
|
budgets = state.budgets,
|
||||||
onBack = { navController.popBackStack() },
|
onBack = { navController.popBackStack() },
|
||||||
onRevoke = {
|
onRevoke = {
|
||||||
nwcVm.deleteConnection(pubkey)
|
nwcVm.deleteConnection(pubkey, strings)
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.bitcointxoko.gudariwallet.api.NwcNewBudget
|
import com.bitcointxoko.gudariwallet.api.NwcNewBudget
|
||||||
|
import com.bitcointxoko.gudariwallet.i18n.AppStrings
|
||||||
|
import com.bitcointxoko.gudariwallet.strings
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
@@ -41,6 +43,16 @@ private class AddConnectionState {
|
|||||||
expiry.atZone(ZoneId.systemDefault()).toEpochSecond()
|
expiry.atZone(ZoneId.systemDefault()).toEpochSecond()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun NwcPermission.displayLabel(strings: AppStrings): String = when (serverKey) {
|
||||||
|
"pay" -> strings.nwc.permPay
|
||||||
|
"invoice" -> strings.nwc.permInvoice
|
||||||
|
"lookup" -> strings.nwc.permLookup
|
||||||
|
"history" -> strings.nwc.permHistory
|
||||||
|
"balance" -> strings.nwc.permBalance
|
||||||
|
"info" -> strings.nwc.permInfo
|
||||||
|
else -> serverKey
|
||||||
|
}
|
||||||
|
|
||||||
// ── Sheet ─────────────────────────────────────────────────────────────────────
|
// ── Sheet ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -58,6 +70,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
val state = remember { AddConnectionState() }
|
val state = remember { AddConnectionState() }
|
||||||
|
|
||||||
|
|
||||||
if (state.showDatePicker) {
|
if (state.showDatePicker) {
|
||||||
val initialMillis = state.expiry
|
val initialMillis = state.expiry
|
||||||
.atZone(ZoneId.systemDefault())
|
.atZone(ZoneId.systemDefault())
|
||||||
@@ -81,10 +94,10 @@ internal fun NwcAddConnectionSheet(
|
|||||||
.withDayOfMonth(picked.dayOfMonth)
|
.withDayOfMonth(picked.dayOfMonth)
|
||||||
}
|
}
|
||||||
state.showDatePicker = false
|
state.showDatePicker = false
|
||||||
}) { Text("OK") }
|
}) { Text(strings.nwc.ok) }
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { state.showDatePicker = false }) { Text("Cancel") }
|
TextButton(onClick = { state.showDatePicker = false }) { Text(strings.nwc.cancel) }
|
||||||
}
|
}
|
||||||
) { DatePicker(state = datePickerState) }
|
) { DatePicker(state = datePickerState) }
|
||||||
}
|
}
|
||||||
@@ -105,10 +118,10 @@ internal fun NwcAddConnectionSheet(
|
|||||||
.withSecond(0)
|
.withSecond(0)
|
||||||
.withNano(0)
|
.withNano(0)
|
||||||
state.showTimePicker = false
|
state.showTimePicker = false
|
||||||
}) { Text("OK") }
|
}) { Text(strings.nwc.ok) }
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { state.showTimePicker = false }) { Text("Cancel") }
|
TextButton(onClick = { state.showTimePicker = false }) { Text(strings.nwc.cancel) }
|
||||||
},
|
},
|
||||||
text = { TimePicker(state = timePickerState) }
|
text = { TimePicker(state = timePickerState) }
|
||||||
)
|
)
|
||||||
@@ -127,7 +140,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "New connection",
|
text = strings.nwc.newConnection,
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -135,7 +148,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.description,
|
value = state.description,
|
||||||
onValueChange = { state.description = it },
|
onValueChange = { state.description = it },
|
||||||
label = { Text("Label (e.g. Amethyst, Bitrefill)") },
|
label = { Text(strings.nwc.labelHint) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
@@ -145,7 +158,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(text = "Expires", style = MaterialTheme.typography.bodyMedium)
|
Text(text = strings.nwc.expires, style = MaterialTheme.typography.bodyMedium)
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
if (!state.neverExpires) {
|
if (!state.neverExpires) {
|
||||||
@@ -158,7 +171,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.CalendarToday,
|
imageVector = Icons.Default.CalendarToday,
|
||||||
contentDescription = "Pick date",
|
contentDescription = strings.nwc.pickDate,
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -173,7 +186,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Schedule,
|
imageVector = Icons.Default.Schedule,
|
||||||
contentDescription = "Pick time",
|
contentDescription = strings.nwc.pickTime,
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -185,7 +198,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
checked = state.neverExpires,
|
checked = state.neverExpires,
|
||||||
onCheckedChange = { state.neverExpires = it }
|
onCheckedChange = { state.neverExpires = it }
|
||||||
)
|
)
|
||||||
Text(text = "Never", style = MaterialTheme.typography.bodyMedium)
|
Text(text = strings.nwc.never, style = MaterialTheme.typography.bodyMedium)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -193,7 +206,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Budgets",
|
text = strings.nwc.budgets,
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -206,11 +219,11 @@ internal fun NwcAddConnectionSheet(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Add,
|
imageVector = Icons.Default.Add,
|
||||||
contentDescription = "Add budget",
|
contentDescription = strings.nwc.addBudget,
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
Text("Add budget")
|
Text(strings.nwc.addBudget)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +243,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
|
|
||||||
// ── Permissions ──────────────────────────────────────────────────
|
// ── Permissions ──────────────────────────────────────────────────
|
||||||
Text(
|
Text(
|
||||||
text = "Permissions",
|
text = strings.nwc.permissions,
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -251,11 +264,11 @@ internal fun NwcAddConnectionSheet(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text(text = perm.displayLabel, style = MaterialTheme.typography.bodyMedium)
|
Text(text = perm.displayLabel(strings), style = MaterialTheme.typography.bodyMedium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val unnamed = strings.nwc.unnamed
|
||||||
// ── Create button ─────────────────────────────────────────────────
|
// ── Create button ─────────────────────────────────────────────────
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -263,7 +276,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
else state.expiryEpochSeconds() // #10
|
else state.expiryEpochSeconds() // #10
|
||||||
val validBudgets = state.budgets.mapNotNull { it.toNwcNewBudget() }
|
val validBudgets = state.budgets.mapNotNull { it.toNwcNewBudget() }
|
||||||
onCreate(
|
onCreate(
|
||||||
state.description.trim().ifBlank { "Unnamed" },
|
state.description.trim().ifBlank { unnamed },
|
||||||
state.selectedPermissions.toList(),
|
state.selectedPermissions.toList(),
|
||||||
expiresAtSecs,
|
expiresAtSecs,
|
||||||
validBudgets
|
validBudgets
|
||||||
@@ -280,7 +293,7 @@ internal fun NwcAddConnectionSheet(
|
|||||||
)
|
)
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
Text(if (isCreating) "Creating…" else "Create connection")
|
Text(if (isCreating) strings.nwc.creating else strings.nwc.createConnection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,6 +301,15 @@ internal fun NwcAddConnectionSheet(
|
|||||||
|
|
||||||
// ── Budget row ────────────────────────────────────────────────────────────────
|
// ── Budget row ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BudgetRefreshWindow.displayLabel(strings: AppStrings): String = when (this) {
|
||||||
|
BudgetRefreshWindow.DAILY -> strings.nwc.refreshDaily
|
||||||
|
BudgetRefreshWindow.WEEKLY -> strings.nwc.refreshWeekly
|
||||||
|
BudgetRefreshWindow.MONTHLY -> strings.nwc.refreshMonthly
|
||||||
|
BudgetRefreshWindow.YEARLY -> strings.nwc.refreshYearly
|
||||||
|
BudgetRefreshWindow.NEVER -> strings.nwc.refreshNever
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun BudgetRow(
|
private fun BudgetRow(
|
||||||
@@ -313,7 +335,7 @@ private fun BudgetRow(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = draft.amountSats,
|
value = draft.amountSats,
|
||||||
onValueChange = { onChange(draft.copy(amountSats = it.filter(Char::isDigit))) },
|
onValueChange = { onChange(draft.copy(amountSats = it.filter(Char::isDigit))) },
|
||||||
label = { Text("Sats") },
|
label = { Text(strings.nwc.sats) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
modifier = Modifier.weight(1f).height(fieldHeight)
|
modifier = Modifier.weight(1f).height(fieldHeight)
|
||||||
@@ -325,10 +347,10 @@ private fun BudgetRow(
|
|||||||
modifier = Modifier.weight(1f).height(fieldHeight)
|
modifier = Modifier.weight(1f).height(fieldHeight)
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = draft.refreshWindow.label,
|
value = draft.refreshWindow.displayLabel(strings),
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
label = { Text("Resets") },
|
label = { Text(strings.nwc.resets) },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
ExposedDropdownMenuDefaults.TrailingIcon(expanded = dropdownExpanded)
|
ExposedDropdownMenuDefaults.TrailingIcon(expanded = dropdownExpanded)
|
||||||
},
|
},
|
||||||
@@ -343,7 +365,7 @@ private fun BudgetRow(
|
|||||||
) {
|
) {
|
||||||
BudgetRefreshWindow.entries.forEach { window ->
|
BudgetRefreshWindow.entries.forEach { window ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(window.label) },
|
text = { Text(window.displayLabel(strings)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onChange(draft.copy(refreshWindow = window))
|
onChange(draft.copy(refreshWindow = window))
|
||||||
dropdownExpanded = false
|
dropdownExpanded = false
|
||||||
@@ -359,7 +381,7 @@ private fun BudgetRow(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Default.Close,
|
||||||
contentDescription = "Remove budget",
|
contentDescription = strings.nwc.removeBudget,
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.bitcointxoko.gudariwallet.ui.nwc
|
package com.bitcointxoko.gudariwallet.ui.nwc
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@@ -17,16 +19,19 @@ import com.bitcointxoko.gudariwallet.api.NwcGetResponse
|
|||||||
import com.bitcointxoko.gudariwallet.strings
|
import com.bitcointxoko.gudariwallet.strings
|
||||||
import com.bitcointxoko.gudariwallet.ui.theme.semanticColors
|
import com.bitcointxoko.gudariwallet.ui.theme.semanticColors
|
||||||
import com.bitcointxoko.gudariwallet.util.formatEpochShort
|
import com.bitcointxoko.gudariwallet.util.formatEpochShort
|
||||||
|
import com.bitcointxoko.gudariwallet.util.isToday
|
||||||
|
import com.bitcointxoko.gudariwallet.util.isYesterday
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun NwcConnectionRow(
|
internal fun NwcConnectionRow(
|
||||||
connection : NwcGetResponse,
|
connection: NwcGetResponse,
|
||||||
isDeleting : Boolean,
|
isDeleting: Boolean,
|
||||||
onDeleteConfirm : () -> Unit,
|
onDeleteConfirm: () -> Unit,
|
||||||
onConnectionClick: () -> Unit,
|
onConnectionClick: () -> Unit,
|
||||||
modifier : Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val key = connection.data
|
val key = connection.data
|
||||||
val expired = key.expires_at > 0 && key.expires_at < Instant.now().epochSecond
|
val expired = key.expires_at > 0 && key.expires_at < Instant.now().epochSecond
|
||||||
@@ -85,7 +90,7 @@ internal fun NwcConnectionRow(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Delete,
|
imageVector = Icons.Default.Delete,
|
||||||
contentDescription = "Delete connection",
|
contentDescription = strings.nwc.deleteConnection,
|
||||||
tint = MaterialTheme.colorScheme.onError.copy(alpha = bgAlpha)
|
tint = MaterialTheme.colorScheme.onError.copy(alpha = bgAlpha)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -111,7 +116,7 @@ internal fun NwcConnectionRow(
|
|||||||
Spacer(Modifier.width(12.dp))
|
Spacer(Modifier.width(12.dp))
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = key.description.ifBlank { "Unnamed connection" },
|
text = key.description.ifBlank { strings.nwc.unnamedConnection },
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
@@ -121,9 +126,15 @@ internal fun NwcConnectionRow(
|
|||||||
MaterialTheme.colorScheme.onSurface
|
MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(2.dp))
|
Spacer(Modifier.height(2.dp))
|
||||||
|
val lastUsedText = when {
|
||||||
|
key.last_used <= 0L -> strings.nwc.neverUsed
|
||||||
|
isToday(key.last_used) -> strings.nwc.lastUsedToday
|
||||||
|
isYesterday(key.last_used) -> strings.nwc.lastUsedYesterday
|
||||||
|
else -> strings.nwc.lastUsedDate.format(formatEpochShort(key.last_used, strings.today, strings.yesterday))
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = if (key.last_used <= 0L) "Never used"
|
text = if (key.last_used <= 0L) strings.nwc.neverUsed
|
||||||
else "Last used ${formatEpochShort(key.last_used, strings.today, strings.yesterday)}",
|
else lastUsedText,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -135,7 +146,7 @@ internal fun NwcConnectionRow(
|
|||||||
contentColor = semantic.onErrorContainer
|
contentColor = semantic.onErrorContainer
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Expired",
|
text = strings.nwc.expired,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,17 +5,16 @@ import com.bitcointxoko.gudariwallet.api.NwcNewBudget
|
|||||||
// ── Available permissions ────────────────────────────────────────────
|
// ── Available permissions ────────────────────────────────────────────
|
||||||
internal data class NwcPermission(
|
internal data class NwcPermission(
|
||||||
val serverKey : String, // what we send in the permissions array
|
val serverKey : String, // what we send in the permissions array
|
||||||
val displayLabel : String, // shown in the UI
|
|
||||||
val default : Boolean // pre-checked by default
|
val default : Boolean // pre-checked by default
|
||||||
)
|
)
|
||||||
|
|
||||||
internal val ALL_PERMISSIONS = listOf(
|
internal val ALL_PERMISSIONS = listOf(
|
||||||
NwcPermission("pay", "Send payments", default = true),
|
NwcPermission("pay", default = true),
|
||||||
NwcPermission("invoice", "Create invoices", default = false),
|
NwcPermission("invoice", default = false),
|
||||||
NwcPermission("lookup", "Lookup invoice status", default = false),
|
NwcPermission("lookup", default = false),
|
||||||
NwcPermission("history", "Read transaction history", default = false),
|
NwcPermission("history", default = false),
|
||||||
NwcPermission("balance", "Read wallet balance", default = false),
|
NwcPermission("balance", default = false),
|
||||||
NwcPermission("info", "Read account info", default = false)
|
NwcPermission("info", default = false)
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── Budget ────────────────────────────────────────────────────────────
|
// ── Budget ────────────────────────────────────────────────────────────
|
||||||
@@ -24,12 +23,12 @@ internal val ALL_PERMISSIONS = listOf(
|
|||||||
* Transient UI state for a single budget row in the "Add connection" sheet.
|
* Transient UI state for a single budget row in the "Add connection" sheet.
|
||||||
* Converted to [NwcNewBudget] on submit.
|
* Converted to [NwcNewBudget] on submit.
|
||||||
*/
|
*/
|
||||||
enum class BudgetRefreshWindow(val label: String, val seconds: Int) {
|
enum class BudgetRefreshWindow(val seconds: Int) {
|
||||||
DAILY ("Daily", 86_400),
|
DAILY (86_400),
|
||||||
WEEKLY ("Weekly", 604_800),
|
WEEKLY (604_800),
|
||||||
MONTHLY ("Monthly", 2_592_000),
|
MONTHLY (2_592_000),
|
||||||
YEARLY ("Yearly", 31_536_000),
|
YEARLY (31_536_000),
|
||||||
NEVER ("Never", 0);
|
NEVER (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
data class BudgetDraft(
|
data class BudgetDraft(
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import com.bitcointxoko.gudariwallet.strings
|
||||||
import com.bitcointxoko.gudariwallet.ui.common.rememberBrightnessController
|
import com.bitcointxoko.gudariwallet.ui.common.rememberBrightnessController
|
||||||
import com.bitcointxoko.gudariwallet.ui.common.QrDisplayCard
|
import com.bitcointxoko.gudariwallet.ui.common.QrDisplayCard
|
||||||
|
|
||||||
@@ -29,20 +30,21 @@ internal fun NwcPairingUrlDialog(
|
|||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text("Connection created") },
|
title = { Text(strings.nwc.connectionCreated) },
|
||||||
text = {
|
text = {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Scan or copy this connection string into your app. It will not be shown again.",
|
text = strings.nwc.scanOrCopyHint,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
val strings = strings
|
||||||
QrDisplayCard(
|
QrDisplayCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
content = url,
|
content = url,
|
||||||
contentDescription = "NWC pairing QR code",
|
contentDescription = strings.nwc.nwcPairingQrCode,
|
||||||
clipLabel = "NWC connection string",
|
clipLabel = strings.nwc.nwcConnectionString,
|
||||||
shareTitle = "Share NWC connection string",
|
shareTitle = strings.nwc.shareNwcConnectionString,
|
||||||
textToCopy = url,
|
textToCopy = url,
|
||||||
brightnessOn = brightness.isOn,
|
brightnessOn = brightness.isOn,
|
||||||
onToggleBrightness = brightness.toggle,
|
onToggleBrightness = brightness.toggle,
|
||||||
@@ -52,7 +54,7 @@ internal fun NwcPairingUrlDialog(
|
|||||||
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||||
try {
|
try {
|
||||||
context.startActivity(
|
context.startActivity(
|
||||||
Intent.createChooser(intent, "Open in app")
|
Intent.createChooser(intent, strings.nwc.openInApp)
|
||||||
)
|
)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
onNoAppFound() // #17: no Toast — caller handles it
|
onNoAppFound() // #17: no Toast — caller handles it
|
||||||
@@ -63,7 +65,7 @@ internal fun NwcPairingUrlDialog(
|
|||||||
},
|
},
|
||||||
confirmButton = {}, // actions live inside QrDisplayCard
|
confirmButton = {}, // actions live inside QrDisplayCard
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismiss) { Text("Done") }
|
TextButton(onClick = onDismiss) { Text(strings.nwc.done) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.bitcointxoko.gudariwallet.ui.nwc
|
package com.bitcointxoko.gudariwallet.ui.nwc
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
@@ -13,8 +15,10 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NwcScreen(
|
fun NwcScreen(
|
||||||
@@ -30,8 +34,13 @@ fun NwcScreen(
|
|||||||
val pendingDeletePubkey by viewModel.pendingDeletePubkey.collectAsStateWithLifecycle()
|
val pendingDeletePubkey by viewModel.pendingDeletePubkey.collectAsStateWithLifecycle()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val strings = LocalAppStrings.current
|
||||||
|
|
||||||
val refresh = { viewModel.loadConnections(includeExpired = true) }
|
val refresh = { viewModel.loadConnections(includeExpired = true, strings) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.loadConnections(includeExpired = true, strings)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(pairingUrl) {
|
LaunchedEffect(pairingUrl) {
|
||||||
if (pairingUrl != null) {
|
if (pairingUrl != null) {
|
||||||
@@ -42,17 +51,17 @@ fun NwcScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Wallet Connect") },
|
title = { Text(strings.nwc.walletConnect) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = strings.nwc.back)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(onClick = { viewModel.openAddSheet() }) {
|
FloatingActionButton(onClick = { viewModel.openAddSheet() }) {
|
||||||
Icon(Icons.Default.Add, contentDescription = "Add connection")
|
Icon(Icons.Default.Add, contentDescription = strings.nwc.addConnection)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) }
|
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||||
@@ -102,7 +111,7 @@ fun NwcScreen(
|
|||||||
connection,
|
connection,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
onDeleteConfirm = { viewModel.requestDelete(connection.data.pubkey) },
|
onDeleteConfirm = { viewModel.requestDelete(connection.data.pubkey) },
|
||||||
onConnectionClick = { onConnectionClick(connection.data.pubkey) }
|
{ onConnectionClick(connection.data.pubkey) }
|
||||||
)
|
)
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
@@ -125,7 +134,8 @@ fun NwcScreen(
|
|||||||
description = description,
|
description = description,
|
||||||
permissions = permissions,
|
permissions = permissions,
|
||||||
expiresAt = expiresAt,
|
expiresAt = expiresAt,
|
||||||
budgets = budgets
|
budgets = budgets,
|
||||||
|
strings = strings
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onDismiss = { viewModel.closeAddSheet() }
|
onDismiss = { viewModel.closeAddSheet() }
|
||||||
@@ -134,12 +144,13 @@ fun NwcScreen(
|
|||||||
|
|
||||||
// ── Pairing URL dialog ─────────────────────────────────────────────────────
|
// ── Pairing URL dialog ─────────────────────────────────────────────────────
|
||||||
pairingUrl?.let { url ->
|
pairingUrl?.let { url ->
|
||||||
|
val strings = strings
|
||||||
NwcPairingUrlDialog(
|
NwcPairingUrlDialog(
|
||||||
url = url,
|
url = url,
|
||||||
onDismiss = { viewModel.clearPairingUrl() },
|
onDismiss = { viewModel.clearPairingUrl() },
|
||||||
onNoAppFound = {
|
onNoAppFound = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState.showSnackbar("No compatible app found")
|
snackbarHostState.showSnackbar(strings.nwc.noCompatibleAppFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -149,15 +160,15 @@ fun NwcScreen(
|
|||||||
pendingDeletePubkey?.let {
|
pendingDeletePubkey?.let {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { viewModel.cancelDelete() },
|
onDismissRequest = { viewModel.cancelDelete() },
|
||||||
title = { Text("Remove connection?") },
|
title = { Text(strings.nwc.removeConnection) },
|
||||||
text = { Text("This will permanently revoke this Nostr Wallet Connect key. Any app using it will lose access.") },
|
text = { Text(strings.nwc.removeConnectionDetail) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = { viewModel.confirmDelete() }
|
onClick = { viewModel.confirmDelete(strings) }
|
||||||
) { Text("Remove", color = MaterialTheme.colorScheme.error) }
|
) { Text(strings.nwc.remove, color = MaterialTheme.colorScheme.error) }
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { viewModel.cancelDelete() }) { Text("Cancel") }
|
TextButton(onClick = { viewModel.cancelDelete() }) { Text(strings.nwc.cancel) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.bitcointxoko.gudariwallet.strings
|
||||||
|
|
||||||
// ── Empty state ──────────────────────────────────────────────────────
|
// ── Empty state ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -21,10 +22,10 @@ internal fun NwcEmptyContent(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Text("No connections yet", style = MaterialTheme.typography.titleMedium)
|
Text(strings.nwc.noConnectionsYet, style = MaterialTheme.typography.titleMedium)
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
"Tap + to connect an app via Nostr Wallet Connect.",
|
strings.nwc.tapToAddHint,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -32,7 +33,7 @@ internal fun NwcEmptyContent(
|
|||||||
Button(onClick = onAddNew) {
|
Button(onClick = onAddNew) {
|
||||||
Icon(Icons.Default.Add, contentDescription = null)
|
Icon(Icons.Default.Add, contentDescription = null)
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text("Add connection")
|
Text(strings.nwc.addConnection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ internal fun NwcErrorContent(
|
|||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"Something went wrong",
|
strings.nwc.somethingWentWrong,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.error
|
color = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
@@ -62,6 +63,6 @@ internal fun NwcErrorContent(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(24.dp))
|
Spacer(Modifier.height(24.dp))
|
||||||
OutlinedButton(onClick = onRetry) { Text("Retry") }
|
OutlinedButton(onClick = onRetry) { Text(strings.nwc.retry) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ package com.bitcointxoko.gudariwallet.ui.nwc
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
||||||
import com.bitcointxoko.gudariwallet.api.NwcGetResponse
|
import com.bitcointxoko.gudariwallet.api.NwcGetResponse
|
||||||
import com.bitcointxoko.gudariwallet.api.NwcNewBudget
|
import com.bitcointxoko.gudariwallet.api.NwcNewBudget
|
||||||
import com.bitcointxoko.gudariwallet.api.NwcRegistrationRequest
|
import com.bitcointxoko.gudariwallet.api.NwcRegistrationRequest
|
||||||
import com.bitcointxoko.gudariwallet.data.NwcCacheRepository
|
import com.bitcointxoko.gudariwallet.data.NwcCacheRepository
|
||||||
import com.bitcointxoko.gudariwallet.data.WalletRepository
|
import com.bitcointxoko.gudariwallet.data.WalletRepository
|
||||||
import com.bitcointxoko.gudariwallet.data.db.toDomain
|
|
||||||
import com.bitcointxoko.gudariwallet.data.toDomain
|
import com.bitcointxoko.gudariwallet.data.toDomain
|
||||||
import com.bitcointxoko.gudariwallet.data.toGetResponse
|
import com.bitcointxoko.gudariwallet.data.toGetResponse
|
||||||
|
import com.bitcointxoko.gudariwallet.i18n.AppStrings
|
||||||
import com.bitcointxoko.gudariwallet.util.NwcKeyGenerator
|
import com.bitcointxoko.gudariwallet.util.NwcKeyGenerator
|
||||||
import com.bitcointxoko.gudariwallet.util.NwcKeypair
|
import com.bitcointxoko.gudariwallet.util.NwcKeypair
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -95,18 +97,14 @@ class NwcViewModel(
|
|||||||
fun requestDelete(pubkey: String) { _pendingDeletePubkey.value = pubkey }
|
fun requestDelete(pubkey: String) { _pendingDeletePubkey.value = pubkey }
|
||||||
fun cancelDelete() { _pendingDeletePubkey.value = null }
|
fun cancelDelete() { _pendingDeletePubkey.value = null }
|
||||||
// confirmDelete() calls the existing deleteConnection() then clears the pending key
|
// confirmDelete() calls the existing deleteConnection() then clears the pending key
|
||||||
fun confirmDelete() {
|
fun confirmDelete(strings: AppStrings) {
|
||||||
_pendingDeletePubkey.value?.let { deleteConnection(it) }
|
_pendingDeletePubkey.value?.let { deleteConnection(it, strings) }
|
||||||
_pendingDeletePubkey.value = null
|
_pendingDeletePubkey.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
loadConnections(includeExpired = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Load ──────────────────────────────────────────────────────────────────
|
// ── Load ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fun loadConnections(includeExpired: Boolean = false) {
|
fun loadConnections(includeExpired: Boolean = false, strings: AppStrings) {
|
||||||
Timber.d("[$TAG] loadConnections(includeExpired=$includeExpired)")
|
Timber.d("[$TAG] loadConnections(includeExpired=$includeExpired)")
|
||||||
isSyncing = true
|
isSyncing = true
|
||||||
|
|
||||||
@@ -154,7 +152,7 @@ class NwcViewModel(
|
|||||||
isSyncing = false
|
isSyncing = false
|
||||||
if (_uiState.value !is NwcUiState.Success) {
|
if (_uiState.value !is NwcUiState.Success) {
|
||||||
_uiState.value = NwcUiState.Error(
|
_uiState.value = NwcUiState.Error(
|
||||||
e.message ?: "Failed to load connections"
|
e.message ?: strings.nwc.failedToLoadConnections
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Timber.w("[$TAG] network failed but cache is showing — non-fatal")
|
Timber.w("[$TAG] network failed but cache is showing — non-fatal")
|
||||||
@@ -172,7 +170,8 @@ class NwcViewModel(
|
|||||||
description : String,
|
description : String,
|
||||||
permissions : List<String>,
|
permissions : List<String>,
|
||||||
expiresAt : Long = 0L,
|
expiresAt : Long = 0L,
|
||||||
budgets : List<NwcNewBudget> = emptyList()
|
budgets : List<NwcNewBudget> = emptyList(),
|
||||||
|
strings : AppStrings
|
||||||
) {
|
) {
|
||||||
Timber.d("[$TAG] createConnection description='$description' permissions=$permissions")
|
Timber.d("[$TAG] createConnection description='$description' permissions=$permissions")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@@ -213,7 +212,7 @@ class NwcViewModel(
|
|||||||
}
|
}
|
||||||
.onFailure { e ->
|
.onFailure { e ->
|
||||||
Timber.e(e, "[$TAG] createConnection failed")
|
Timber.e(e, "[$TAG] createConnection failed")
|
||||||
_uiState.value = NwcUiState.Error(e.message ?: "Failed to create connection")
|
_uiState.value = NwcUiState.Error(e.message ?: strings.nwc.failedToCreateConnection)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
_isCreating.value = false
|
_isCreating.value = false
|
||||||
@@ -234,7 +233,7 @@ class NwcViewModel(
|
|||||||
* and the detail screen (revoke button). Sets [_isDeleting] for the duration
|
* and the detail screen (revoke button). Sets [_isDeleting] for the duration
|
||||||
* so the list screen can disable row interactions while in flight.
|
* so the list screen can disable row interactions while in flight.
|
||||||
*/
|
*/
|
||||||
fun deleteConnection(pubkey: String) {
|
fun deleteConnection(pubkey: String, strings: AppStrings) {
|
||||||
Timber.d("[$TAG] deleteConnection pubkey=${pubkey.take(16)}…")
|
Timber.d("[$TAG] deleteConnection pubkey=${pubkey.take(16)}…")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_isDeleting.value = true
|
_isDeleting.value = true
|
||||||
@@ -254,7 +253,7 @@ class NwcViewModel(
|
|||||||
.onFailure { e ->
|
.onFailure { e ->
|
||||||
Timber.e(e, "[$TAG] failed to delete connection pubkey=${pubkey.take(16)}…")
|
Timber.e(e, "[$TAG] failed to delete connection pubkey=${pubkey.take(16)}…")
|
||||||
_uiState.value = NwcUiState.Error(
|
_uiState.value = NwcUiState.Error(
|
||||||
e.message ?: "Failed to delete connection"
|
e.message ?: strings.nwc.failedToDeleteConnection
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -264,7 +263,7 @@ class NwcViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun loadConnectionDetail(pubkey: String) {
|
fun loadConnectionDetail(pubkey: String, strings: AppStrings) {
|
||||||
Timber.d("[$TAG] loadConnectionDetail pubkey=${pubkey.take(16)}…")
|
Timber.d("[$TAG] loadConnectionDetail pubkey=${pubkey.take(16)}…")
|
||||||
|
|
||||||
// Cancel any subscription from a previous key — prevents stale Flow
|
// Cancel any subscription from a previous key — prevents stale Flow
|
||||||
@@ -303,7 +302,7 @@ class NwcViewModel(
|
|||||||
Timber.e(e, "[$TAG] failed to load connection detail")
|
Timber.e(e, "[$TAG] failed to load connection detail")
|
||||||
if (_connectionDetailState.value !is ConnectionDetailState.Success) {
|
if (_connectionDetailState.value !is ConnectionDetailState.Success) {
|
||||||
_connectionDetailState.value = ConnectionDetailState.Error(
|
_connectionDetailState.value = ConnectionDetailState.Error(
|
||||||
e.message ?: "Failed to load connection"
|
e.message ?: strings.nwc.failedToLoadConnection
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Timber.w("[$TAG] detail network refresh failed — cache showing (non-fatal)")
|
Timber.w("[$TAG] detail network refresh failed — cache showing (non-fatal)")
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.bitcointxoko.gudariwallet.util
|
package com.bitcointxoko.gudariwallet.util
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
@@ -98,3 +100,15 @@ fun formatEpoch(epochSeconds: Long, todayLabel: String, yesterdayLabel: String):
|
|||||||
"—"
|
"—"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||||
|
private fun epochToLocalDate(epochSeconds: Long): LocalDate =
|
||||||
|
LocalDate.ofInstant(Instant.ofEpochSecond(epochSeconds), ZoneId.systemDefault())
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||||
|
internal fun isToday(epochSeconds: Long): Boolean =
|
||||||
|
epochToLocalDate(epochSeconds) == LocalDate.now(ZoneId.systemDefault())
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||||
|
internal fun isYesterday(epochSeconds: Long): Boolean =
|
||||||
|
epochToLocalDate(epochSeconds) == LocalDate.now(ZoneId.systemDefault()).minusDays(1)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user