Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-23 18:04:31 +04:00
parent ad65a62b3f
commit 97a13da0d7
19 changed files with 176 additions and 56 deletions

View file

@ -8,6 +8,8 @@ import com.tangem.domain.appupdate.repository.AppUpdateRepository
import com.tangem.utils.coroutines.runSuspendCatching
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class GetAppUpdateStateUseCase(
private val repository: AppUpdateRepository,
@ -17,11 +19,21 @@ class GetAppUpdateStateUseCase(
/**
* Instant decision computed from the cached thresholds and the current app/OS version. No network.
* Records the optional update as shown when it decides to show it (24h throttle). Never throws
* any failure resolves to [AppUpdateState.NoUpdate] so the initial navigation is never blocked.
* Records the optional update as shown as it decides to show it (24h throttle) used by the startup gate.
* Never throws any failure resolves to [AppUpdateState.NoUpdate] so the initial navigation is never blocked.
*/
suspend fun getCached(): AppUpdateState = runSuspendCatching {
resolve(freshCachedInfoOrNull(), recordOptionalShown = true)
suspend fun getCached(): AppUpdateState = getCachedState(recordOptionalShown = true)
/**
* Cache-only update state as a cold [Flow], with the optional-update throttle disabled so a
* non-dismissible banner stays visible while the optional update is relevant. No network, no side effects.
*/
fun getBannerStateFlow(): Flow<AppUpdateState> = flow {
emit(getCachedState(recordOptionalShown = false))
}
private suspend fun getCachedState(recordOptionalShown: Boolean): AppUpdateState = runSuspendCatching {
resolve(freshCachedInfoOrNull(), recordOptionalShown = recordOptionalShown)
}.getOrElse { error ->
TangemLogger.e("Unable to resolve cached app update state", error)
AppUpdateState.NoUpdate
@ -65,7 +77,7 @@ class GetAppUpdateStateUseCase(
val minSupportedVersion = info.minSupportedVersion?.let(AppVersion::parseOrNull)
if (minSupportedVersion != null &&
appVersion <= minSupportedVersion &&
appVersion < minSupportedVersion &&
isEscapable(latestVersion, minSupportedVersion)
) {
return blockingStateFor(info.minSupportedOSVersion, deviceOsVersion, AppUpdateState.OsTooOld)

View file

@ -15,6 +15,7 @@ import io.mockk.coVerify
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@ -78,9 +79,9 @@ internal class GetAppUpdateStateUseCaseTest {
}
@Test
fun `GIVEN app at min supported and OS ok WHEN getCached THEN ForceUpdate`() = runTest {
fun `GIVEN app below min supported and OS ok WHEN getCached THEN ForceUpdate`() = runTest {
givenCached(
appVersion = "5.0",
appVersion = "4.9",
osVersion = "14",
info = info(minSupportedVersion = "5.0", minSupportedOSVersion = "10", latestVersion = "5.1"),
)
@ -89,9 +90,20 @@ internal class GetAppUpdateStateUseCaseTest {
}
@Test
fun `GIVEN app at min supported and OS too old WHEN getCached THEN OsTooOld`() = runTest {
fun `GIVEN app exactly at min supported WHEN getCached THEN not force and optional`() = runTest {
givenCached(
appVersion = "5.0",
osVersion = "14",
info = info(minSupportedVersion = "5.0", minSupportedOSVersion = "10", latestVersion = "5.1"),
)
assertThat(useCase.getCached()).isEqualTo(AppUpdateState.OptionalUpdate)
}
@Test
fun `GIVEN app below min supported and OS too old WHEN getCached THEN OsTooOld`() = runTest {
givenCached(
appVersion = "4.9",
osVersion = "9",
info = info(minSupportedVersion = "5.0", minSupportedOSVersion = "10", latestVersion = "5.1"),
)
@ -165,6 +177,16 @@ internal class GetAppUpdateStateUseCaseTest {
assertThat(useCase.getCached()).isEqualTo(AppUpdateState.OptionalUpdate)
}
@Test
fun `GIVEN optional shown recently WHEN getBannerStateFlow THEN OptionalUpdate without throttle`() = runTest {
givenCached(appVersion = "5.0", info = info(latestVersion = "5.37"))
coEvery { repository.getOptionalUpdateShown() } returns
OptionalUpdateShown(version = "5.37", shownAtMillis = NOW)
assertThat(useCase.getBannerStateFlow().first()).isEqualTo(AppUpdateState.OptionalUpdate)
coVerify(exactly = 0) { repository.setOptionalUpdateShown(any()) }
}
@Test
fun `GIVEN all thresholds null WHEN getCached THEN NoUpdate`() = runTest {
givenCached(info = info())