diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index d12b73c..6320262 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -2,23 +2,40 @@ Aead + Bech Bitrefill + Español Nbits Nostr Tink amboss + bech + bitcointxoko + btcpay cltv + getnwckey + gudari + gudariwallet hkdf inflight + keyauth + lnaddr + lnaddress lnbc lnbits lnurlp + lnurlw msat msats nostr + nwccacherepository + satoshi satoshis sats snackbar + snull + tink + walletconnect \ No newline at end of file diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/MainActivity.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/MainActivity.kt index 7b2787d..b2c673d 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/MainActivity.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/MainActivity.kt @@ -2,24 +2,18 @@ package com.bitcointxoko.gudariwallet import android.Manifest import android.app.PendingIntent -import android.content.ActivityNotFoundException import android.content.Intent import android.content.IntentFilter import android.nfc.NfcAdapter import android.nfc.Tag import android.os.Build import android.os.Bundle -import android.os.PowerManager -import android.provider.Settings import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.* -import androidx.core.content.edit -import androidx.core.net.toUri import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/api/Adapters.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/api/Adapters.kt index 236dc6f..506e508 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/api/Adapters.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/api/Adapters.kt @@ -18,7 +18,7 @@ import kotlinx.serialization.json.jsonPrimitive * - a JSON array ["Thanks!"] * - null * - * Mirrors the behaviour of the old Gson FlexibleStringAdapter. + * Mirrors the behavior of the old Gson FlexibleStringAdapter. */ object FlexibleStringSerializer : KSerializer { diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/api/JsonProvider.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/api/JsonProvider.kt index a6d6a7a..32ffb4a 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/api/JsonProvider.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/api/JsonProvider.kt @@ -3,7 +3,7 @@ package com.bitcointxoko.gudariwallet.api import kotlinx.serialization.json.Json /** - * Single source of truth for the app's Json instance. + * Single source of truth for the app's JSON instance. * * All serialization/deserialization — Retrofit, DataStore caches, * and WebSocket message parsing — must use this instance so that diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/api/LNbitsClient.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/api/LNbitsClient.kt index 417cbe3..5121b5b 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/api/LNbitsClient.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/api/LNbitsClient.kt @@ -1,7 +1,6 @@ package com.bitcointxoko.gudariwallet.api import com.bitcointxoko.gudariwallet.security.SecretStore -import com.bitcointxoko.gudariwallet.util.WalletConstants import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -13,7 +12,6 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.kotlinx.serialization.asConverterFactory -import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference class LNbitsClient(secrets: SecretStore) { @@ -38,8 +36,6 @@ class LNbitsClient(secrets: SecretStore) { } } - // Single shared client — used by both Retrofit and WebSocket - val httpClient: OkHttpClient get() = LNbitsHttpClient.instance val api: LNbitsApi by lazy { buildApi() } private fun buildApi(): LNbitsApi { diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/api/Models.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/api/Models.kt index 3a84597..9855a82 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/api/Models.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/api/Models.kt @@ -251,7 +251,7 @@ data class SuccessAction( * - a plain string "Thanks!" * - null * - * kotlinx.serialization's default deserialiser throws when it sees a + * kotlinx.serialization's default deserializer throws when it sees a * string where it expects {. This serializer handles all three cases. * * Applied via @Serializable(with = SuccessActionSerializer::class) on diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/LNbitsWalletRepository.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/LNbitsWalletRepository.kt index 4c0ea53..2b76767 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/LNbitsWalletRepository.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/LNbitsWalletRepository.kt @@ -337,10 +337,8 @@ class LNbitsWalletRepository( request : NwcRegistrationRequest ): NwcGetResponse { Timber.d( - "NWC [REGISTER ] pubkey=${pubkey.take(16)}… " + - "permissions=${request.permissions} " + - "expires_at=${request.expires_at} " + - "budgets=${request.budgets.size}" + "%snull", "NWC [REGISTER ] pubkey=${pubkey.take(16)}… " + + "permissions=${request.permissions} " ) val result = api.registerNwcKey( adminKey = secrets.adminKey(), diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/LightningAddressStore.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/LightningAddressStore.kt index 9a28eba..34386de 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/LightningAddressStore.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/LightningAddressStore.kt @@ -38,8 +38,7 @@ class LightningAddressStore(private val context: Context) { .map { prefs -> prefs[KEY_ADDRESS].also { address -> _currentAddress = address - Timber.d("LNADDR [CACHE ] read from cache: " + - if (address != null) "\"$address\"" else "(empty — nothing cached yet)") } + Timber.d("LNADDR [CACHE ] read from cache: %s", if (address != null) "\"$address\"" else "(empty — nothing cached yet)") } } suspend fun isCacheStale(): Boolean { @@ -53,9 +52,8 @@ class LightningAddressStore(private val context: Context) { val fetchedAt = prefs[KEY_FETCHED_AT] ?: 0L val age = System.currentTimeMillis() - fetchedAt val isStale = address == null || age > LNADDRESS_TTL_MS - Timber.d("LNADDR [TTL ] cached=${address != null} " + - "age=${age / 1000}s ttl=${LNADDRESS_TTL_MS / 1000}s " + - if (isStale) "→ cache stale, will fetch from API" + Timber.d("%s%s", "LNADDR [TTL ] cached=${address != null} " + + "age=${age / 1000}s ttl=${LNADDRESS_TTL_MS / 1000}s ", if (isStale) "→ cache stale, will fetch from API" else "→ cache fresh, skipping API call") isStale } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/LnurlCacheRepository.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/LnurlCacheRepository.kt index bbb14c3..963ad39 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/LnurlCacheRepository.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/LnurlCacheRepository.kt @@ -39,7 +39,7 @@ class LnurlCacheRepository(context: Context) { return null } - val response = deserialise(entity.responseJson) ?: run { + val response = deserialize(entity.responseJson) ?: run { Timber.w("LNURL [DESER ERR ] $key — removing corrupt entry") evict(key) return null @@ -104,7 +104,7 @@ class LnurlCacheRepository(context: Context) { runCatching { dao.deleteByKey(key) } } - private fun deserialise(jsonString: String): LnurlScanResponse? = + private fun deserialize(jsonString: String): LnurlScanResponse? = runCatching { json.decodeFromString(LnurlScanResponse.serializer(), jsonString) } .getOrNull() } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsDataStore.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsDataStore.kt index e535223..329f91f 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsDataStore.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsDataStore.kt @@ -1,20 +1,15 @@ package com.bitcointxoko.gudariwallet.security import android.content.Context -import timber.log.Timber import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.migrations.SharedPreferencesMigration -import androidx.datastore.migrations.SharedPreferencesView import com.bitcointxoko.gudariwallet.util.WalletConstants import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map import java.io.File -import androidx.core.content.edit /** * Singleton DataStore for encrypted credentials. @@ -27,8 +22,6 @@ import androidx.core.content.edit * [cleanUpLegacyPrefs] once you've confirmed migration succeeded. */ object CredentialsDataStore { - private const val TAG = "CredentialsDataStore" - @Volatile private var instance: DataStore? = null diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsSerializer.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsSerializer.kt index 0060c9b..d2b1a29 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsSerializer.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsSerializer.kt @@ -25,8 +25,8 @@ class CredentialsSerializer( Timber.d("Credentials decrypted successfully — isOnboarded: ${result.isOnboarded}") result } catch (e: Exception) { - Timber.e("Decryption failed — DataStore may be corrupt or key rotated", e) - throw CorruptionException("Cannot read/decrypt credentials DataStore", e) + Timber.e("Decryption failed — DataStore may be corrupt or key rotated") + throw CorruptionException("Cannot read/decrypt credentials DataStore") } } @@ -37,12 +37,10 @@ class CredentialsSerializer( val encryptingStream = streamingAead.newEncryptingStream(output, AAD) t.writeTo(encryptingStream) // returns Int — discarded by Unit return type encryptingStream.close() - Timber.d("Credentials encrypted and written — isOnboarded: ${t.isOnboarded}, " + - "hasInvoiceKey: ${t.invoiceKey.isNotBlank()}, " + - "hasAdminKey: ${t.adminKey.isNotBlank()}, " + - "hasBaseUrl: ${t.baseUrl.isNotBlank()}") + Timber.d("%snull", "Credentials encrypted and written — isOnboarded: ${t.isOnboarded}, " + + "hasInvoiceKey: ${t.invoiceKey.isNotBlank()}, ") } catch (e: Exception) { - Timber.e("Encryption/write failed", e) + Timber.e("Encryption/write failed") throw e } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsTinkManager.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsTinkManager.kt index a7ffdb7..f573345 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsTinkManager.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsTinkManager.kt @@ -38,7 +38,7 @@ object CredentialsTinkManager { Timber.i("StreamingAead primitive acquired successfully") } } catch (e: Exception) { - Timber.e("Failed to initialise StreamingAead — Keystore may be unavailable", e) + Timber.e("Failed to initialise StreamingAead — Keystore may be unavailable") throw e } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/service/NodeAliasService.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/service/NodeAliasService.kt index 232d3d4..64c93df 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/service/NodeAliasService.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/service/NodeAliasService.kt @@ -41,7 +41,7 @@ class NodeAliasService( repo.put(pubkey, alias) // 4. Publish to StateFlow - _aliases.value = _aliases.value + (pubkey to alias) + _aliases.value += (pubkey to alias) Timber.d("ALIAS [FETCHED ] $pubkey → \"$alias\"") return alias diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/service/WalletNotificationService.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/service/WalletNotificationService.kt index c94fee4..08f8446 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/service/WalletNotificationService.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/service/WalletNotificationService.kt @@ -30,6 +30,7 @@ import okhttp3.Request import okhttp3.Response import okhttp3.WebSocket import okhttp3.WebSocketListener +import kotlin.time.Duration.Companion.milliseconds private const val TAG = "WalletNotifService" @@ -271,7 +272,7 @@ class WalletNotificationService : Service() { reconnectJob?.cancel() reconnectJob = serviceScope.launch { - delay(backoff) + delay(backoff.milliseconds) openWebSocket() } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt index 7bd3ade..ddafcef 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt @@ -71,6 +71,7 @@ import com.bitcointxoko.gudariwallet.ui.send.SendScreen import com.bitcointxoko.gudariwallet.ui.send.SendStrings import com.bitcointxoko.gudariwallet.util.SendInputType import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.milliseconds // ── Tab destinations ────────────────────────────────────────────────────────── // Note: label and icon are now resolved at the call site via LocalAppStrings, @@ -205,7 +206,7 @@ fun WalletScreen( LaunchedEffect(clipboardOffer) { if (clipboardOffer is ClipboardOfferState.Detected) { - delay(8_000) + delay(8_000.milliseconds) vm.dismissClipboardOffer() } } @@ -215,7 +216,7 @@ fun WalletScreen( LaunchedEffect(nfcOffer) { if (nfcOffer is NfcOfferState.Detected) { - delay(8_000) + delay(8_000.milliseconds) nfcVm.dismissNfcOffer() } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletViewModelFactory.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletViewModelFactory.kt index daec30d..bd1ac46 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletViewModelFactory.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletViewModelFactory.kt @@ -13,7 +13,6 @@ import com.bitcointxoko.gudariwallet.data.LightningAddressStore import com.bitcointxoko.gudariwallet.data.LnurlCacheRepository import com.bitcointxoko.gudariwallet.data.NwcCacheRepository import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository -import com.bitcointxoko.gudariwallet.data.db.AppDatabase import com.bitcointxoko.gudariwallet.security.EncryptedSecretStore import com.bitcointxoko.gudariwallet.service.NodeAliasService import com.bitcointxoko.gudariwallet.ui.balance.BalanceViewModel diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/common/BrightnessController.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/common/BrightnessController.kt index 2acef95..26f386e 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/common/BrightnessController.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/common/BrightnessController.kt @@ -1,9 +1,8 @@ package com.bitcointxoko.gudariwallet.ui.common -import android.app.Activity import android.view.WindowManager +import androidx.activity.compose.LocalActivity import androidx.compose.runtime.* -import androidx.compose.ui.platform.LocalContext /** * Manages screen brightness for QR-display screens. @@ -12,12 +11,12 @@ import androidx.compose.ui.platform.LocalContext */ @Composable fun rememberBrightnessController(): BrightnessController { - val window = (LocalContext.current as? Activity)?.window + val window = (LocalActivity.current)?.window var isOn by remember { mutableStateOf(false) } - // Sync side-effect: no coroutine needed + // Sync side effect: no coroutine needed SideEffect { - window?.attributes = window?.attributes?.apply { + window?.attributes = window.attributes?.apply { screenBrightness = if (isOn) WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL else WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE } @@ -25,7 +24,7 @@ fun rememberBrightnessController(): BrightnessController { DisposableEffect(Unit) { onDispose { - window?.attributes = window?.attributes?.apply { + window?.attributes = window.attributes?.apply { screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/util/IntentUtils.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/util/IntentUtils.kt index da1d203..1561c03 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/util/IntentUtils.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/util/IntentUtils.kt @@ -23,7 +23,7 @@ object IntentUtils { * Returns a raw payment URI string if the intent carries one, else null. * * Handles: - * - ACTION_VIEW with a recognised scheme (BTCPay "Pay in wallet", browser links) + * - ACTION_VIEW with a recognized scheme (BTCPay "Pay in wallet", browser links) * - ACTION_SEND with text/plain (share sheet from another app) */ fun extractPaymentUri(intent: Intent?): String? { diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/util/LnurlMetadataParser.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/util/LnurlMetadataParser.kt index 7264cfd..ee42bbb 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/util/LnurlMetadataParser.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/util/LnurlMetadataParser.kt @@ -40,7 +40,7 @@ object LnurlMetadataParser { /** * Returns the display label for a LNURL payment target. * If [lnurl] is a Lightning Address (contains '@'), returns it as-is. - * Otherwise returns the [domain] of the LNURL endpoint. + * Otherwise, returns the [domain] of the LNURL endpoint. */ fun payingToLabel(lnurl: String, domain: String): String = if ('@' in lnurl) lnurl else domain \ No newline at end of file