diff --git a/.circleci/config.yml b/.circleci/config.yml index ca18843c..ae0d3da8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,12 +70,25 @@ jobs: tag: 2024.07.1-ndk resource_class: large environment: - GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2" + # Lowered from -Xmx4g/workers.max=2: the Gradle daemon heap plus forked + # unit-test worker JVMs plus OS/container overhead were exceeding the + # `large` resource class's available RAM, causing the daemon to be + # OOM-killed mid-build ("daemon has disappeared"). Reducing the daemon + # heap and worker parallelism leaves more headroom for the AGP-forked + # test JVMs without changing the resource class. + GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=512m -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1" steps: - setup_environment - run: name: "Execute Unit Tests" - command: ./gradlew testBrainwalletDebugUnitTest --no-daemon --max-workers=2 + # -x detekt: android-build-logic's DetektSetup.attachDetektTask() wires + # `detekt` (autoCorrect=true, parallel=true, HTML/XML/TXT/SARIF/MD reports) + # as a dependency of every compile*/assemble* task project-wide, so a plain + # test run was also paying for 3-4 full static-analysis passes it doesn't + # need — real CPU/memory competing with compilation and the forked test + # JVMs on this resource-constrained executor. Excluding it here only + # affects this CI job, not local dev/lint workflows. + command: ./gradlew testBrainwalletDebugUnitTest --no-daemon --max-workers=1 -x detekt - android/save_gradle_cache - run: name: Save test results diff --git a/.github/workflows/pr-summary-copilot.yml b/.github/workflows/pr-summary-copilot.yml index cd3c3beb..92bec6f7 100644 --- a/.github/workflows/pr-summary-copilot.yml +++ b/.github/workflows/pr-summary-copilot.yml @@ -1,11 +1,6 @@ name: 🤖 Copilot PR Summary on: - pull_request: - types: [opened, reopened] - branches: - - develop - - main workflow_dispatch: permissions: diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9b590970..1795f011 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -31,8 +31,8 @@ android { applicationId = "ltd.grunt.brainwallet" minSdk = 29 targetSdk = 35 - versionCode = 202506342 - versionName = "v4.10.4" + versionCode = 202506343 + versionName = "v4.10.5" multiDexEnabled = true base.archivesName.set("${defaultConfig.versionName}(${defaultConfig.versionCode})") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModel.kt b/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModel.kt index 9c9f2d59..a2576156 100644 --- a/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModel.kt @@ -6,6 +6,7 @@ import com.brainwallet.ui.BrainwalletViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.koin.android.annotation.KoinViewModel @@ -26,41 +27,37 @@ class ShopBentoViewModel( val currentCountryISO: String = Locale.getDefault().country.ifEmpty { "US" } init { - viewModelScope.launch { - settingRepository.settings.collect { setting -> - _state.update { - it.copy( - darkMode = setting.isDarkMode, - countryIso = currentCountryISO - ) - } - } - } viewModelScope.launch { shopProxyRepository.refresh() - shopProxyRepository.shopProxy.collect { shopList -> - val widget = shopList.firstOrNull()?.widget.orEmpty() - val cards = shopList.firstOrNull()?.shopCards.orEmpty() - .filter { it.countryCode == currentCountryISO } - var imageUrl1 = "" - var imageUrl2 = "" - var imageUrl3 = "" + combine( + settingRepository.settings, + shopProxyRepository.shopProxy + ) { setting, shopList -> setting to shopList } + .collect { (setting, shopList) -> + val widget = shopList.firstOrNull()?.widget.orEmpty() + val cards = shopList.firstOrNull()?.shopCards.orEmpty() + .filter { it.countryCode == currentCountryISO } + var imageUrl1 = "" + var imageUrl2 = "" + var imageUrl3 = "" - if (cards.count() >= 3) { - imageUrl1 = cards[0].cardImageWebP - imageUrl2 = cards[1].cardImageWebP - imageUrl3 = cards[2].cardImageWebP - } - _state.update { - it.copy( - shopBaseUrl = widget, - shopCards = cards, - cardImageURL1 = imageUrl1, - cardImageURL2 = imageUrl2, - cardImageURL3 = imageUrl3 - ) + if (cards.count() >= 3) { + imageUrl1 = cards[0].cardImageWebP + imageUrl2 = cards[1].cardImageWebP + imageUrl3 = cards[2].cardImageWebP + } + _state.update { + it.copy( + darkMode = setting.isDarkMode, + countryIso = currentCountryISO, + shopBaseUrl = widget, + shopCards = cards, + cardImageURL1 = imageUrl1, + cardImageURL2 = imageUrl2, + cardImageURL3 = imageUrl3 + ) + } } - } } } diff --git a/app/src/test/kotlin/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModelTest.kt b/app/src/test/kotlin/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModelTest.kt index cd85b224..9677b4eb 100644 --- a/app/src/test/kotlin/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModelTest.kt +++ b/app/src/test/kotlin/com/brainwallet/ui/bentosections/shopbento/ShopBentoViewModelTest.kt @@ -7,18 +7,24 @@ import com.brainwallet.data.repository.SettingRepository import com.brainwallet.data.repository.ShopProxy import com.brainwallet.data.repository.ShopProxyRepository import com.brainwallet.testing.FlakyTest +import com.brainwallet.util.MainDispatcherRule import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before +import org.junit.Rule import org.junit.Test import io.mockk.coEvery class ShopBentoViewModelTest { + @get:Rule + val mainDispatcherRule = MainDispatcherRule() + private lateinit var app: Application private lateinit var settingRepository: SettingRepository private lateinit var shopProxyRepository: ShopProxyRepository @@ -62,16 +68,16 @@ class ShopBentoViewModelTest { @Test fun `init - sets shopBaseUrl from widget`() = runTest { turbineScope { - shopProxyFlow.emit(listOf(ShopProxy(widget = "https://shop.example.com", shopCards = emptyList()))) + shopProxyFlow.emit(listOf(ShopProxy(widget = "https://embed.bitrefill.com", shopCards = emptyList()))) val viewModel = buildViewModel() val turbine = viewModel.state.testIn(backgroundScope) settingsFlow.emit(AppSetting()) - advanceTimeBy(100) + advanceUntilIdle() val state = turbine.expectMostRecentItem() - assertEquals("https://shop.example.com", state.shopBaseUrl) + assertEquals("https://embed.bitrefill.com", state.shopBaseUrl) turbine.cancelAndIgnoreRemainingEvents() } }