diff --git a/app/build.gradle.kts b/app/build.gradle.kts index aab040b..cd1e797 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,6 +27,14 @@ android { minorApiLevel = 1 } } + splits { + abi { + isEnable = true + reset() // Clear all defaults + include("arm64-v8a") // Specify desired ABIs + isUniversalApk = false + } + } defaultConfig { applicationId = "com.bitcointxoko.gudariwallet" @@ -37,9 +45,6 @@ android { androidResources { localeFilters += setOf("en", "es", "eu") } - - - // testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner" testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "ACTIVITY-MISSING" } @@ -174,7 +179,6 @@ dependencies { //Navigation implementation(libs.androidx.navigation.compose) - implementation(libs.androidx.appcompat) // animations diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 3a7c583..5d141a7 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -6,53 +6,44 @@ # ────────────────────────────────────────────────────────────────── # Google Tink 1.13.0 # ────────────────────────────────────────────────────────────────── --keep class com.google.crypto.tink.proto.** { *; } --keep class com.google.crypto.tink.shaded.protobuf.** { *; } --keep class com.google.crypto.tink.** { *; } +-keepclassmembers class * extends com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite { + ; +} +-keep class com.google.crypto.tink.KeyTemplates { *; } +-keep class com.google.crypto.tink.StreamingAead { *; } +-keep class com.google.crypto.tink.KeysetHandle { *; } +-keep class com.google.crypto.tink.integration.android.AndroidKeysetManager { *; } +-keep class com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder { *; } +-keep class com.google.crypto.tink.streamingaead.StreamingAeadConfig { *; } +-keep class com.google.crypto.tink.streamingaead.AesGcmHkdfStreamingKeyManager { *; } +-keep class com.google.crypto.tink.streamingaead.StreamingAeadWrapper { *; } +-keep class com.google.crypto.tink.subtle.AesGcmHkdfStreaming { *; } -dontwarn com.google.crypto.tink.** # ────────────────────────────────────────────────────────────────── -# Gson — generic signature safety +# kotlinx.serialization +# The compiler plugin generates a $serializer inner class for every +# @Serializable class. R8 must not remove or rename these — they are +# looked up by name at runtime via SerializersModule. # ────────────────────────────────────────────────────────────────── --keepattributes Signature --keepattributes *Annotation* --keep,allowobfuscation,allowshrinking,allowoptimization class com.google.gson.reflect.TypeToken --keep,allowobfuscation,allowshrinking,allowoptimization class * extends com.google.gson.reflect.TypeToken +-keepattributes *Annotation*, InnerClasses +-dontnote kotlinx.serialization.** +-dontwarn kotlinx.serialization.** -# ────────────────────────────────────────────────────────────────── -# Gson — ALL classes Gson instantiates via reflection -# ────────────────────────────────────────────────────────────────── +# Keep generated serializers for all @Serializable classes in your app +-keepclassmembers class com.bitcointxoko.gudariwallet.** { + kotlinx.serialization.KSerializer serializer(...); +} -# Response bodies --keep class com.bitcointxoko.gudariwallet.api.WalletResponse { *; } --keep class com.bitcointxoko.gudariwallet.api.PaymentStatusResponse { *; } --keep class com.bitcointxoko.gudariwallet.api.PaymentDetails { *; } --keep class com.bitcointxoko.gudariwallet.api.PaymentDetailResponse { *; } --keep class com.bitcointxoko.gudariwallet.api.WithdrawCallbackResponse { *; } --keep class com.bitcointxoko.gudariwallet.api.FiatRateResponse { *; } --keep class com.bitcointxoko.gudariwallet.api.LnurlpLink { *; } +# Keep the $serializer companion objects themselves +-keep class com.bitcointxoko.gudariwallet.**$$serializer { *; } -# NWC models --keep class com.bitcointxoko.gudariwallet.api.NwcGetResponse { *; } --keep class com.bitcointxoko.gudariwallet.api.NwcKey { *; } --keep class com.bitcointxoko.gudariwallet.api.NwcBudget { *; } --keep class com.bitcointxoko.gudariwallet.api.NwcNewBudget { *; } --keep class com.bitcointxoko.gudariwallet.api.NwcRegistrationRequest { *; } - -# Request bodies --keep class com.bitcointxoko.gudariwallet.api.CreateInvoiceRequest { *; } --keep class com.bitcointxoko.gudariwallet.api.PayInvoiceRequest { *; } --keep class com.bitcointxoko.gudariwallet.api.DecodeInvoiceRequest { *; } --keep class com.bitcointxoko.gudariwallet.api.PayLnurlRequest { *; } - -# SuccessAction sealed class + TypeAdapter subclasses --keep class com.bitcointxoko.gudariwallet.api.SuccessAction { *; } --keep class com.bitcointxoko.gudariwallet.api.FlexibleStringAdapter --keep class com.bitcointxoko.gudariwallet.api.SuccessActionAdapter - -# Node alias API responses --keep class com.bitcointxoko.gudariwallet.api.MempoolNodeResponse { *; } --keep class com.bitcointxoko.gudariwallet.api.OneMlNodeResponse { *; } +# Required for sealed class polymorphic serialization +-keepclassmembers @kotlinx.serialization.Serializable class * { + *** Companion; + *** INSTANCE; + kotlinx.serialization.KSerializer serializer(...); +} # ────────────────────────────────────────────────────────────────── # Retrofit 3.0.0 @@ -79,8 +70,32 @@ # ────────────────────────────────────────────────────────────────── # ZXing 4.3.0 +# +# ZXing uses reflection to instantiate Reader subclasses by class name. +# Only the classes your app actually exercises need to be kept. +# A Lightning wallet only needs QR code scanning (QR_CODE format). # ────────────────────────────────────────────────────────────────── --keep class com.google.zxing.** { *; } + +# Core entry points used directly via the API +-keep class com.google.zxing.BarcodeFormat { *; } +-keep class com.google.zxing.DecodeHintType { *; } +-keep class com.google.zxing.Result { *; } +-keep class com.google.zxing.ResultMetadataType { *; } +-keep class com.google.zxing.MultiFormatReader { *; } + +# QR code reader — instantiated reflectively by MultiFormatReader +-keep class com.google.zxing.qrcode.QRCodeReader { *; } +-keep class com.google.zxing.qrcode.decoder.** { *; } +-keep class com.google.zxing.qrcode.detector.** { *; } + +# QR code generator +-keep class com.google.zxing.qrcode.QRCodeWriter { *; } + +# Common infrastructure used by all readers +-keep class com.google.zxing.common.** { *; } + +# Suppress warnings for unused format readers (1D barcodes, DataMatrix, etc.) +# that are in the jar but not needed by a wallet app -dontwarn com.google.zxing.** # ────────────────────────────────────────────────────────────────── @@ -93,11 +108,15 @@ # ────────────────────────────────────────────────────────────────── # WorkManager 2.9.1 # ────────────────────────────────────────────────────────────────── --keep class * extends androidx.work.Worker --keep class * extends androidx.work.CoroutineWorker --keep class * extends androidx.work.ListenableWorker { - public (android.content.Context, androidx.work.WorkerParameters); -} +-keep class * extends androidx.work.InputMerger { *; } +-keep class * extends androidx.work.Worker { *; } +-keep class * extends androidx.work.CoroutineWorker { *; } +-keep class * extends androidx.work.ListenableWorker { *; } +-keep class * extends androidx.work.WorkerFactory { *; } +# Keep specific classes explicitly as a safety net +-keep class com.bitcointxoko.gudariwallet.sync.HistoricalSyncWorker { *; } +-keep class com.bitcointxoko.gudariwallet.sync.HistoricalSyncWorker$Companion { *; } +-keep class com.bitcointxoko.gudariwallet.sync.HistoricalSyncWorkerFactory { *; } # ────────────────────────────────────────────────────────────────── # Kotlin Coroutines 1.11.0 diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 1f6fe45..5681485 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -8,12 +8,17 @@ "variantName": "release", "elements": [ { - "type": "SINGLE", - "filters": [], + "type": "ONE_OF_MANY", + "filters": [ + { + "filterType": "ABI", + "value": "arm64-v8a" + } + ], "attributes": [], "versionCode": 1, "versionName": "0.1.0", - "outputFile": "app-release.apk" + "outputFile": "app-arm64-v8a-release.apk" } ], "elementType": "File", diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/GudariWalletApplication.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/GudariWalletApplication.kt index efe5121..7b029f4 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/GudariWalletApplication.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/GudariWalletApplication.kt @@ -29,7 +29,7 @@ class GudariWalletApplication : Application(), Configuration.Provider { override fun onCreate() { super.onCreate() - NotificationHelper.createChannels(this) // moved from MainActivity + NotificationHelper.createChannels(this) if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) } 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 12752cd..a6d6a7a 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/api/JsonProvider.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/api/JsonProvider.kt @@ -19,5 +19,6 @@ object JsonProvider { ignoreUnknownKeys = true // tolerate extra fields from the API isLenient = true // accept unquoted/malformed JSON from some endpoints explicitNulls = false // omit null fields when serializing requests + encodeDefaults = true } } 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 daafd78..7dba757 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/api/Models.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/api/Models.kt @@ -340,42 +340,43 @@ data class FiatRateResponse( // ── History sync ────────────────────────────────────────────────────────────── @Serializable data class PaginatedPaymentsResponse( - @SerialName("data") val data: List, - @SerialName("total") val total: Int + @SerialName("data") val data: List = emptyList(), + @SerialName("total") val total: Int = 0 ) // ── NWC Provider models ─────────────────────────────────────────────────────── @Serializable data class NwcKey( - val pubkey: String, - val wallet: String, - val description: String, - val expires_at: Long, - val permissions: String, - val created_at: Long, - val last_used: Long + val pubkey: String = "", + val wallet: String = "", + val description: String = "", + val expires_at: Long = 0L, + val permissions: String = "", + val created_at: Long = 0L, + val last_used: Long = 0L ) @Serializable data class NwcBudget( - val id: Int, - val pubkey: String, - val budget_msats: Long, - val refresh_window: Int, // seconds - val created_at: Long, - val used_budget_msats: Long = 0 + val id: Int = 0, + val pubkey: String = "", + val budget_msats: Long = 0L, + val refresh_window: Int = 0, + val created_at: Long = 0L, + val used_budget_msats: Long = 0L ) @Serializable data class NwcGetResponse( - val data: NwcKey, - val budgets: List + val data: NwcKey = NwcKey(), + val budgets: List = emptyList() ) +// Request models — no defaults needed (you control the fields, never deserializing) @Serializable data class NwcNewBudget( val budget_msats: Long, - val refresh_window: Int, // seconds + val refresh_window: Int, val created_at: Long = System.currentTimeMillis() / 1000L ) @@ -383,7 +384,7 @@ data class NwcNewBudget( data class NwcRegistrationRequest( val permissions: List, val description: String, - val expires_at: Long, // unix timestamp; 0 = no expiry + val expires_at: Long, val budgets: List = emptyList() ) diff --git a/gradle.properties b/gradle.properties index fb54c5d..4549c4d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,11 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. For more details, visit -# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects -# org.gradle.parallel=true +# org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official #Your project is using a version of the Android Gradle Plugin that has built-in Kotlin support, @@ -35,3 +31,11 @@ org.gradle.parallel=true # Enable build caching (caches task outputs across clean builds) org.gradle.caching=true +# Increase heap if R8 is being swapped out ? default is often too low +org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# Enables incremental R8 ? only reprocesses changed classes +# Safe for release builds, significant speedup on incremental rebuilds +android.enableR8.incremental=true +# R8-specific: run R8 in a separate process with more memory +android.r8.jvmArgs=-Xmx4g \ No newline at end of file