diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5ae5967..6bc7742 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,7 +27,7 @@ android { minSdk = 26 targetSdk = 36 versionCode = 1 - versionName = "1.0" + versionName = "0.1.0" // testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner" @@ -160,11 +160,14 @@ dependencies { // secp256k1 implementation(libs.secp256k1.kmp.android) - + // lyricist implementation(libs.lyricist) // Required for @LyricistStrings code generation ksp(libs.lyricist.processor) + + // timber + implementation(libs.timber) } protobuf { diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/GudariWalletApplication.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/GudariWalletApplication.kt index 8ee1488..efe5121 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/GudariWalletApplication.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/GudariWalletApplication.kt @@ -9,6 +9,7 @@ import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository import com.bitcointxoko.gudariwallet.security.EncryptedSecretStore import com.bitcointxoko.gudariwallet.sync.HistoricalSyncWorkerFactory import com.bitcointxoko.gudariwallet.util.NotificationHelper +import timber.log.Timber class GudariWalletApplication : Application(), Configuration.Provider { @@ -29,5 +30,8 @@ class GudariWalletApplication : Application(), Configuration.Provider { override fun onCreate() { super.onCreate() NotificationHelper.createChannels(this) // moved from MainActivity + if (BuildConfig.DEBUG) { + Timber.plant(Timber.DebugTree()) + } } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/BalancePrefsStore.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/BalancePrefsStore.kt index 7504e42..8982f43 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/BalancePrefsStore.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/BalancePrefsStore.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.data import android.content.Context -import android.util.Log +import timber.log.Timber import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey @@ -47,7 +47,7 @@ class BalancePrefsStore(context: Context) { val prefs = dataStore.data .catch { e -> if (e is IOException) { - Log.w(TAG, "BALANCE [SATS ] read error — ${e.message}") + Timber.w("BALANCE [SATS ] read error — ${e.message}") emit(emptyPreferences()) } else throw e } @@ -62,7 +62,7 @@ class BalancePrefsStore(context: Context) { val prefs = dataStore.data .catch { e -> if (e is IOException) { - Log.w(TAG, "BALANCE [SAVED_AT] read error — ${e.message}") + Timber.w("BALANCE [SAVED_AT] read error — ${e.message}") emit(emptyPreferences()) } else throw e } @@ -80,9 +80,9 @@ class BalancePrefsStore(context: Context) { prefs[BALANCE_SAVED_AT] = System.currentTimeMillis() } }.onFailure { e -> - Log.w(TAG, "BALANCE [SATS ] persist failed — ${e.message}") + Timber.w("BALANCE [SATS ] persist failed — ${e.message}") } - Log.d(TAG, "BALANCE [SATS ] $sats sats — persisted to DataStore") + Timber.d("BALANCE [SATS ] $sats sats — persisted to DataStore") } // ── Balance visibility ──────────────────────────────────────────────────── @@ -94,7 +94,7 @@ class BalancePrefsStore(context: Context) { val isBalanceHiddenFlow: Flow = dataStore.data .catch { e -> if (e is IOException) { - Log.w(TAG, "BALANCE [HIDDEN ] read error — ${e.message}") + Timber.w("BALANCE [HIDDEN ] read error — ${e.message}") emit(emptyPreferences()) } else throw e } @@ -104,7 +104,7 @@ class BalancePrefsStore(context: Context) { runCatching { dataStore.edit { prefs -> prefs[BALANCE_HIDDEN] = hidden } }.onFailure { e -> - Log.w(TAG, "BALANCE [HIDDEN ] persist failed — ${e.message}") + Timber.w("BALANCE [HIDDEN ] persist failed — ${e.message}") } } @@ -114,7 +114,7 @@ class BalancePrefsStore(context: Context) { val prefs = dataStore.data .catch { e -> if (e is IOException) { - Log.w(TAG, "BALANCE [CURRENCY] read error — ${e.message}") + Timber.w("BALANCE [CURRENCY] read error — ${e.message}") emit(emptyPreferences()) } else throw e } @@ -129,7 +129,7 @@ class BalancePrefsStore(context: Context) { else prefs.remove(FIAT_CURRENCY) } }.onFailure { e -> - Log.w(TAG, "BALANCE [CURRENCY] persist failed — ${e.message}") + Timber.w("BALANCE [CURRENCY] persist failed — ${e.message}") } } } \ No newline at end of file diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/FiatDataStore.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/FiatDataStore.kt index aad170e..2b0f424 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/FiatDataStore.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/FiatDataStore.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.data import android.content.Context -import android.util.Log +import timber.log.Timber import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit @@ -45,14 +45,14 @@ class FiatDataStore(context: Context) { val prefs = dataStore.data .catch { e -> if (e is IOException) { - Log.w(TAG, "FIAT [RATE STALE ] read error — ${e.message}") + Timber.w("FIAT [RATE STALE ] read error — ${e.message}") emit(emptyPreferences()) } else throw e } .first() val value = prefs[rateKey(currency)]?.toDouble() if (value != null && value > 0.0) { - Log.d(TAG, "FIAT [RATE STALE ] $currency — using persisted rate $value") + Timber.d("FIAT [RATE STALE ] $currency — using persisted rate $value") } return value?.takeIf { it > 0.0 } } @@ -68,9 +68,9 @@ class FiatDataStore(context: Context) { prefs[rateFetchedAtKey(currency)] = System.currentTimeMillis() } }.onFailure { e -> - Log.w(TAG, "FIAT [RATE FRESH ] persist failed for $currency — ${e.message}") + Timber.w("FIAT [RATE FRESH ] persist failed for $currency — ${e.message}") } - Log.d(TAG, "FIAT [RATE FRESH ] $currency = $rate — persisted to DataStore") + Timber.d("FIAT [RATE FRESH ] $currency = $rate — persisted to DataStore") } // ── Currency list ───────────────────────────────────────────────────────── @@ -82,7 +82,7 @@ class FiatDataStore(context: Context) { val prefs = dataStore.data .catch { e -> if (e is IOException) { - Log.w(TAG, "FIAT [CURRENCIES] read error — ${e.message}") + Timber.w("FIAT [CURRENCIES] read error — ${e.message}") emit(emptyPreferences()) } else throw e } @@ -91,10 +91,10 @@ class FiatDataStore(context: Context) { return runCatching { val type = object : TypeToken>() {}.type val list: List = gson.fromJson(json, type) - Log.d(TAG, "FIAT [CURRENCIES] DataStore hit (${list.size} currencies)") + Timber.d("FIAT [CURRENCIES] DataStore hit (${list.size} currencies)") list }.getOrElse { e -> - Log.w(TAG, "FIAT [CURRENCIES] parse failed — ${e.message}") + Timber.w("FIAT [CURRENCIES] parse failed — ${e.message}") null } } @@ -109,8 +109,8 @@ class FiatDataStore(context: Context) { prefs[currenciesKey] = gson.toJson(currencies) } }.onFailure { e -> - Log.w(TAG, "FIAT [CURRENCIES] persist failed — ${e.message}") + Timber.w("FIAT [CURRENCIES] persist failed — ${e.message}") } - Log.d(TAG, "FIAT [CURRENCIES] persisted ${currencies.size} currencies to DataStore") + Timber.d("FIAT [CURRENCIES] persisted ${currencies.size} currencies to DataStore") } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/FiatRateCache.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/FiatRateCache.kt index 412b706..83d1c91 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/FiatRateCache.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/FiatRateCache.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.data -import android.util.Log +import timber.log.Timber private const val TAG = "FiatRateCache" @@ -29,14 +29,14 @@ class FiatRateCache(private val fiatDataStore: FiatDataStore) { val age = now - rateLastFetchedAt if (cachedRate != null && cachedRateCurrency == currency && age < ttlMs) { - Log.d(TAG, "FIAT [RATE CACHE ] $currency — in-memory hit (age ${age / 1000}s)") + Timber.d("FIAT [RATE CACHE ] $currency — in-memory hit (age ${age / 1000}s)") return cachedRate } // Cold-start fallback: last persisted rate from DataStore val persisted = fiatDataStore.getPersistedRate(currency) if (persisted != null) { - Log.d(TAG, "FIAT [RATE STALE ] $currency — using persisted rate $persisted") + Timber.d("FIAT [RATE STALE ] $currency — using persisted rate $persisted") return persisted } @@ -52,7 +52,7 @@ class FiatRateCache(private val fiatDataStore: FiatDataStore) { rateLastFetchedAt = now cachedRateCurrency = currency fiatDataStore.persistRate(currency, rate) - Log.d(TAG, "FIAT [RATE FRESH ] 1 $currency = $rate sats — cached + persisted") + Timber.d("FIAT [RATE FRESH ] 1 $currency = $rate sats — cached + persisted") } /** Clears the in-memory rate (e.g. when the user switches currency). */ @@ -72,7 +72,7 @@ class FiatRateCache(private val fiatDataStore: FiatDataStore) { */ suspend fun getCurrencies(): List? { cachedCurrencies?.let { - Log.d(TAG, "FIAT [CURRENCIES] in-memory hit (${it.size} currencies)") + Timber.d("FIAT [CURRENCIES] in-memory hit (${it.size} currencies)") return it } @@ -88,7 +88,7 @@ class FiatRateCache(private val fiatDataStore: FiatDataStore) { if (currencies.isEmpty()) return cachedCurrencies = currencies fiatDataStore.persistCurrencies(currencies) - Log.d(TAG, "FIAT [CURRENCIES] cached ${currencies.size} currencies") + Timber.d("FIAT [CURRENCIES] cached ${currencies.size} currencies") } val currentRate: Double? 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 c18f11c..fabbc38 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/LNbitsWalletRepository.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/LNbitsWalletRepository.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.data import android.net.Uri -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.* import com.bitcointxoko.gudariwallet.security.SecretStore import com.bitcointxoko.gudariwallet.util.Bolt11Decoder @@ -52,14 +52,14 @@ class LNbitsWalletRepository( // ── 1. Try local decode first (no network, no latency) ─────────────── val local = runCatching { Bolt11Decoder.decode(bolt11) }.getOrNull() if (local != null) { - Log.i("WalletRepository", "decodeBolt11: decoded locally ✓") + Timber.i("decodeBolt11: decoded locally ✓") return local } // ── 2. Fall back to network API ─────────────────────────────────────── - Log.w("WalletRepository", "decodeBolt11: local decode failed, falling back to network") + Timber.w("decodeBolt11: local decode failed, falling back to network") val response = api.decodeInvoice(secrets.invoiceKey(), DecodeInvoiceRequest(data = bolt11)) - Log.i("WalletRepository", "decodeBolt11: decoded via network ✓") + Timber.i("decodeBolt11: decoded via network ✓") val amountSats = response.amountMsat ?.takeIf { it > 0L } @@ -75,7 +75,7 @@ class LNbitsWalletRepository( // createdAt / expiresAt / fallbackAddress are not returned by the API — // use safe defaults and warn so we know if this path is hit in practice. - Log.w("WalletRepository", "decodeBolt11: network response has no timestamp/expiry — " + + Timber.w("decodeBolt11: network response has no timestamp/expiry — " + "using Instant.now() + 1h as fallback") val createdAt = Instant.now() val expiresAt = createdAt.plusSeconds(3600L) @@ -134,7 +134,7 @@ class LNbitsWalletRepository( } } - Log.d(TAG, "LNURL [DIRECT SCAN] $code → $httpsUrl") + Timber.d("LNURL [DIRECT SCAN] $code → $httpsUrl") val r = api.fetchLnurlMetadata(httpsUrl) return ScannedLnurl( @@ -160,7 +160,7 @@ class LNbitsWalletRepository( comment : String? ): PaymentConfirmation { val bolt11 = fetchLnurlCallbackInvoice(rawRes, amountMsat, comment) - Log.d(TAG, "LNURL [DIRECT PAY ] got invoice, paying bolt11") + Timber.d("LNURL [DIRECT PAY ] got invoice, paying bolt11") return payBolt11(bolt11) } @@ -186,7 +186,7 @@ class LNbitsWalletRepository( append(Uri.encode(comment)) } } - Log.d(TAG, "LNURL [FETCH INVOICE] callback=$callbackUrl") + Timber.d("LNURL [FETCH INVOICE] callback=$callbackUrl") val callbackResponse = api.fetchLnurlCallback(callbackUrl) if (callbackResponse.status == "ERROR") { throw RuntimeException( @@ -246,7 +246,7 @@ class LNbitsWalletRepository( append("&pr=") append(Uri.encode(bolt11)) } - Log.d(TAG, "LNURL-WITHDRAW [CALLBACK] $url") + Timber.d("LNURL-WITHDRAW [CALLBACK] $url") return api.withdrawCallback(url) } @@ -265,12 +265,12 @@ class LNbitsWalletRepository( // ── Fiat ────────────────────────────────────────────────────────────────── override suspend fun getFiatRate(currency: String): Double { - Log.d(TAG, "FIAT [RATE FETCH ] currency=$currency") + Timber.d("FIAT [RATE FETCH ] currency=$currency") return api.getFiatRate(currency).rate } override suspend fun getSupportedCurrencies(): List { - Log.d(TAG, "FIAT [CURRENCIES] fetching supported currency list") + Timber.d("FIAT [CURRENCIES] fetching supported currency list") return api.getSupportedCurrencies() } 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 c5bb296..9a28eba 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/LightningAddressStore.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/LightningAddressStore.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.data import android.content.Context -import android.util.Log +import timber.log.Timber import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit @@ -38,7 +38,7 @@ class LightningAddressStore(private val context: Context) { .map { prefs -> prefs[KEY_ADDRESS].also { address -> _currentAddress = address - Log.d("LightningAddressStore", "LNADDR [CACHE ] read from cache: " + + Timber.d("LNADDR [CACHE ] read from cache: " + if (address != null) "\"$address\"" else "(empty — nothing cached yet)") } } @@ -53,7 +53,7 @@ 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 - Log.d("LightningAddressStore", "LNADDR [TTL ] cached=${address != null} " + + 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" else "→ cache fresh, skipping API call") 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 7e942b6..4a91f94 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/LnurlCacheRepository.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/LnurlCacheRepository.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.data import android.content.Context -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.GsonProvider import com.bitcointxoko.gudariwallet.api.LnurlScanResponse import com.bitcointxoko.gudariwallet.data.db.AppDatabase @@ -34,13 +34,13 @@ class LnurlCacheRepository(context: Context) { val ageMs = System.currentTimeMillis() - entity.savedAt if (ageMs > WalletConstants.LNURL_CACHE_TTL_MS) { - Log.d(TAG, "LNURL [CACHE STALE] $key (age ${ageMs / 1000}s)") + Timber.d("LNURL [CACHE STALE] $key (age ${ageMs / 1000}s)") evict(key) return null } val response = deserialise(entity.responseJson) ?: run { - Log.w(TAG, "LNURL [DESER ERR ] $key — removing corrupt entry") + Timber.w("LNURL [DESER ERR ] $key — removing corrupt entry") evict(key) return null } @@ -48,11 +48,11 @@ class LnurlCacheRepository(context: Context) { // LUD-06: disposable == null means treat as true — do not serve from cache val disposable = response.disposable if (disposable == true) { - Log.d(TAG, "LNURL [DISPOSABLE ] $key — not serving from cache (disposable=$disposable)") + Timber.d("LNURL [DISPOSABLE ] $key — not serving from cache (disposable=$disposable)") return null } - Log.d(TAG, "LNURL [CACHE HIT ] $key (age ${ageMs / 1000}s)") + Timber.d("LNURL [CACHE HIT ] $key (age ${ageMs / 1000}s)") return response } @@ -63,7 +63,7 @@ class LnurlCacheRepository(context: Context) { suspend fun put(key: String, response: LnurlScanResponse) { val disposable = response.disposable if ( disposable == true) { - Log.d(TAG, "LNURL [SKIP CACHE ] $key — disposable=$disposable, not caching") + Timber.d("LNURL [SKIP CACHE ] $key — disposable=$disposable, not caching") return } val entity = LnurlCacheEntity( @@ -74,9 +74,9 @@ class LnurlCacheRepository(context: Context) { runCatching { dao.insert(entity) memCache[key] = entity - Log.d(TAG, "LNURL [CACHE SET ] $key") + Timber.d("LNURL [CACHE SET ] $key") }.onFailure { - Log.w(TAG, "LNURL [PERSIST ERR] ${it.message}") + Timber.w("LNURL [PERSIST ERR] ${it.message}") } } @@ -87,14 +87,14 @@ class LnurlCacheRepository(context: Context) { */ suspend fun invalidate(key: String) { evict(key) - Log.d(TAG, "LNURL [INVALIDATED] $key") + Timber.d("LNURL [INVALIDATED] $key") } suspend fun clearAll() { memCache.clear() runCatching { dao.deleteAll() } - .onFailure { Log.w(TAG, "LNURL [CLEAR ERR ] ${it.message}") } - Log.d(TAG, "LNURL [CLEARED ] all entries") + .onFailure { Timber.w("LNURL [CLEAR ERR ] ${it.message}") } + Timber.d("LNURL [CLEARED ] all entries") } // ── Helpers ──────────────────────────────────────────────────────────────── diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/NodeAliasCacheRepository.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/NodeAliasCacheRepository.kt index 1bff074..94ae66e 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/NodeAliasCacheRepository.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/NodeAliasCacheRepository.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.data import android.content.Context -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.data.db.AppDatabase import com.bitcointxoko.gudariwallet.data.db.NodeAliasCacheEntity import com.bitcointxoko.gudariwallet.util.WalletConstants @@ -18,15 +18,15 @@ class NodeAliasCacheRepository(context: Context) { */ suspend fun get(pubkey: String): String? { val entry = dao.getByPubkey(pubkey) ?: run { - Log.d(TAG, "ALIAS [CACHE MISS ] $pubkey") + Timber.d("ALIAS [CACHE MISS ] $pubkey") return null } val ageMs = System.currentTimeMillis() - entry.savedAt return if (ageMs < WalletConstants.ALIAS_CACHE_TTL_MS) { - Log.d(TAG, "ALIAS [CACHE HIT ] $pubkey → \"${entry.alias}\" (age ${ageMs / 1000}s)") + Timber.d("ALIAS [CACHE HIT ] $pubkey → \"${entry.alias}\" (age ${ageMs / 1000}s)") entry.alias } else { - Log.d(TAG, "ALIAS [CACHE STALE] $pubkey (age ${ageMs / 1000}s) — will refetch") + Timber.d("ALIAS [CACHE STALE] $pubkey (age ${ageMs / 1000}s) — will refetch") null } } @@ -40,7 +40,7 @@ class NodeAliasCacheRepository(context: Context) { savedAt = System.currentTimeMillis() ) ) - Log.d(TAG, "ALIAS [PERSIST ] $pubkey → \"$alias\"") + Timber.d("ALIAS [PERSIST ] $pubkey → \"$alias\"") } /** @@ -52,7 +52,7 @@ class NodeAliasCacheRepository(context: Context) { return dao.getAll() .filter { now - it.savedAt < WalletConstants.ALIAS_PERSIST_TTL_MS } .associate { it.pubkey to it.alias } - .also { Log.d(TAG, "ALIAS [PERSIST ] Loaded ${it.size} non-stale entries from Room") } + .also { Timber.d("ALIAS [PERSIST ] Loaded ${it.size} non-stale entries from Room") } } /** Remove a single entry. */ @@ -63,6 +63,6 @@ class NodeAliasCacheRepository(context: Context) { /** Wipe the entire alias cache — mirrors [clearPersistedCache]. */ suspend fun clearAll() { dao.deleteAll() - Log.d(TAG, "ALIAS [PERSIST ] Cache cleared") + Timber.d("ALIAS [PERSIST ] Cache cleared") } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/PaymentCacheRepository.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/PaymentCacheRepository.kt index 46d2523..85f5442 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/PaymentCacheRepository.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/PaymentCacheRepository.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.data import android.content.Context -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.PaymentDetailResponse import com.bitcointxoko.gudariwallet.api.PaymentRecord import com.bitcointxoko.gudariwallet.data.db.AppDatabase @@ -25,16 +25,16 @@ class PaymentCacheRepository(context: Context) { /** Load the persisted first-page list. Returns empty list if nothing cached or cache is stale. */ suspend fun loadCachedPayments(): List { val savedAt = dao.getLatestSavedAt() ?: run { - Log.d(TAG, "PAYMENTS [CACHE MISS] no rows in db") + Timber.d("PAYMENTS [CACHE MISS] no rows in db") return emptyList() } val ageMs = System.currentTimeMillis() - savedAt if (ageMs > WalletConstants.PAYMENTS_CACHE_TTL_MS) { - Log.d(TAG, "PAYMENTS [CACHE STALE] age ${ageMs / 1000}s — ignoring persisted list") + Timber.d("PAYMENTS [CACHE STALE] age ${ageMs / 1000}s — ignoring persisted list") return emptyList() } return dao.getAll().map { it.toDomain() }.also { - Log.d(TAG, "PAYMENTS [CACHE HIT ] loaded ${it.size} payments (age ${ageMs / 1000}s)") + Timber.d("PAYMENTS [CACHE HIT ] loaded ${it.size} payments (age ${ageMs / 1000}s)") } } @@ -44,9 +44,9 @@ class PaymentCacheRepository(context: Context) { .map { it.toDomain() } .also { if (it.isNotEmpty()) - Log.d(TAG, "PAYMENTS [CACHE HIT ] offset=$offset got ${it.size} rows from Room") + Timber.d("PAYMENTS [CACHE HIT ] offset=$offset got ${it.size} rows from Room") else - Log.d(TAG, "PAYMENTS [CACHE MISS] offset=$offset — no rows in Room") + Timber.d("PAYMENTS [CACHE MISS] offset=$offset — no rows in Room") } } @@ -59,9 +59,9 @@ class PaymentCacheRepository(context: Context) { val now = System.currentTimeMillis() runCatching { dao.insertAll(payments.map { it.toEntity(now) }) - Log.d(TAG, "PAYMENTS [MERGE ] upserted ${payments.size} payments (total: ${dao.count()})") + Timber.d("PAYMENTS [MERGE ] upserted ${payments.size} payments (total: ${dao.count()})") }.onFailure { - Log.w(TAG, "PAYMENTS [MERGE ERR] ${it.message}") + Timber.w("PAYMENTS [MERGE ERR] ${it.message}") } } @@ -71,9 +71,9 @@ class PaymentCacheRepository(context: Context) { runCatching { dao.deleteAll() dao.insertAll(payments.map { it.toEntity(now) }) - Log.d(TAG, "PAYMENTS [PERSIST ] saved ${payments.size} payments") + Timber.d("PAYMENTS [PERSIST ] saved ${payments.size} payments") }.onFailure { - Log.w(TAG, "PAYMENTS [PERSIST ] write failed: ${it.message}") + Timber.w("PAYMENTS [PERSIST ] write failed: ${it.message}") } } @@ -109,7 +109,7 @@ class PaymentCacheRepository(context: Context) { minCreatedAt = filter.minCreatedAt, maxCreatedAt = filter.maxCreatedAt ).map { it.toDomain() }.also { - Log.d(TAG, "PAYMENTS [QUERY ] q=$searchQuery status=$statusPattern " + + Timber.d("PAYMENTS [QUERY ] q=$searchQuery status=$statusPattern " + "amt=${filter.minAmountSat}–${filter.maxAmountSat} " + "date=${filter.minCreatedAt}–${filter.maxCreatedAt} → ${it.size} rows") } @@ -120,26 +120,26 @@ class PaymentCacheRepository(context: Context) { /** Returns a cached detail response, or null if not yet fetched this session. */ fun getCachedDetail(checkingId: String): PaymentDetailResponse? = detailCache[checkingId]?.also { - Log.d(TAG, "PAYMENTS [DETAIL HIT] $checkingId") + Timber.d("PAYMENTS [DETAIL HIT] $checkingId") } /** Store a detail response in the in-memory cache. */ fun saveDetail(checkingId: String, detail: PaymentDetailResponse) { detailCache[checkingId] = detail - Log.d(TAG, "PAYMENTS [DETAIL SET] $checkingId") + Timber.d("PAYMENTS [DETAIL SET] $checkingId") } /** Upsert PaymentRecord to database. */ suspend fun upsertPayment(payment: PaymentRecord) { val entity = payment.toEntity(savedAt = System.currentTimeMillis()) dao.insert(entity) - Log.d(TAG, "PAYMENTS [UPSERT ] ${payment.checkingId}") + Timber.d("PAYMENTS [UPSERT ] ${payment.checkingId}") } /** Check the DB for a stored payment record and return it as a detail response. */ suspend fun getDetailFromDb(checkingId: String): PaymentDetailResponse? { val entity = dao.getByCheckingId(checkingId) ?: return null - Log.d(TAG, "PAYMENTS [DETAIL DB ] $checkingId — found in Room") + Timber.d("PAYMENTS [DETAIL DB ] $checkingId — found in Room") return PaymentDetailResponse( paid = entity.status == "success", details = entity.toDomain() @@ -158,6 +158,6 @@ class PaymentCacheRepository(context: Context) { suspend fun clearAll() { detailCache.clear() dao.deleteAll() - Log.d(TAG, "PAYMENTS [CLEARED ] cache wiped") + Timber.d("PAYMENTS [CLEARED ] cache wiped") } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/db/PaymentRecordMapper.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/db/PaymentRecordMapper.kt index 310f900..4397b69 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/db/PaymentRecordMapper.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/db/PaymentRecordMapper.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.data.db -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.GsonProvider.gson import com.bitcointxoko.gudariwallet.api.PaymentRecord import com.bitcointxoko.gudariwallet.data.db.PaymentRecordEntity @@ -16,7 +16,7 @@ private fun parseCreatedAt(time: String): Long? { java.time.OffsetDateTime.parse(time).toEpochSecond() }.getOrNull() if (result == null) { - Log.w(TAG, "parseCreatedAt: could not parse time string \"$time\" — createdAt will be null") + Timber.w("parseCreatedAt: could not parse time string \"$time\" — createdAt will be null") } return result } 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 da96c3f..e535223 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsDataStore.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsDataStore.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.security import android.content.Context -import android.util.Log +import timber.log.Timber import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler 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 ddd58dd..0060c9b 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsSerializer.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsSerializer.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.security -import android.util.Log +import timber.log.Timber import androidx.datastore.core.CorruptionException import androidx.datastore.core.Serializer import com.google.crypto.tink.StreamingAead @@ -22,10 +22,10 @@ class CredentialsSerializer( val result = Credentials.parseFrom( streamingAead.newDecryptingStream(input, AAD) ) - Log.d(TAG, "Credentials decrypted successfully — isOnboarded: ${result.isOnboarded}") + Timber.d("Credentials decrypted successfully — isOnboarded: ${result.isOnboarded}") result } catch (e: Exception) { - Log.e(TAG, "Decryption failed — DataStore may be corrupt or key rotated", e) + Timber.e("Decryption failed — DataStore may be corrupt or key rotated", e) throw CorruptionException("Cannot read/decrypt credentials DataStore", e) } } @@ -37,12 +37,12 @@ class CredentialsSerializer( val encryptingStream = streamingAead.newEncryptingStream(output, AAD) t.writeTo(encryptingStream) // returns Int — discarded by Unit return type encryptingStream.close() - Log.d(TAG, "Credentials encrypted and written — isOnboarded: ${t.isOnboarded}, " + + Timber.d("Credentials encrypted and written — isOnboarded: ${t.isOnboarded}, " + "hasInvoiceKey: ${t.invoiceKey.isNotBlank()}, " + "hasAdminKey: ${t.adminKey.isNotBlank()}, " + "hasBaseUrl: ${t.baseUrl.isNotBlank()}") } catch (e: Exception) { - Log.e(TAG, "Encryption/write failed", e) + Timber.e("Encryption/write failed", e) 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 59dc11a..a7ffdb7 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsTinkManager.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/security/CredentialsTinkManager.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.security import android.content.Context -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.util.WalletConstants import com.google.crypto.tink.StreamingAead import com.google.crypto.tink.integration.android.AndroidKeysetManager @@ -21,7 +21,7 @@ object CredentialsTinkManager { init { // Register all StreamingAead primitives once at class load time StreamingAeadConfig.register() - Log.d(TAG, "StreamingAeadConfig registered") + Timber.d("StreamingAeadConfig registered") } fun getOrCreateStreamingAead(context: Context): StreamingAead { @@ -33,12 +33,12 @@ object CredentialsTinkManager { .build() .keysetHandle - Log.i(TAG, "Keyset loaded — key count: ${handle.size()}, primary key ID: ${handle.primary.id}") + Timber.i("Keyset loaded — key count: ${handle.size()}, primary key ID: ${handle.primary.id}") handle.getPrimitive(StreamingAead::class.java).also { - Log.i(TAG, "StreamingAead primitive acquired successfully") + Timber.i("StreamingAead primitive acquired successfully") } } catch (e: Exception) { - Log.e(TAG, "Failed to initialise StreamingAead — Keystore may be unavailable", e) + Timber.e("Failed to initialise StreamingAead — Keystore may be unavailable", e) throw e } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/security/SecretStore.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/security/SecretStore.kt index 01e9d19..dc28ae9 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/security/SecretStore.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/security/SecretStore.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.security import android.content.Context -import android.util.Log +import timber.log.Timber import androidx.datastore.core.DataStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first @@ -50,7 +50,7 @@ class EncryptedSecretStore(context: Context) : SecretStore { invoiceKey: String, adminKey: String ) { - Log.i(TAG, "Saving credentials — baseUrl length: ${baseUrl.length}, " + + Timber.i("Saving credentials — baseUrl length: ${baseUrl.length}, " + "invoiceKey length: ${invoiceKey.length}, adminKey length: ${adminKey.length}") dataStore.updateData { current -> current.toBuilder() @@ -59,7 +59,7 @@ class EncryptedSecretStore(context: Context) : SecretStore { .setAdminKey(adminKey) .build() } - Log.i(TAG, "Credentials saved and DataStore updated") + Timber.i("Credentials saved and DataStore updated") } override suspend fun saveBaseUrl(baseUrl: String) { dataStore.updateData { current -> @@ -67,7 +67,7 @@ class EncryptedSecretStore(context: Context) : SecretStore { .setBaseUrl(baseUrl) .build() } - Log.i(TAG, "BaseUrl saved and DataStore updated") + Timber.i("BaseUrl saved and DataStore updated") } override suspend fun saveInvoiceKey(invoiceKey: String) { @@ -76,7 +76,7 @@ class EncryptedSecretStore(context: Context) : SecretStore { .setInvoiceKey(invoiceKey) .build() } - Log.i(TAG, "InvoiceKey saved and DataStore updated") + Timber.i("InvoiceKey saved and DataStore updated") } override suspend fun saveAdminKey(adminKey: String) { @@ -85,7 +85,7 @@ class EncryptedSecretStore(context: Context) : SecretStore { .setAdminKey(adminKey) .build() } - Log.i(TAG, "AdminKey saved and DataStore updated") + Timber.i("AdminKey saved and DataStore updated") } override suspend fun setOnboarded() { 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 90cfbcd..232d3d4 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/service/NodeAliasService.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/service/NodeAliasService.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.service import android.content.Context -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.NodeAliasClient as NodeAliasApiClient import com.bitcointxoko.gudariwallet.data.NodeAliasCacheRepository import kotlinx.coroutines.flow.MutableStateFlow @@ -26,14 +26,14 @@ class NodeAliasService( suspend fun resolve(pubkey: String): String? { // 1. Room cache hit repo.get(pubkey)?.let { - Log.d(TAG, "ALIAS [CACHE HIT ] $pubkey → \"$it\"") + Timber.d("ALIAS [CACHE HIT ] $pubkey → \"$it\"") return it } // 2. Network via api/NodeAliasService (Retrofit, typed responses) - Log.d(TAG, "ALIAS [CACHE MISS] $pubkey — fetching from network") + Timber.d("ALIAS [CACHE MISS] $pubkey — fetching from network") val alias = apiClient.resolveAlias(pubkey) ?: run { - Log.w(TAG, "ALIAS [NOT FOUND ] $pubkey") + Timber.w("ALIAS [NOT FOUND ] $pubkey") return null } @@ -43,7 +43,7 @@ class NodeAliasService( // 4. Publish to StateFlow _aliases.value = _aliases.value + (pubkey to alias) - Log.d(TAG, "ALIAS [FETCHED ] $pubkey → \"$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 38d7fed..baa8053 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/service/WalletNotificationService.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/service/WalletNotificationService.kt @@ -6,7 +6,7 @@ import android.content.Intent import android.content.pm.ServiceInfo import android.os.Build import android.os.IBinder -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.GsonProvider import com.bitcointxoko.gudariwallet.api.LNbitsClient import com.bitcointxoko.gudariwallet.api.PaymentRecord @@ -124,7 +124,7 @@ class WalletNotificationService : Service() { override fun onDestroy() { super.onDestroy() - Log.d(TAG, "Service destroyed — closing WebSocket") + Timber.d("Service destroyed — closing WebSocket") reconnectJob?.cancel() webSocket?.close(WalletConstants.WS_CLOSE_NORMAL, "Service stopped") webSocket = null @@ -143,7 +143,7 @@ class WalletNotificationService : Service() { val baseUrl = secrets.baseUrl() if (invoiceKey.isBlank() || baseUrl.isBlank()) { - Log.e(TAG, "Credentials not available — cannot open WebSocket") + Timber.e("Credentials not available — cannot open WebSocket") stopSelf() return } @@ -153,36 +153,36 @@ class WalletNotificationService : Service() { .replace("http://", "ws://") .trimEnd('/') + "/api/v1/ws/${invoiceKey}" - Log.d(TAG, "Opening WebSocket: $wsUrl") + Timber.d("Opening WebSocket: $wsUrl") val request = Request.Builder().url(wsUrl).build() webSocket = LNbitsClient.httpClient.newWebSocket(request, object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { - Log.d(TAG, "WebSocket connected") + Timber.d("WebSocket connected") // Reset backoff on successful connection reconnectAttempts = 0 currentBackoffMs = WalletConstants.WS_INITIAL_BACKOFF_MS } override fun onMessage(webSocket: WebSocket, text: String) { - Log.d(TAG, "WebSocket message: $text") + Timber.d("WebSocket message: $text") handleMessage(text) } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { - Log.e(TAG, "WebSocket failure (attempt $reconnectAttempts): ${t.message}") + Timber.e("WebSocket failure (attempt $reconnectAttempts): ${t.message}") scheduleReconnect() } override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { - Log.d(TAG, "WebSocket closing: $code $reason") + Timber.d("WebSocket closing: $code $reason") webSocket.close(WalletConstants.WS_CLOSE_NORMAL, null) } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { - Log.d(TAG, "WebSocket closed: $code $reason") + Timber.d("WebSocket closed: $code $reason") // Only reconnect on unexpected closes (not our own WS_CLOSE_NORMAL) if (code != WalletConstants.WS_CLOSE_NORMAL) { scheduleReconnect() @@ -257,7 +257,7 @@ class WalletNotificationService : Service() { private fun scheduleReconnect() { if (reconnectAttempts >= WalletConstants.WS_MAX_ATTEMPTS) { - Log.e(TAG, "WebSocket gave up after ${WalletConstants.WS_MAX_ATTEMPTS} attempts — service will wait for next start") + Timber.e("WebSocket gave up after ${WalletConstants.WS_MAX_ATTEMPTS} attempts — service will wait for next start") // Do not stop the service — it will reconnect on next onStartCommand // (e.g. when the app is brought to foreground and MainActivity calls start()) return @@ -267,7 +267,7 @@ class WalletNotificationService : Service() { reconnectAttempts++ currentBackoffMs = (currentBackoffMs * 2).coerceAtMost(WalletConstants.WS_MAX_BACKOFF_MS) - Log.d(TAG, "Reconnecting in ${backoff}ms (attempt $reconnectAttempts of ${WalletConstants.WS_MAX_ATTEMPTS})") + Timber.d("Reconnecting in ${backoff}ms (attempt $reconnectAttempts of ${WalletConstants.WS_MAX_ATTEMPTS})") reconnectJob?.cancel() reconnectJob = serviceScope.launch { diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/sync/HistoricalSyncWorker.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/sync/HistoricalSyncWorker.kt index 834fb1a..b08ef14 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/sync/HistoricalSyncWorker.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/sync/HistoricalSyncWorker.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.sync import android.content.Context -import android.util.Log +import timber.log.Timber import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import androidx.work.workDataOf @@ -29,11 +29,11 @@ class HistoricalSyncWorker( override suspend fun doWork(): Result { // Guard: already completed — safe if WorkManager ever retries a success if (syncStore.isComplete()) { - Log.d(TAG, "Already complete — skipping") + Timber.d("Already complete — skipping") return Result.success() } - Log.d(TAG, "Starting historical payment sync") + Timber.d("Starting historical payment sync") var offset = 0 var total = -1 @@ -41,14 +41,14 @@ class HistoricalSyncWorker( val page = runCatching { repo.getPaymentsPaginated(offset = offset, limit = PAGE_SIZE) }.getOrElse { e -> - Log.w(TAG, "Network error on offset=$offset: ${e.message} — scheduling retry") + Timber.w("Network error on offset=$offset: ${e.message} — scheduling retry") return Result.retry() // WorkManager applies exponential backoff automatically } // First page: learn the real total from the server if (total == -1) { total = page.total - Log.d(TAG, "Server reports $total total payments") + Timber.d("Server reports $total total payments") if (total == 0) { syncStore.markComplete() return Result.success() @@ -59,7 +59,7 @@ class HistoricalSyncWorker( paymentCache.mergePayments(page.data) offset += page.data.size - Log.d(TAG, "Progress: $offset / $total") + Timber.d("Progress: $offset / $total") setProgress(workDataOf( KEY_FETCHED to offset, KEY_TOTAL to total @@ -70,7 +70,7 @@ class HistoricalSyncWorker( } syncStore.markComplete() - Log.d(TAG, "Historical sync complete — $offset payments stored") + Timber.d("Historical sync complete — $offset payments stored") NotificationHelper.notifySyncComplete(applicationContext, offset) return Result.success() } 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 7433f2f..60498ab 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt @@ -1,7 +1,7 @@ package com.bitcointxoko.gudariwallet.ui import android.content.ClipboardManager -import android.util.Log +import timber.log.Timber import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn @@ -164,7 +164,7 @@ fun WalletScreen( LaunchedEffect(Unit) { vm.navigateToReceive.collect { - Log.d("WalletScreen", "LNURL-WITHDRAW [NAV RECEIVED] switching to Receive tab") + Timber.d("LNURL-WITHDRAW [NAV RECEIVED] switching to Receive tab") navController.navigate(TabItem.Receive.route) { popUpTo(navController.graph.findStartDestination().id) { saveState = true @@ -178,7 +178,7 @@ fun WalletScreen( val intentUri = pendingPaymentUri.value LaunchedEffect(intentUri) { if (intentUri != null) { - Log.d("WalletScreen", "INTENT [URI] deep link received: $intentUri") + Timber.d("INTENT [URI] deep link received: $intentUri") pendingPaymentUri.value = null vm.scan(intentUri, sendStrings) navController.navigate(TabItem.Send.route) { diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletViewModel.kt index c2594dd..09278de 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletViewModel.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui -import android.util.Log +import timber.log.Timber import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bitcointxoko.gudariwallet.api.LnurlScanResponse @@ -144,21 +144,21 @@ class WalletViewModel( // via SharingStarted.Eagerly — this coroutine only does the network refresh. viewModelScope.launch { if (!lnAddressStore.isCacheStale()) { - Log.d(TAG, "LNADDR [TTL ] cache is fresh — skipping network fetch") + Timber.d("LNADDR [TTL ] cache is fresh — skipping network fetch") return@launch } runCatching { repo.getLightningAddress() } .onSuccess { address -> if (address != null) { - Log.d(TAG, "LNADDR [FETCH OK ] fetched from API: \"$address\" — updating cache") + Timber.d("LNADDR [FETCH OK ] fetched from API: \"$address\" — updating cache") lnAddressStore.setLightningAddress(address) // StateFlow updates automatically via the DataStore flow } else { - Log.d(TAG, "LNADDR [FETCH OK ] fetched from API: no pay link / username configured") + Timber.d("LNADDR [FETCH OK ] fetched from API: no pay link / username configured") } } .onFailure { e -> - Log.w(TAG, "LNADDR [FETCH ERR] API call failed (${e.message}) — falling back to cached value") + Timber.w("LNADDR [FETCH ERR] API call failed (${e.message}) — falling back to cached value") // Nothing to do — DataStore flow already holds the last good value } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/balance/BalanceViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/balance/BalanceViewModel.kt index 103bdc4..28d77fe 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/balance/BalanceViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/balance/BalanceViewModel.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.balance -import android.util.Log +import timber.log.Timber import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bitcointxoko.gudariwallet.data.BalancePrefsStore @@ -45,7 +45,7 @@ class BalanceViewModel( val savedAt = balancePrefs.getBalanceSavedAt() val ageMs = System.currentTimeMillis() - savedAt if (savedSats >= 0 && ageMs < WalletConstants.BALANCE_CACHE_TTL_MS) { - Log.d(TAG, "BALANCE [CACHE HIT ] $savedSats sats (age ${ageMs / 1000}s) — skipping network fetch") + Timber.d("BALANCE [CACHE HIT ] $savedSats sats (age ${ageMs / 1000}s) — skipping network fetch") _balanceState.value = BalanceState.Success( sats = savedSats, isRefreshing = false, @@ -53,9 +53,9 @@ class BalanceViewModel( ) } else { if (savedSats >= 0) { - Log.d(TAG, "BALANCE [CACHE STALE] $savedSats sats, age ${ageMs / 1000}s > TTL — cold load") + Timber.d("BALANCE [CACHE STALE] $savedSats sats, age ${ageMs / 1000}s > TTL — cold load") } else { - Log.d(TAG, "BALANCE [CACHE MISS ] No persisted balance — cold load") + Timber.d("BALANCE [CACHE MISS ] No persisted balance — cold load") } _balanceState.value = BalanceState.Loading refreshBalance() @@ -67,7 +67,7 @@ class BalanceViewModel( fun refreshBalance() { val current = _balanceState.value if (current is BalanceState.Success && !current.isRefreshing) { - Log.d(TAG, "BALANCE [REFRESH ] Manual refresh triggered (current: ${current.sats} sats)") + Timber.d("BALANCE [REFRESH ] Manual refresh triggered (current: ${current.sats} sats)") _balanceState.value = current.copy(isRefreshing = true) } @@ -77,9 +77,9 @@ class BalanceViewModel( val sats = balance.sats val prev = (_balanceState.value as? BalanceState.Success)?.sats when { - prev == null -> Log.d(TAG, "BALANCE [NETWORK ] $sats sats — cold load complete") - prev == sats -> Log.d(TAG, "BALANCE [NETWORK ] $sats sats — matches cache, no change") - else -> Log.d(TAG, "BALANCE [NETWORK ] $sats sats — was $prev sats (diff ${sats - prev})") + prev == null -> Timber.d("BALANCE [NETWORK ] $sats sats — cold load complete") + prev == sats -> Timber.d("BALANCE [NETWORK ] $sats sats — matches cache, no change") + else -> Timber.d("BALANCE [NETWORK ] $sats sats — was $prev sats (diff ${sats - prev})") } _balanceState.value = BalanceState.Success( sats = sats, @@ -91,13 +91,13 @@ class BalanceViewModel( onBalanceRefreshed?.invoke() } .onFailure { e -> - Log.w(TAG, "BALANCE [NET ERROR ] ${e.message}") + Timber.w("BALANCE [NET ERROR ] ${e.message}") val staleSats = (_balanceState.value as? BalanceState.Success)?.sats ?: balancePrefs.getSavedBalanceSats().takeIf { it >= 0 } if (staleSats != null) { - Log.w(TAG, "BALANCE [NET ERROR ] Falling back to stale value: $staleSats sats") + Timber.w("BALANCE [NET ERROR ] Falling back to stale value: $staleSats sats") } else { - Log.w(TAG, "BALANCE [NET ERROR ] No stale value available — showing error state") + Timber.w("BALANCE [NET ERROR ] No stale value available — showing error state") } _balanceState.value = BalanceState.Error( message = e.message ?: "Failed to load balance", @@ -115,11 +115,11 @@ class BalanceViewModel( private fun observePaymentEvents() { viewModelScope.launch { WalletNotificationService.paymentEvents.collect { event -> - Log.d(TAG, "BALANCE [WS EVENT ] ${if (event.isOutgoing) "SENT" else "RECEIVED"} ${event.amountSats} sats") + Timber.d("BALANCE [WS EVENT ] ${if (event.isOutgoing) "SENT" else "RECEIVED"} ${event.amountSats} sats") val newSats = if (event.walletBalance != null) { // Authoritative balance from the server — no delta math needed - Log.d(TAG, "BALANCE [WS BALANCE] ${event.walletBalance} sats (from server)") + Timber.d("BALANCE [WS BALANCE] ${event.walletBalance} sats (from server)") event.walletBalance } else { // Fallback: server didn't send balance, apply delta optimistically @@ -127,7 +127,7 @@ class BalanceViewModel( if (current is BalanceState.Success) { val delta = if (event.isOutgoing) -event.amountSats else event.amountSats val sats = (current.sats + delta).coerceAtLeast(0L) - Log.d(TAG, "BALANCE [WS DELTA ] ${current.sats} ${if (delta >= 0) "+" else ""}$delta = $sats sats (optimistic fallback)") + Timber.d("BALANCE [WS DELTA ] ${current.sats} ${if (delta >= 0) "+" else ""}$delta = $sats sats (optimistic fallback)") sats } else null } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/clipboard/ClipboardViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/clipboard/ClipboardViewModel.kt index c9ff0ad..a29b823 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/clipboard/ClipboardViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/clipboard/ClipboardViewModel.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.clipboard -import android.util.Log +import timber.log.Timber import androidx.lifecycle.ViewModel import com.bitcointxoko.gudariwallet.ui.ClipboardOfferState import com.bitcointxoko.gudariwallet.util.SendInputDetector @@ -23,7 +23,7 @@ class ClipboardViewModel( val clipboardOfferState: StateFlow = _clipboardOfferState fun checkClipboard(text: String?) { - Log.d(TAG, "checkClipboard called with: $text") + Timber.d("checkClipboard called with: $text") val trimmed = text?.trim().takeIf { !it.isNullOrBlank() } ?: return // ── Deduplicate: don't re-offer the same string twice ───────────────── @@ -32,7 +32,7 @@ class ClipboardViewModel( // ── Guard: ignore our own invoice ────────────────────────────────────── val invoice = ownInvoice() if (invoice != null && trimmed.equals(invoice, ignoreCase = true)) { - Log.d(TAG, "Skipping own invoice") + Timber.d("Skipping own invoice") lastOfferedClipboard = trimmed // mark as seen so re-focus doesn't re-trigger return } @@ -40,7 +40,7 @@ class ClipboardViewModel( // ── Guard: ignore our own lightning address ──────────────────────────── val address = ownAddress() if (address != null && trimmed.equals(address, ignoreCase = true)) { - Log.d(TAG, "Skipping own lightning address") + Timber.d("Skipping own lightning address") lastOfferedClipboard = trimmed return } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/fiat/FiatViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/fiat/FiatViewModel.kt index 2ea4cf7..c0efcba 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/fiat/FiatViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/fiat/FiatViewModel.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.fiat -import android.util.Log +import timber.log.Timber import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bitcointxoko.gudariwallet.data.BalancePrefsStore @@ -57,7 +57,7 @@ class FiatViewModel( init { viewModelScope.launch { _selectedCurrency.value = balancePrefs.getSelectedCurrency() - Log.d(TAG, "FIAT [CURRENCY ] loaded from DataStore: ${_selectedCurrency.value ?: "none"}") + Timber.d("FIAT [CURRENCY ] loaded from DataStore: ${_selectedCurrency.value ?: "none"}") balanceVm.balanceState.collect { state -> if (state is BalanceState.Success && !state.isRefreshing) { fetchAndApplyFiatRate() @@ -72,7 +72,7 @@ class FiatViewModel( viewModelScope.launch { balancePrefs.setSelectedCurrency(currency) fiatCache.invalidateRate() - Log.d(TAG, "FIAT [CURRENCY ] changed to ${currency ?: "none"} — cache invalidated") + Timber.d("FIAT [CURRENCY ] changed to ${currency ?: "none"} — cache invalidated") if (currency != null) { viewModelScope.launch { fetchAndApplyFiatRate() } } else { @@ -109,8 +109,8 @@ class FiatViewModel( ?: run { runCatching { repo.getFiatRate(currency) } .getOrElse { e -> - Log.w(TAG, "FIAT [RATE ERROR ] $currency — ${e.message}") - Log.w(TAG, "FIAT [RATE MISS ] $currency — no fallback available, suppressing fiat display") + Timber.w("FIAT [RATE ERROR ] $currency — ${e.message}") + Timber.w("FIAT [RATE MISS ] $currency — no fallback available, suppressing fiat display") return } .also { fresh -> fiatCache.putRate(currency, fresh) } @@ -118,7 +118,7 @@ class FiatViewModel( val currentSats = (balanceVm.balanceState.value as? BalanceState.Success)?.sats ?: return val fiat = satsToFiat(currentSats, rate) - Log.d(TAG, "FIAT [APPLY ] $currentSats sats ÷ $rate sats/$currency = $fiat $currency") + Timber.d("FIAT [APPLY ] $currentSats sats ÷ $rate sats/$currency = $fiat $currency") balanceVm.applyFiat(fiat, currency) } @@ -131,7 +131,7 @@ class FiatViewModel( private suspend fun fetchAndCacheCurrencies(): List { return runCatching { repo.getSupportedCurrencies() } .getOrElse { e -> - Log.w(TAG, "FIAT [CURRENCIES] network fetch failed — ${e.message}") + Timber.w("FIAT [CURRENCIES] network fetch failed — ${e.message}") emptyList() } .also { list -> fiatCache.putCurrencies(list) } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt index 4304796..733b5e4 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.history -import android.util.Log +import timber.log.Timber import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope @@ -161,10 +161,10 @@ class HistoryViewModel( ) { _, f -> f } .collect { f -> if (!f.needsRoomQuery()) { - Log.d(TAG, "FILTER [IN-MEMORY] filter=default → using cached state") + Timber.d("FILTER [IN-MEMORY] filter=default → using cached state") _roomResults.value = null } else { - Log.d(TAG, "FILTER [ROOM ] q=\"${f.searchQuery}\" " + + Timber.d("FILTER [ROOM ] q=\"${f.searchQuery}\" " + "statuses=${f.statuses} dir=${f.direction} types=${f.types} " + "amt=${f.minAmountSat}–${f.maxAmountSat} " + "date=${f.minCreatedAt}–${f.maxCreatedAt}") @@ -174,7 +174,7 @@ class HistoryViewModel( } viewModelScope.launch { WalletNotificationService.paymentEvents.collect { event -> - Log.d(TAG, "PAYMENTS [WS EVENT ] incoming event ${event.paymentHash} — refreshing list") + Timber.d("PAYMENTS [WS EVENT ] incoming event ${event.paymentHash} — refreshing list") syncManager.refresh() } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentDetailDelegate.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentDetailDelegate.kt index b113325..021cd13 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentDetailDelegate.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentDetailDelegate.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.history -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.PaymentRecord import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository import com.bitcointxoko.gudariwallet.data.WalletRepository @@ -22,15 +22,15 @@ class PaymentDetailDelegate( fun load(checkingId: String, record: PaymentRecord? = null) { paymentCache.getCachedDetail(checkingId)?.let { - Log.d(TAG, "DETAIL [HIT] $checkingId — instant from cache") + Timber.d("DETAIL [HIT] $checkingId — instant from cache") _state.value = DetailState.Success(it) return } _state.value = if (record != null) { - Log.d(TAG, "DETAIL [PAR] $checkingId — partial from PaymentRecord") + Timber.d("DETAIL [PAR] $checkingId — partial from PaymentRecord") DetailState.Partial(record) } else { - Log.d(TAG, "DETAIL [LOD] $checkingId — no record, showing spinner") + Timber.d("DETAIL [LOD] $checkingId — no record, showing spinner") DetailState.Loading } scope.launch { @@ -38,17 +38,17 @@ class PaymentDetailDelegate( if (fromDb != null) { paymentCache.saveDetail(checkingId, fromDb) _state.value = DetailState.Success(fromDb) - Log.d(TAG, "DETAIL [DB] $checkingId — served from Room") + Timber.d("DETAIL [DB] $checkingId — served from Room") return@launch } runCatching { repo.getPaymentDetail(checkingId) } .onSuccess { detail -> - Log.d(TAG, "DETAIL [NET] $checkingId — fetched from network") + Timber.d("DETAIL [NET] $checkingId — fetched from network") paymentCache.saveDetail(checkingId, detail) _state.value = DetailState.Success(detail) } .onFailure { e -> - Log.w(TAG, "DETAIL [ERR] $checkingId — ${e.message}") + Timber.w("DETAIL [ERR] $checkingId — ${e.message}") if (_state.value !is DetailState.Partial) { _state.value = DetailState.Error(e.message ?: "Failed to load payment") } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentEnricher.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentEnricher.kt index 52234cc..9ade9e0 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentEnricher.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentEnricher.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.history -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.PaymentRecord import com.bitcointxoko.gudariwallet.data.NodeAliasRepository import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository @@ -21,7 +21,7 @@ class PaymentEnricher( val candidates = payments.filter { it.isOutgoing } if (candidates.isEmpty()) return val toEnrich = candidates.filter { !it.bolt11.isNullOrBlank() && it.pubkey == null } - Log.d(TAG, "ENRICH [START ] ${toEnrich.size} payments to enrich " + + Timber.d("ENRICH [START ] ${toEnrich.size} payments to enrich " + "(${candidates.size - toEnrich.size} already enriched or no bolt11)") if (toEnrich.isEmpty()) return diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentSyncManager.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentSyncManager.kt index 5b76af6..090d85d 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentSyncManager.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentSyncManager.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.history -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.PaymentRecord import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository import com.bitcointxoko.gudariwallet.data.WalletRepository @@ -37,7 +37,7 @@ class PaymentSyncManager( limit = WalletConstants.PAYMENTS_PAGE_SIZE ) if (cached.isNotEmpty()) { - Log.d(TAG, "PAYMENTS [INIT ] Showing ${cached.size} cached payments immediately") + Timber.d("PAYMENTS [INIT ] Showing ${cached.size} cached payments immediately") currentOffset = cached.size mutableState.value = HistoryState.Success( payments = cached.distinctBy { it.paymentHash }, @@ -45,7 +45,7 @@ class PaymentSyncManager( isRefreshing = true ) } else { - Log.d(TAG, "PAYMENTS [INIT ] No cache — cold load") + Timber.d("PAYMENTS [INIT ] No cache — cold load") } syncNewPayments() } @@ -78,7 +78,7 @@ class PaymentSyncManager( limit = WalletConstants.PAYMENTS_PAGE_SIZE ) if (fromDb.size == WalletConstants.PAYMENTS_PAGE_SIZE) { - Log.d(TAG, "PAYMENTS [MORE DB ] offset=$currentOffset — served ${fromDb.size} rows from Room") + Timber.d("PAYMENTS [MORE DB ] offset=$currentOffset — served ${fromDb.size} rows from Room") val deduplicated = (current.payments + fromDb).distinctBy { it.paymentHash } currentOffset += fromDb.size val newState = HistoryState.Success( @@ -91,7 +91,7 @@ class PaymentSyncManager( return@launch } - Log.d(TAG, "PAYMENTS [MORE NET ] offset=$currentOffset — DB has ${fromDb.size}, fetching from network") + Timber.d("PAYMENTS [MORE NET ] offset=$currentOffset — DB has ${fromDb.size}, fetching from network") runCatching { repo.getPayments(offset = currentOffset, limit = WalletConstants.PAYMENTS_PAGE_SIZE) }.onSuccess { newPage -> @@ -106,7 +106,7 @@ class PaymentSyncManager( mutableState.value = newState onNewPayments(newState.payments) }.onFailure { e -> - Log.w(TAG, "PAYMENTS [MORE ERR ] ${e.message}") + Timber.w("PAYMENTS [MORE ERR ] ${e.message}") mutableState.value = current.copy(isRefreshing = false) } } @@ -119,11 +119,11 @@ class PaymentSyncManager( var totalNew = 0 var pagesChecked = 0 - Log.d(TAG, "PAYMENTS [SYNC START] scanning for new payments (cap=$maxPayments)") + Timber.d("PAYMENTS [SYNC START] scanning for new payments (cap=$maxPayments)") while (true) { if (totalNew >= maxPayments) { - Log.d(TAG, "PAYMENTS [SYNC CAP ] reached $maxPayments payment limit — stopping early") + Timber.d("PAYMENTS [SYNC CAP ] reached $maxPayments payment limit — stopping early") break } @@ -132,10 +132,10 @@ class PaymentSyncManager( }.getOrElse { e -> val current = mutableState.value if (current is HistoryState.Success) { - Log.w(TAG, "PAYMENTS [SYNC ERR ] ${e.message} — keeping cached list") + Timber.w("PAYMENTS [SYNC ERR ] ${e.message} — keeping cached list") mutableState.value = current.copy(isRefreshing = false) } else { - Log.w(TAG, "PAYMENTS [SYNC ERR ] ${e.message} — no cache to fall back on") + Timber.w("PAYMENTS [SYNC ERR ] ${e.message} — no cache to fall back on") mutableState.value = HistoryState.Error(e.message ?: "Failed to load payments") } return @@ -152,14 +152,14 @@ class PaymentSyncManager( paymentCache.mergePayments(toMerge) totalNew += toMerge.size val withTimestamp = toMerge.count { it.time.toLongOrNull() != null } - Log.d(TAG, "PAYMENTS [SYNC PAGE ] page $pagesChecked — ${toMerge.size} new payments merged ($withTimestamp with numeric timestamp)") + Timber.d("PAYMENTS [SYNC PAGE ] page $pagesChecked — ${toMerge.size} new payments merged ($withTimestamp with numeric timestamp)") } val reachedOverlap = overlapIndex != -1 val reachedEnd = page.size < WalletConstants.PAYMENTS_PAGE_SIZE if (reachedOverlap || reachedEnd) { - Log.d(TAG, "PAYMENTS [SYNC DONE ] $totalNew new payment(s) across $pagesChecked page(s) — " + + Timber.d("PAYMENTS [SYNC DONE ] $totalNew new payment(s) across $pagesChecked page(s) — " + if (reachedOverlap) "stopped at known record" else "reached end of history") break } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/nfc/NdefHceService.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/nfc/NdefHceService.kt index 0aeced1..8030338 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/nfc/NdefHceService.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/nfc/NdefHceService.kt @@ -2,7 +2,7 @@ package com.bitcointxoko.gudariwallet.ui.nfc import android.nfc.cardemulation.HostApduService import android.os.Bundle -import android.util.Log +import timber.log.Timber /** * HCE service that emulates an NFC Forum Type 4 Tag containing an NDEF @@ -71,7 +71,7 @@ class NdefHceService : HostApduService() { private var selectedFileId: ByteArray? = null override fun processCommandApdu(apdu: ByteArray, extras: Bundle?): ByteArray { - Log.d(TAG, "APDU IN: ${apdu.toHex()}") + Timber.d("APDU IN: ${apdu.toHex()}") if (apdu.size < 4) return SW_UNKNOWN @@ -85,7 +85,7 @@ class NdefHceService : HostApduService() { ins == 0xB0.toByte() -> handleReadBinary(apdu) else -> SW_UNKNOWN - }.also { Log.d(TAG, "APDU OUT: ${it.toHex()}") } + }.also { Timber.d("APDU OUT: ${it.toHex()}") } } private fun handleSelect(apdu: ByteArray): ByteArray { @@ -97,21 +97,21 @@ class NdefHceService : HostApduService() { return when { data.contentEquals(NDEF_AID) -> { selectedFileId = null // app selected, no file yet - Log.d(TAG, "SELECT NDEF Application OK") + Timber.d("SELECT NDEF Application OK") SW_OK } data.contentEquals(CC_FILE_ID) -> { selectedFileId = CC_FILE_ID - Log.d(TAG, "SELECT CC file OK") + Timber.d("SELECT CC file OK") SW_OK } data.contentEquals(NDEF_FILE_ID) -> { selectedFileId = NDEF_FILE_ID - Log.d(TAG, "SELECT NDEF file OK") + Timber.d("SELECT NDEF file OK") SW_OK } else -> { - Log.w(TAG, "SELECT unknown file: ${data.toHex()}") + Timber.w("SELECT unknown file: ${data.toHex()}") SW_FILE_NOT_FOUND } } @@ -127,7 +127,7 @@ class NdefHceService : HostApduService() { CC_FILE selectedFileId != null && selectedFileId!!.contentEquals(NDEF_FILE_ID) -> { ndefFileBytes ?: run { - Log.w(TAG, "READ BINARY: no NDEF data loaded") + Timber.w("READ BINARY: no NDEF data loaded") return SW_FILE_NOT_FOUND } } @@ -141,7 +141,7 @@ class NdefHceService : HostApduService() { } override fun onDeactivated(reason: Int) { - Log.d(TAG, "HCE deactivated, reason=$reason") + Timber.d("HCE deactivated, reason=$reason") selectedFileId = null } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/nfc/NfcViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/nfc/NfcViewModel.kt index ead6782..28e8866 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/nfc/NfcViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/nfc/NfcViewModel.kt @@ -8,7 +8,7 @@ import android.nfc.Tag import android.nfc.tech.IsoDep import android.nfc.tech.Ndef import android.os.Build -import android.util.Log +import timber.log.Timber import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bitcointxoko.gudariwallet.ui.NfcOfferState @@ -33,7 +33,7 @@ class NfcViewModel : ViewModel() { * Builds the NDEF file bytes and loads them into NdefHceService. */ fun startEmulating(uri: String) { - Log.d(TAG, "startEmulating: $uri") + Timber.d("startEmulating: $uri") NdefHceService.ndefFileBytes = buildNdefFileBytes(uri) _isNfcEmulating.value = true } @@ -42,7 +42,7 @@ class NfcViewModel : ViewModel() { * Stop emulating — clears the NDEF payload from the HCE service. */ fun stopEmulating() { - Log.d(TAG, "stopEmulating") + Timber.d("stopEmulating") NdefHceService.ndefFileBytes = null _isNfcEmulating.value = false } @@ -63,7 +63,7 @@ class NfcViewModel : ViewModel() { * recognisable payment URI. */ fun onTagDiscovered(tag: Tag) { - Log.d(TAG, "onTagDiscovered: ${tag.id.joinToString("") { "%02X".format(it) }}") + Timber.d("onTagDiscovered: ${tag.id.joinToString("") { "%02X".format(it) }}") viewModelScope.launch(Dispatchers.IO) { // ← move to IO thread readNdefText(tag) } @@ -87,7 +87,7 @@ class NfcViewModel : ViewModel() { val message = rawMessages[0] as NdefMessage val uri = extractUri(message) if (uri != null) { - Log.d(TAG, "onNfcIntent (cold-start): uri=$uri") + Timber.d("onNfcIntent (cold-start): uri=$uri") _nfcOfferState.value = NfcOfferState.Detected(uri, SendInputDetector.detect(uri)) return } @@ -127,14 +127,14 @@ class NfcViewModel : ViewModel() { extractUri(msg) } } catch (e: Exception) { - Log.e(TAG, "readNdefText error: ${e.message}") + Timber.e("readNdefText error: ${e.message}") null } if (uri != null) { - Log.d(TAG, "readNdefText: uri=$uri") + Timber.d("readNdefText: uri=$uri") _nfcOfferState.value = NfcOfferState.Detected(uri, SendInputDetector.detect(uri)) } else { - Log.w(TAG, "readNdefText: no URI found in tag") + Timber.w("readNdefText: no URI found in tag") } } /** diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/receive/ReceiveViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/receive/ReceiveViewModel.kt index 909c034..0bb04d9 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/receive/ReceiveViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/receive/ReceiveViewModel.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.receive -import android.util.Log +import timber.log.Timber import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bitcointxoko.gudariwallet.data.WalletRepository @@ -32,7 +32,7 @@ class ReceiveViewModel( fun onIncomingPayment(event: IncomingPaymentEvent) { val rs = _receiveState.value if (rs is ReceiveState.InvoiceReady) { - Log.d(TAG, "BALANCE [WS RECEIVE] Marking invoice ${rs.paymentHash.take(8)}… as received") + Timber.d("BALANCE [WS RECEIVE] Marking invoice ${rs.paymentHash.take(8)}… as received") _receiveState.value = ReceiveState.PaymentReceived( amountSats = event.amountSats, memo = event.memo, @@ -72,7 +72,7 @@ class ReceiveViewModel( ) } .onFailure { e -> - Log.e(TAG, "RECEIVE [CREATE INVOICE ERROR] ${e.message}") + Timber.e("RECEIVE [CREATE INVOICE ERROR] ${e.message}") _receiveState.value = ReceiveState.Error( parseApiError(e, failedToCreateInvoice) // ← localised ) @@ -100,7 +100,7 @@ class ReceiveViewModel( lnurl = lnurl ) _navigateToReceive.tryEmit(Unit) - Log.d(TAG, "LNURL-WITHDRAW [NAV EVENT] min=$minSats max=$maxSats sats — navigating to Receive") + Timber.d("LNURL-WITHDRAW [NAV EVENT] min=$minSats max=$maxSats sats — navigating to Receive") } /** @@ -124,19 +124,19 @@ class ReceiveViewModel( if (invoiceResult.isFailure) { val e = invoiceResult.exceptionOrNull() ?: Exception(failedToCreateInvoice) - Log.e(TAG, "LNURL-WITHDRAW [INVOICE ERROR] ${e.message}") + Timber.e("LNURL-WITHDRAW [INVOICE ERROR] ${e.message}") _receiveState.value = ReceiveState.Error( parseApiError(e, failedToCreateInvoice) // ← localised ) return@launch } val invoice = invoiceResult.getOrThrow() - Log.d(TAG, "LNURL-WITHDRAW [INVOICE] ${invoice.paymentHash} — submitting to callback") + Timber.d("LNURL-WITHDRAW [INVOICE] ${invoice.paymentHash} — submitting to callback") runCatching { repo.executeWithdraw(callback, k1, invoice.bolt11) } .onSuccess { response -> if (response.status == "OK") { - Log.d(TAG, "LNURL-WITHDRAW [CALLBACK OK] waiting for push payment") + Timber.d("LNURL-WITHDRAW [CALLBACK OK] waiting for push payment") _receiveState.value = ReceiveState.InvoiceReady( bolt11 = invoice.bolt11, paymentHash = invoice.paymentHash, @@ -147,12 +147,12 @@ class ReceiveViewModel( } else { val reason = response.reason?.ifBlank { null } ?: withdrawRejected // ← localised - Log.w(TAG, "LNURL-WITHDRAW [CALLBACK ERR] $reason") + Timber.w("LNURL-WITHDRAW [CALLBACK ERR] $reason") _receiveState.value = ReceiveState.Error(reason) } } .onFailure { e -> - Log.w(TAG, "LNURL-WITHDRAW [CALLBACK FAIL] ${e.message}") + Timber.w("LNURL-WITHDRAW [CALLBACK FAIL] ${e.message}") _receiveState.value = ReceiveState.Error( parseApiError(e, withdrawFailed) // ← localised ) diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/LnurlPayer.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/LnurlPayer.kt index 389b66a..9d9d1b0 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/LnurlPayer.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/LnurlPayer.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.send -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.LnurlScanResponse import com.bitcointxoko.gudariwallet.data.LnurlCacheRepository import com.bitcointxoko.gudariwallet.data.WalletRepository @@ -28,12 +28,12 @@ class LnurlPayer( // ── 2. Terminal error — don't retry val initialError = initial.exceptionOrNull() ?: Exception("Payment failed") if (isTerminalPayError(initialError)) { - Log.w(TAG, "LNURL [PAY TERMINAL] ${initialError.message}") + Timber.w("LNURL [PAY TERMINAL] ${initialError.message}") return Result.failure(initialError) } // ── 3. Invalidate cache and rescan - Log.w(TAG, "LNURL [PAY RETRY] invalidating cache and rescanning") + Timber.w("LNURL [PAY RETRY] invalidating cache and rescanning") cache.invalidate(lnurl) val rescanResult = scanner.rescan(lnurl) @@ -44,7 +44,7 @@ class LnurlPayer( val protocolError = lnurlProtocolError(freshRaw) if (protocolError != null) { - Log.w(TAG, "LNURL [RESCAN PROTO] $protocolError") + Timber.w("LNURL [RESCAN PROTO] $protocolError") return Result.failure(Exception(protocolError)) } cache.put(lnurl, freshRaw) @@ -53,7 +53,7 @@ class LnurlPayer( val retry = tryWithFallback(freshRaw, lnurl, amountMsat, comment, "RETRY") if (retry.isSuccess) return retry - Log.e(TAG, "LNURL [PAY FINAL ERR] ${retry.exceptionOrNull()?.message}") + Timber.e("LNURL [PAY FINAL ERR] ${retry.exceptionOrNull()?.message}") return Result.failure(retry.exceptionOrNull() ?: Exception("Payment failed")) } @@ -66,17 +66,17 @@ class LnurlPayer( ): Result { val direct = runCatching { repo.payLnurlDirect(rawRes, amountMsat, comment) } if (direct.isSuccess) { - Log.d(TAG, "LNURL [PAY $label DIRECT] success") + Timber.d("LNURL [PAY $label DIRECT] success") return Result.success(direct.getOrThrow().paymentHash) } - Log.w(TAG, "LNURL [PAY $label DIRECT FAIL] ${direct.exceptionOrNull()?.message} — trying proxy") + Timber.w("LNURL [PAY $label DIRECT FAIL] ${direct.exceptionOrNull()?.message} — trying proxy") val proxy = runCatching { repo.payLnurl(rawRes, lnurl, amountMsat, comment) } if (proxy.isSuccess) { - Log.d(TAG, "LNURL [PAY $label PROXY] success") + Timber.d("LNURL [PAY $label PROXY] success") return Result.success(proxy.getOrThrow().paymentHash) } - Log.w(TAG, "LNURL [PAY $label PROXY FAIL] ${proxy.exceptionOrNull()?.message}") + Timber.w("LNURL [PAY $label PROXY FAIL] ${proxy.exceptionOrNull()?.message}") return Result.failure(proxy.exceptionOrNull() ?: Exception("Payment failed")) } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/LnurlScanner.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/LnurlScanner.kt index d31bfe9..f53d4b0 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/LnurlScanner.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/LnurlScanner.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.send -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.LnurlScanResponse import com.bitcointxoko.gudariwallet.data.LnurlCacheRepository import com.bitcointxoko.gudariwallet.data.WalletRepository @@ -18,25 +18,25 @@ class LnurlScanner( // 1. Cache hit val cached = lnurlCache.get(lnurl) if (cached != null) { - Log.d(TAG, "LNURL [SCAN CACHE ] $lnurl") + Timber.d("LNURL [SCAN CACHE ] $lnurl") return Result.success(LnurlScanResult(cached, fromCache = true)) } // 2. Direct val direct = runCatching { repo.scanLnurlDirect(lnurl) } if (direct.isSuccess) { - Log.d(TAG, "LNURL [SCAN DIRECT] tag=${direct.getOrThrow().tag}") + Timber.d("LNURL [SCAN DIRECT] tag=${direct.getOrThrow().tag}") return Result.success(LnurlScanResult(direct.getOrThrow().rawRes, fromCache = false)) } - Log.w(TAG, "LNURL [SCAN DIRECT FAIL] ${direct.exceptionOrNull()?.message} — trying proxy") + Timber.w("LNURL [SCAN DIRECT FAIL] ${direct.exceptionOrNull()?.message} — trying proxy") // 3. Proxy fallback val proxy = runCatching { repo.scanLnurl(lnurl) } if (proxy.isSuccess) { - Log.d(TAG, "LNURL [SCAN PROXY ] tag=${proxy.getOrThrow().tag}") + Timber.d("LNURL [SCAN PROXY ] tag=${proxy.getOrThrow().tag}") return Result.success(LnurlScanResult(proxy.getOrThrow().rawRes, fromCache = false)) } - Log.e(TAG, "LNURL [SCAN PROXY FAIL] ${proxy.exceptionOrNull()?.message}") + Timber.e("LNURL [SCAN PROXY FAIL] ${proxy.exceptionOrNull()?.message}") return Result.failure(proxy.exceptionOrNull() ?: Exception("Scan failed")) } @@ -44,15 +44,15 @@ class LnurlScanner( suspend fun rescan(lnurl: String): Result { val direct = runCatching { repo.scanLnurlDirect(lnurl) } if (direct.isSuccess) { - Log.d(TAG, "LNURL [RESCAN DIRECT] success") + Timber.d("LNURL [RESCAN DIRECT] success") return Result.success(direct.getOrThrow().rawRes) } - Log.w(TAG, "LNURL [RESCAN DIRECT FAIL] ${direct.exceptionOrNull()?.message} — trying proxy") + Timber.w("LNURL [RESCAN DIRECT FAIL] ${direct.exceptionOrNull()?.message} — trying proxy") val proxy = runCatching { repo.scanLnurl(lnurl) } if (proxy.isSuccess) return Result.success(proxy.getOrThrow().rawRes) - Log.e(TAG, "LNURL [RESCAN ERR] ${proxy.exceptionOrNull()?.message}") + Timber.e("LNURL [RESCAN ERR] ${proxy.exceptionOrNull()?.message}") return Result.failure(proxy.exceptionOrNull() ?: Exception("Rescan failed")) } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/PaymentPoller.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/PaymentPoller.kt index 1d0dc7d..44a2eb0 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/PaymentPoller.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/PaymentPoller.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.send -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.data.WalletRepository import kotlinx.coroutines.delay @@ -25,21 +25,21 @@ class PaymentPoller(private val repo: WalletRepository) { when { detail?.paid == true -> { - Log.d(TAG, "HOLD INVOICE [POLL $attempt] settled — $paymentHash") + Timber.d("HOLD INVOICE [POLL $attempt] settled — $paymentHash") return PollResult.Settled(detail.details?.feeSat) } detail?.details?.status == "pending" || detail?.details?.pending == true -> { - Log.d(TAG, "HOLD INVOICE [POLL $attempt] still pending — $paymentHash") + Timber.d("HOLD INVOICE [POLL $attempt] still pending — $paymentHash") // continue } else -> { - Log.w(TAG, "HOLD INVOICE [POLL $attempt] payment failed — $paymentHash") + Timber.w("HOLD INVOICE [POLL $attempt] payment failed — $paymentHash") return PollResult.Failed } } } - Log.w(TAG, "HOLD INVOICE [POLL TIMEOUT] $paymentHash") + Timber.w("HOLD INVOICE [POLL TIMEOUT] $paymentHash") return PollResult.TimedOut } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendViewModel.kt index fc327f1..4166be7 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/send/SendViewModel.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.ui.send -import android.util.Log +import timber.log.Timber import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bitcointxoko.gudariwallet.api.LnurlScanResponse @@ -78,7 +78,7 @@ class SendViewModel( fun scan(rawInput: String, strings: SendStrings) { val normalized = SendInputDetector.normalize(rawInput) val type = SendInputDetector.detect(rawInput) - Log.d(TAG, "SCAN [DETECT] input='$rawInput' → type=$type") + Timber.d("SCAN [DETECT] input='$rawInput' → type=$type") when (type) { SendInputType.Unknown -> { @@ -100,11 +100,11 @@ class SendViewModel( viewModelScope.launch { val decoded = _sendState.value as? SendState.Bolt11Decoded if (decoded == null) { - Log.w(TAG, "SEND [PAY BOLT11] called in unexpected state: ${_sendState.value::class.simpleName}") + Timber.w("SEND [PAY BOLT11] called in unexpected state: ${_sendState.value::class.simpleName}") return@launch } _sendState.value = SendState.Paying - Log.d(TAG, "SEND [PAY BOLT11 START] Sending invoice to server") + Timber.d("SEND [PAY BOLT11 START] Sending invoice to server") runCatching { repo.payBolt11(bolt11) } .onSuccess { response -> handlePaymentResult( @@ -118,7 +118,7 @@ class SendViewModel( ) } .onFailure { e -> - Log.e(TAG, "SEND [PAY BOLT11 ERROR] ${e.message}") + Timber.e("SEND [PAY BOLT11 ERROR] ${e.message}") _sendState.value = SendState.Error( parseApiError(e, strings.paymentFailed) // ← "Payment failed" ) @@ -238,7 +238,7 @@ class SendViewModel( val bolt11 = runCatching { repo.fetchLnurlCallbackInvoice(rawRes, amountMsat, comment) }.getOrElse { e -> - Log.e(TAG, "LNURL [FETCH INVOICE ERR] ${e.message}") + Timber.e("LNURL [FETCH INVOICE ERR] ${e.message}") _sendState.value = SendState.Error( parseApiError(e, strings.couldNotFetchInvoice) // ← "Could not fetch invoice from recipient" ) @@ -248,7 +248,7 @@ class SendViewModel( val decoded = runCatching { repo.decodeBolt11(bolt11) }.getOrElse { e -> - Log.e(TAG, "LNURL [DECODE ERR] ${e.message}") + Timber.e("LNURL [DECODE ERR] ${e.message}") _sendState.value = SendState.Error( parseApiError(e, strings.couldNotDecodeInvoice) // ← "Could not decode invoice" ) @@ -260,7 +260,7 @@ class SendViewModel( hints = decoded.routeHints ) - Log.d(TAG, "LNURL [INVOICE READY] amountMsat=$amountMsat " + + Timber.d("LNURL [INVOICE READY] amountMsat=$amountMsat " + "routeRisk=${routeRisk?.level} hints=${decoded.routeHints.size}") _sendState.value = SendState.LnurlInvoiceReady( @@ -315,7 +315,7 @@ class SendViewModel( ) return@launch } - Log.w(TAG, "LNURL [PAY INVOICE DIRECT FAIL] " + + Timber.w("LNURL [PAY INVOICE DIRECT FAIL] " + "${directResult.exceptionOrNull()?.message} — falling back to payLnurl") executeLnurlPayment(state.rawRes, state.lnurl, state.amountMsat, state.comment, strings) } @@ -347,12 +347,12 @@ class SendViewModel( return } val httpsUrl = "https://" + rawInput.trim().substringAfter("://") - Log.d(TAG, "LUD17 [FETCH] $httpsUrl") + Timber.d("LUD17 [FETCH] $httpsUrl") _sendState.value = SendState.Scanning viewModelScope.launch { runCatching { repo.fetchLnurlDirect(httpsUrl) } .onSuccess { raw -> - Log.d(TAG, "LUD17 [RESPONSE] tag=${raw.tag}") + Timber.d("LUD17 [RESPONSE] tag=${raw.tag}") when (raw.tag) { "withdrawRequest" -> {emitWithdrawScanned(raw, httpsUrl)} "payRequest" -> { @@ -365,7 +365,7 @@ class SendViewModel( } } .onFailure { e -> - Log.e(TAG, "LUD17 [ERROR] ${e.message}") + Timber.e("LUD17 [ERROR] ${e.message}") _sendState.value = SendState.Error( parseApiError(e, strings.couldNotResolveAddress) // ← "Could not resolve address" ) @@ -410,7 +410,7 @@ class SendViewModel( ) } .onFailure { e -> - Log.e(TAG, "SEND [DECODE ERROR] ${e.message}") + Timber.e("SEND [DECODE ERROR] ${e.message}") _sendState.value = SendState.Error( parseApiError(e, strings.couldNotDecodeInvoice) // ← "Could not decode invoice" ) @@ -428,7 +428,7 @@ class SendViewModel( val protocolError = lnurlProtocolError(raw) if (protocolError != null) { - Log.w(TAG, "LNURL [SCAN PROTO ERR] $protocolError") + Timber.w("LNURL [SCAN PROTO ERR] $protocolError") _sendState.value = SendState.Error(protocolError) return@launch } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/util/BiometricHelper.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/util/BiometricHelper.kt index aaf742d..9de59f8 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/util/BiometricHelper.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/util/BiometricHelper.kt @@ -2,7 +2,7 @@ package com.bitcointxoko.gudariwallet.util import android.content.Context import android.content.ContextWrapper -import android.util.Log +import timber.log.Timber import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat @@ -61,7 +61,7 @@ object BiometricHelper { if (code != BiometricPrompt.ERROR_USER_CANCELED && code != BiometricPrompt.ERROR_NEGATIVE_BUTTON ) { - Log.e(TAG, "Biometric error $code: $msg") + Timber.e("Biometric error $code: $msg") onError(code, msg) } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/util/Bolt11Decoder.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/util/Bolt11Decoder.kt index 1245580..cd71f37 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/util/Bolt11Decoder.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/util/Bolt11Decoder.kt @@ -1,6 +1,6 @@ package com.bitcointxoko.gudariwallet.util -import android.util.Log +import timber.log.Timber import com.bitcointxoko.gudariwallet.api.RouteHintHop import com.bitcointxoko.gudariwallet.data.DecodedBolt11 import fr.acinq.secp256k1.Secp256k1 @@ -59,7 +59,7 @@ object Bolt11Decoder { // ── 1. Split HRP / data ─────────────────────────────────────────────── val sepIdx = lower.lastIndexOf('1') if (sepIdx < 1) { - Log.w(TAG, "No bech32 separator found") + Timber.w("No bech32 separator found") return null } @@ -67,21 +67,21 @@ object Bolt11Decoder { val dataChars = lower.substring(sepIdx + 1) if (dataChars.length < 6 + SIGNATURE_GROUPS + 7) { - Log.w(TAG, "Data part too short: ${dataChars.length} chars") + Timber.w("Data part too short: ${dataChars.length} chars") return null } // ── 2. Parse HRP ────────────────────────────────────────────────────── val prefix = KNOWN_PREFIXES.firstOrNull { hrp.startsWith(it) } ?: run { - Log.w(TAG, "Unknown HRP prefix: $hrp") + Timber.w("Unknown HRP prefix: $hrp") return null } val amountStr = hrp.removePrefix(prefix) val amountMsat = parseAmountMsat(amountStr) ?: run { - Log.w(TAG, "Could not parse amount from HRP: '$amountStr'") + Timber.w("Could not parse amount from HRP: '$amountStr'") return null } @@ -98,7 +98,7 @@ object Bolt11Decoder { val groups = payload.map { c -> val idx = CHARSET.indexOf(c) if (idx < 0) { - Log.w(TAG, "Invalid bech32 character: '$c'") + Timber.w("Invalid bech32 character: '$c'") return null } idx @@ -108,7 +108,7 @@ object Bolt11Decoder { val sigGroups = groups.takeLast(SIGNATURE_GROUPS) val signedGroups = groups.dropLast(SIGNATURE_GROUPS) if (signedGroups.size < 7) { - Log.w(TAG, "Data part too short for timestamp") + Timber.w("Data part too short for timestamp") return null } @@ -139,7 +139,7 @@ object Bolt11Decoder { pos += 3 if (pos + dataLen > taggedGroups.size) { - Log.w(TAG, "Tagged field overflows data (type=$type, len=$dataLen)") + Timber.w("Tagged field overflows data (type=$type, len=$dataLen)") break } @@ -152,7 +152,7 @@ object Bolt11Decoder { TAG_PAYMENT_HASH -> { // Fixed: 52 groups × 5 = 260 bits → 32 bytes + 4 padding bits if (fieldGroups.size != 52) { - Log.w(TAG, "Malformed 'p' field (len=${fieldGroups.size}, expected 52)") + Timber.w("Malformed 'p' field (len=${fieldGroups.size}, expected 52)") continue } val bytes = convertBits(fieldGroups, 5, 8, false) ?: continue @@ -166,7 +166,7 @@ object Bolt11Decoder { TAG_PAYEE_PUBKEY -> { if (fieldGroups.size != 53) { - Log.w(TAG, "Malformed 'n' field (len=${fieldGroups.size}, expected 53)") + Timber.w("Malformed 'n' field (len=${fieldGroups.size}, expected 53)") continue } val bytes = convertBits(fieldGroups, 5, 8, false) ?: continue @@ -185,7 +185,7 @@ object Bolt11Decoder { TAG_FALLBACK_ADDR -> { fallbackAddress = decodeFallbackAddress(fieldGroups, addrHrp) if (fallbackAddress == null) { - Log.w(TAG, "Could not decode fallback address") + Timber.w("Could not decode fallback address") } } @@ -217,18 +217,18 @@ object Bolt11Decoder { // ── 6. Recover payee from signature if 'n' field was absent ─────────── if (payee == null) { payee = recoverPayee(hrp, signedGroups, sigGroups) - if (payee != null) Log.d(TAG, "Recovered payee pubkey from signature") + if (payee != null) Timber.d("Recovered payee pubkey from signature") } // ── NEW: paymentHash is required — fail loudly if absent ───────────── if (paymentHash == null) { - Log.w(TAG, "Invoice missing required 'p' (payment hash) field") + Timber.w("Invoice missing required 'p' (payment hash) field") return null } val routeHints = routeHintRoutes.flatten() - Log.d(TAG, "Decoded locally — amount: ${amountSats}sat, memo: \"$memo\", " + + Timber.d("Decoded locally — amount: ${amountSats}sat, memo: \"$memo\", " + "payee: $payee, routes: ${routeHintRoutes.size}, hops: ${routeHints.size}, " + "createdAt: $createdAt, expiresAt: $expiresAt, " + "fallback: $fallbackAddress, paymentHash: $paymentHash") @@ -287,7 +287,7 @@ object Bolt11Decoder { encodeBech32Address(addrHrp, witnessVersion = 0, dataGroups) } else -> { - Log.w(TAG, "Unknown fallback address version: $version") + Timber.w("Unknown fallback address version: $version") null } } @@ -372,20 +372,20 @@ object Bolt11Decoder { val sigBytes = convertBits(sigGroups, 5, 8, false)?.toByteArray() ?: return null if (sigBytes.size != 65) { - Log.w(TAG, "Unexpected signature length: ${sigBytes.size}") + Timber.w("Unexpected signature length: ${sigBytes.size}") return null } val compactSig = sigBytes.copyOfRange(0, 64) val recid = sigBytes[64].toInt() if (recid !in 0..3) { - Log.w(TAG, "Invalid recovery id: $recid") + Timber.w("Invalid recovery id: $recid") return null } val uncompressed = Secp256k1.ecdsaRecover(compactSig, message, recid) Secp256k1.pubKeyCompress(uncompressed).toHex() }.getOrElse { - Log.w(TAG, "Payee recovery failed: ${it.message}") + Timber.w("Payee recovery failed: ${it.message}") null } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 85bfb4a..172c1b8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,7 @@ work = "2.9.1" benchmark = "1.4.1" runner = "1.5.2" androidx-test-ext = "1.2.1" +timber = "5.0.1" [libraries] androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } @@ -81,6 +82,7 @@ androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx" androidx-benchmark-junit4 = { group = "androidx.benchmark", name = "benchmark-junit4", version.ref = "benchmark" } androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext" } +timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }