From a329b5e63101978bd4a1b54fe50a3f535a158d68 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 25 Jun 2026 16:23:43 -0400 Subject: [PATCH 1/2] fix(onramp): stub ResolvedUserFlags to fix ClassCastException in tests MockK relaxed mocks cannot properly handle the generic computed property ResolvedFlag.effectiveValue, returning a mock Object instead of a Boolean. Explicitly stub requireCoinbaseEmailVerification with a real ResolvedFlag to avoid the cast failure. Signed-off-by: Brandon McAnsh --- .../app/onramp/CoinbaseOnRampControllerTest.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/flipcash/shared/onramp/coinbase/src/test/kotlin/com/flipcash/app/onramp/CoinbaseOnRampControllerTest.kt b/apps/flipcash/shared/onramp/coinbase/src/test/kotlin/com/flipcash/app/onramp/CoinbaseOnRampControllerTest.kt index 966e91d3b..53102f11a 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/test/kotlin/com/flipcash/app/onramp/CoinbaseOnRampControllerTest.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/test/kotlin/com/flipcash/app/onramp/CoinbaseOnRampControllerTest.kt @@ -3,6 +3,9 @@ package com.flipcash.app.onramp import com.coinbase.onramp.api.CoinbaseApi import com.coinbase.onramp.data.OnRampApiConfig import com.flipcash.app.featureflags.FeatureFlagController +import com.flipcash.app.userflags.FieldOverride +import com.flipcash.app.userflags.ResolvedFlag +import com.flipcash.app.userflags.ResolvedUserFlags import com.flipcash.app.userflags.UserFlagsCoordinator import com.flipcash.services.models.UserProfile import com.flipcash.services.user.UserManager @@ -22,6 +25,7 @@ import io.mockk.mockkStatic import io.mockk.slot import io.mockk.unmockkStatic import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject @@ -78,6 +82,14 @@ class CoinbaseOnRampControllerTest { every { webViewChannelDetector.detect() } returns null + val resolvedFlags = mockk(relaxed = true) { + every { requireCoinbaseEmailVerification } returns ResolvedFlag( + serverValue = true, + override = FieldOverride.None, + ) + } + every { userFlags.resolvedFlags } returns MutableStateFlow(resolvedFlags) + controller = CoinbaseOnRampController( jwtProvider = jwtProvider, onRampApiEndpoint = onRampApiEndpoint, From 6ebb26293604f348bcca3137fd4b1ad23efeff41 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 25 Jun 2026 16:24:12 -0400 Subject: [PATCH 2/2] chore(ci): optimize build performance with config cache, R8 full mode, and lint restructuring - Persist Gradle configuration cache between CI runs (~4 min saved) - Enable R8 full mode and bump JVM heap to 6g - Move lint from release builds to PR CI (lintDebug) - Skip R8/shrink on PR CI via skipExpensiveReleaseTasks property - Remove dead Voyager keep rule from proguard-rules.pro Signed-off-by: Brandon McAnsh --- .github/workflows/build-fcash2-upload-android.yml | 8 ++++++-- .github/workflows/ci.yml | 4 +++- apps/flipcash/app/build.gradle.kts | 11 +++++++++-- apps/flipcash/app/proguard-rules.pro | 3 --- fastlane/Fastfile | 2 +- gradle.properties | 6 +++++- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-fcash2-upload-android.yml b/.github/workflows/build-fcash2-upload-android.yml index 9ba015000..e97f30fae 100644 --- a/.github/workflows/build-fcash2-upload-android.yml +++ b/.github/workflows/build-fcash2-upload-android.yml @@ -52,7 +52,9 @@ jobs: - name: Gradle build cache uses: actions/cache@v4 with: - path: ~/.gradle/caches/build-cache-1 + path: | + ~/.gradle/caches/build-cache-1 + .gradle/configuration-cache key: gradle-build-cache-${{ hashFiles('**/*.gradle.kts', 'gradle.properties') }} restore-keys: | gradle-build-cache- @@ -105,7 +107,9 @@ jobs: - name: Gradle build cache uses: actions/cache@v4 with: - path: ~/.gradle/caches/build-cache-1 + path: | + ~/.gradle/caches/build-cache-1 + .gradle/configuration-cache key: gradle-build-cache-${{ hashFiles('**/*.gradle.kts', 'gradle.properties') }} restore-keys: | gradle-build-cache- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1421009d..55acc3106 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,9 @@ jobs: - name: Gradle build cache uses: actions/cache@v4 with: - path: ~/.gradle/caches/build-cache-1 + path: | + ~/.gradle/caches/build-cache-1 + .gradle/configuration-cache key: gradle-build-cache-${{ hashFiles('**/*.gradle.kts', 'gradle.properties') }} restore-keys: | gradle-build-cache- diff --git a/apps/flipcash/app/build.gradle.kts b/apps/flipcash/app/build.gradle.kts index c3a25f7a0..a75441b46 100644 --- a/apps/flipcash/app/build.gradle.kts +++ b/apps/flipcash/app/build.gradle.kts @@ -27,6 +27,9 @@ fun gitVersionCode(): Int { val contributorsSigningConfig = ContributorsSignatory(rootDir) val appNamespace = "${Gradle.flipcashNamespace}.app.android" +val skipExpensiveReleaseTasks = providers.gradleProperty("skipExpensiveReleaseTasks") + .orElse("false").get().toBooleanStrict() + android { // static namespace namespace = appNamespace @@ -65,8 +68,8 @@ android { buildTypes { getByName("release") { resValue("string", "applicationId", appNamespace) - isMinifyEnabled = true - isShrinkResources = true + isMinifyEnabled = !skipExpensiveReleaseTasks + isShrinkResources = !skipExpensiveReleaseTasks proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } getByName("debug") { @@ -92,6 +95,10 @@ android { } } + lint { + checkReleaseBuilds = false + } + compileOptions { sourceCompatibility(libs.versions.android.java.get()) targetCompatibility(libs.versions.android.java.get()) diff --git a/apps/flipcash/app/proguard-rules.pro b/apps/flipcash/app/proguard-rules.pro index 8b186bfe6..2d65c81b8 100644 --- a/apps/flipcash/app/proguard-rules.pro +++ b/apps/flipcash/app/proguard-rules.pro @@ -6,9 +6,6 @@ # Preserve source file names and line numbers for stack traces (call site tracking, Bugsnag) -keepattributes SourceFile,LineNumberTable -# Keep screen names --keepnames class * implements cafe.adriel.voyager.core.screen.Screen - # Keep AppRoute class names for analytics screen tracking -keepnames class com.flipcash.app.core.AppRoute -keepnames class com.flipcash.app.core.AppRoute$** diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 0b54ff445..c56197299 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -19,7 +19,7 @@ platform :android do desc "Runs all the tests for Flipcash" lane :flipcash_tests do gradle( - task: "generateEmojiList flipcashTestDebug", + task: "generateEmojiList flipcashTestDebug lintDebug", properties: { "skipCoverage" => ENV.fetch("SKIP_COVERAGE", "false") } diff --git a/gradle.properties b/gradle.properties index e57b45e0e..0d37ec7fd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +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=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true +org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects @@ -41,3 +41,7 @@ android.experimental.enableTestFixturesKotlinSupport=true android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,android.experimental.enableTestFixturesKotlinSupport android.uniquePackageNames=false android.dependency.useConstraints=false +# R8 full mode: enables more aggressive optimizations (class merging, +# enum unboxing, constant propagation). Produces smaller, faster output +# and can also reduce R8's own processing time. +android.enableR8.fullMode=true