diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb60deb9..9a98d2e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2 ## [Unreleased] +### Fixed +- The Disconnected popup trigger when the app is backgrounded has been fixed + ## [1.3.2 (829)] - 2025-01-10 ### Changed diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3f59ee951..b0f1d9206 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -178,6 +178,7 @@ dependencies { implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.coroutines.core) + implementation(libs.androidx.lifecycle.process) implementation(libs.zcash.sdk) // just to configure logging implementation(projects.crashAndroidLib) implementation(projects.preferenceApiLib) diff --git a/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt b/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt index 46b992fa9..8b7750bc6 100644 --- a/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt +++ b/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt @@ -1,5 +1,7 @@ package co.electriccoin.zcash.app +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.ProcessLifecycleOwner import co.electriccoin.zcash.crash.android.GlobalCrashReporter import co.electriccoin.zcash.di.coreModule import co.electriccoin.zcash.di.dataSourceModule @@ -10,6 +12,7 @@ import co.electriccoin.zcash.di.viewModelModule import co.electriccoin.zcash.preference.StandardPreferenceProvider import co.electriccoin.zcash.spackle.StrictModeCompat import co.electriccoin.zcash.spackle.Twig +import co.electriccoin.zcash.ui.common.provider.ApplicationStateProvider import co.electriccoin.zcash.ui.common.repository.FlexaRepository import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys import kotlinx.coroutines.launch @@ -18,10 +21,10 @@ import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger import org.koin.core.context.startKoin -@Suppress("unused") class ZcashApplication : CoroutineApplication() { private val standardPreferenceProvider by inject() private val flexaRepository by inject() + private val applicationStateProvider: ApplicationStateProvider by inject() override fun onCreate() { super.onCreate() @@ -43,6 +46,13 @@ class ZcashApplication : CoroutineApplication() { ) } + // Observe the application process lifecycle + ProcessLifecycleOwner.get().lifecycle.addObserver( + LifecycleEventObserver { _, event -> + applicationStateProvider.setApplicationState(event) + } + ) + // Since analytics will need disk IO internally, we want this to be registered after strict // mode is configured to ensure none of that IO happens on the main thread configureAnalytics() diff --git a/docs/whatsNew/WHATS_NEW_EN.md b/docs/whatsNew/WHATS_NEW_EN.md index 92254bd6e..c77b40e08 100644 --- a/docs/whatsNew/WHATS_NEW_EN.md +++ b/docs/whatsNew/WHATS_NEW_EN.md @@ -12,6 +12,9 @@ directly impact users rather than highlighting other key architectural updates.* ## [Unreleased] +### Fixed +- The Disconnected popup trigger when the app is backgrounded has been fixed + ## [1.3.2 (829)] - 2025-01-10 ### Changed diff --git a/docs/whatsNew/WHATS_NEW_ES.md b/docs/whatsNew/WHATS_NEW_ES.md index e269683ad..5f2f641ac 100644 --- a/docs/whatsNew/WHATS_NEW_ES.md +++ b/docs/whatsNew/WHATS_NEW_ES.md @@ -12,6 +12,9 @@ directly impact users rather than highlighting other key architectural updates.* ## [Unreleased] +### Fixed +- The Disconnected popup trigger when the app is backgrounded has been fixed + ## [1.3.2 (829)] - 2025-01-10 ### Cambiado diff --git a/settings.gradle.kts b/settings.gradle.kts index 07b2c4f6e..da9e202b5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -224,6 +224,7 @@ dependencyResolutionManagement { library("androidx-fragment-compose", "androidx.fragment:fragment-compose:$androidxFragmentVersion") library("androidx-lifecycle-livedata", "androidx.lifecycle:lifecycle-livedata-ktx:$androidxLifecycleVersion") library("androidx-lifecycle-compose", "androidx.lifecycle:lifecycle-runtime-compose:$androidxLifecycleVersion") + library("androidx-lifecycle-process", "androidx.lifecycle:lifecycle-process:$androidxLifecycleVersion") library("androidx-navigation-compose", "androidx.navigation:navigation-compose:$androidxNavigationComposeVersion") library("androidx-profileinstaller", "androidx.profileinstaller:profileinstaller:$androidxProfileInstallerVersion") library("androidx-security-crypto", "androidx.security:security-crypto-ktx:$androidxSecurityCryptoVersion") diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/ProviderModule.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/ProviderModule.kt index ec6f23e58..b7020aac2 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/di/ProviderModule.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/ProviderModule.kt @@ -6,6 +6,8 @@ import co.electriccoin.zcash.ui.common.provider.AddressBookProvider import co.electriccoin.zcash.ui.common.provider.AddressBookProviderImpl import co.electriccoin.zcash.ui.common.provider.AddressBookStorageProvider import co.electriccoin.zcash.ui.common.provider.AddressBookStorageProviderImpl +import co.electriccoin.zcash.ui.common.provider.ApplicationStateProvider +import co.electriccoin.zcash.ui.common.provider.ApplicationStateProviderImpl import co.electriccoin.zcash.ui.common.provider.GetDefaultServersProvider import co.electriccoin.zcash.ui.common.provider.GetMonetarySeparatorProvider import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider @@ -33,4 +35,5 @@ val providerModule = factoryOf(::SelectedAccountUUIDProviderImpl) bind SelectedAccountUUIDProvider::class singleOf(::PersistableWalletProviderImpl) bind PersistableWalletProvider::class singleOf(::SynchronizerProviderImpl) bind SynchronizerProvider::class + singleOf(::ApplicationStateProviderImpl) bind ApplicationStateProvider::class } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt index 0de8257f9..1a386dd30 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt @@ -43,6 +43,8 @@ import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT import co.electriccoin.zcash.ui.NavigationTargets.WHATS_NEW import co.electriccoin.zcash.ui.common.compose.LocalNavController import co.electriccoin.zcash.ui.common.model.SerializableAddress +import co.electriccoin.zcash.ui.common.provider.ApplicationStateProvider +import co.electriccoin.zcash.ui.common.provider.isInForeground import co.electriccoin.zcash.ui.configuration.ConfigurationEntries import co.electriccoin.zcash.ui.configuration.RemoteConfig import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.enterTransition @@ -102,6 +104,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.launch import kotlinx.serialization.json.Json +import org.koin.android.ext.android.inject import org.koin.compose.koinInject // TODO [#1297]: Consider: Navigation passing complex data arguments different way @@ -409,6 +412,8 @@ private fun MainActivity.NavigationHome( navController: NavHostController, backStack: NavBackStackEntry ) { + val applicationStateProvider: ApplicationStateProvider by inject() + WrapHome( goScan = { navController.navigateJustOnce(ScanNavigationArgs(ScanNavigationArgs.DEFAULT)) }, sendArguments = @@ -432,11 +437,19 @@ private fun MainActivity.NavigationHome( val sdkStatus = walletViewModel.currentWalletSnapshot.collectAsStateWithLifecycle().value?.status + val currentAppState = applicationStateProvider.state.collectAsStateWithLifecycle().value + if (isEnoughSpace == false) { Twig.info { "Not enough free space" } navController.navigateJustOnce(NOT_ENOUGH_SPACE) } else if (Synchronizer.Status.DISCONNECTED == sdkStatus) { Twig.info { "Disconnected state received from Synchronizer" } + + if (!currentAppState.isInForeground()) { + Twig.info { "Disconnected state received but omitted as the app is not in foreground" } + return + } + WrapDisconnected( goChooseServer = { navController.navigateJustOnce(CHOOSE_SERVER) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/provider/ApplicationStateProvider.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/provider/ApplicationStateProvider.kt new file mode 100644 index 000000000..bea6a39f6 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/provider/ApplicationStateProvider.kt @@ -0,0 +1,34 @@ +package co.electriccoin.zcash.ui.common.provider + +import androidx.lifecycle.Lifecycle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.last +import kotlinx.coroutines.flow.update + +interface ApplicationStateProvider { + val state: StateFlow + + suspend fun getApplicationState(): Lifecycle.Event? + + fun setApplicationState(newState: Lifecycle.Event) +} + +class ApplicationStateProviderImpl : ApplicationStateProvider { + private val _state = MutableStateFlow(null) + + override val state = _state.asStateFlow() + + override suspend fun getApplicationState(): Lifecycle.Event? { + return _state.last() + } + + override fun setApplicationState(newState: Lifecycle.Event) { + _state.update { newState } + } +} + +fun Lifecycle.Event?.isInForeground(): Boolean = + this == Lifecycle.Event.ON_RESUME || + this == Lifecycle.Event.ON_START