Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-13 15:55:28 +03:00
parent bc70ed5327
commit ccd7a7dd18
11 changed files with 98 additions and 39 deletions

View file

@ -208,10 +208,12 @@ dependencies {
implementation(deps.androidx.paging.runtime) implementation(deps.androidx.paging.runtime)
implementation(deps.androidx.swipeRefreshLayout) implementation(deps.androidx.swipeRefreshLayout)
implementation(deps.androidx.fragment.compose) implementation(deps.androidx.fragment.compose)
implementation(deps.androidx.workmanager)
implementation(deps.lifecycle.runtime.ktx) implementation(deps.lifecycle.runtime.ktx)
implementation(deps.lifecycle.common.java8) implementation(deps.lifecycle.common.java8)
implementation(deps.lifecycle.viewModel.ktx) implementation(deps.lifecycle.viewModel.ktx)
implementation(deps.lifecycle.compose) implementation(deps.lifecycle.compose)
implementation(deps.hilt.work)
/** Compose libraries */ /** Compose libraries */
implementation(deps.compose.constraintLayout) implementation(deps.compose.constraintLayout)
@ -250,6 +252,7 @@ dependencies {
implementation(deps.hilt.android) implementation(deps.hilt.android)
kapt(deps.hilt.kapt) kapt(deps.hilt.kapt)
kapt(deps.hilt.compilerx)
/** Other libraries */ /** Other libraries */
implementation(deps.kotlin.immutable.collections) implementation(deps.kotlin.immutable.collections)

View file

@ -27,8 +27,7 @@
<queries> <queries>
<intent> <intent>
<action android:name= <action android:name="android.support.customtabs.action.CustomTabsService" />
"android.support.customtabs.action.CustomTabsService" />
</intent> </intent>
</queries> </queries>
@ -145,6 +144,13 @@
</activity> </activity>
<!-- Disable android.startup completely. Used for Worker according doc -->
<!--https://developer.android.com/jetpack/androidx/releases/work#version_260_3-->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"

View file

@ -1,5 +1,6 @@
package com.tangem.tap package com.tangem.tap
import androidx.hilt.work.HiltWorkerFactory
import com.tangem.TangemSdkLogger import com.tangem.TangemSdkLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.data.card.TransactionSignerFactory import com.tangem.data.card.TransactionSignerFactory
@ -139,4 +140,6 @@ interface ApplicationEntryPoint {
@GlobalUiMessageSender @GlobalUiMessageSender
fun getUiMessageSender(): UiMessageSender fun getUiMessageSender(): UiMessageSender
fun getWorkerFactory(): HiltWorkerFactory
} }

View file

@ -0,0 +1,34 @@
package com.tangem.tap
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import timber.log.Timber
@HiltWorker
class LockTimerWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val settingsRepository: SettingsRepository,
private val userWalletsListManager: UserWalletsListManager,
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
Timber.i("onStart job")
val userWalletsListManagerLockable = userWalletsListManager.asLockable() ?: return Result.failure()
userWalletsListManagerLockable.lock()
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
Timber.i("onStart job complete")
return Result.success()
}
companion object {
const val TAG = "LOCK_TIMER"
}
}

View file

@ -1,23 +1,32 @@
package com.tangem.tap package com.tangem.tap
import android.app.job.JobScheduler
import android.content.Context
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.tap.LockTimerWorker.Companion.TAG
import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.dispatchNavigationAction
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import kotlin.time.Duration import kotlin.time.Duration
internal class LockUserWalletsTimer( internal class LockUserWalletsTimer(
owner: LifecycleOwner, private val context: Context,
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val duration: Duration = with(Duration) { 10.minutes }, private val duration: Duration = with(Duration) { 5.minutes },
private val userWalletsListManager: UserWalletsListManager, private val userWalletsListManager: UserWalletsListManager,
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
) : LifecycleOwner by owner, ) : LifecycleOwner by context as LifecycleOwner,
DefaultLifecycleObserver { DefaultLifecycleObserver {
private var delayJob: Job? = null private var delayJob: Job? = null
@ -31,20 +40,16 @@ internal class LockUserWalletsTimer(
} }
override fun onStart(owner: LifecycleOwner) { override fun onStart(owner: LifecycleOwner) {
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
coroutineScope.launch { coroutineScope.launch {
val wasApplicationStopped = settingsRepository.wasApplicationStopped()
val shouldOpenWelcomeScreenOnResume = settingsRepository.shouldOpenWelcomeScreenOnResume() val shouldOpenWelcomeScreenOnResume = settingsRepository.shouldOpenWelcomeScreenOnResume()
Timber.i( Timber.i(
""" """
Owner resumed Owner resumed
|- Was stopped: $wasApplicationStopped
|- Need to open welcome screen: $shouldOpenWelcomeScreenOnResume |- Need to open welcome screen: $shouldOpenWelcomeScreenOnResume
""".trimIndent(), """.trimIndent(),
) )
settingsRepository.setWasApplicationStopped(value = false)
if (shouldOpenWelcomeScreenOnResume) { if (shouldOpenWelcomeScreenOnResume) {
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false) settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false)
@ -58,10 +63,9 @@ internal class LockUserWalletsTimer(
override fun onStop(owner: LifecycleOwner) { override fun onStop(owner: LifecycleOwner) {
Timber.i("Owner stopped") Timber.i("Owner stopped")
delayJob = null
coroutineScope.launch { startTimerWorker()
settingsRepository.setWasApplicationStopped(value = true)
}
} }
fun restart() { fun restart() {
@ -75,6 +79,15 @@ internal class LockUserWalletsTimer(
start(log = false) start(log = false)
} }
private fun startTimerWorker() {
val lockWorkRequest = OneTimeWorkRequest.Builder(LockTimerWorker::class.java)
.addTag(TAG)
.setInitialDelay(duration.inWholeSeconds, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueue(lockWorkRequest)
}
private fun start(log: Boolean = true) { private fun start(log: Boolean = true) {
if (log) { if (log) {
Timber.i( Timber.i(
@ -87,6 +100,9 @@ internal class LockUserWalletsTimer(
delayJob = createDelayJob() delayJob = createDelayJob()
} }
/**
* This job used only when app is foreground when background use [JobScheduler]
*/
private fun createDelayJob(): Job = coroutineScope.launch { private fun createDelayJob(): Job = coroutineScope.launch {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
@ -96,22 +112,16 @@ internal class LockUserWalletsTimer(
if (userWalletsListManager.hasUserWallets) { if (userWalletsListManager.hasUserWallets) {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
val wasApplicationStopped = settingsRepository.wasApplicationStopped()
Timber.i( Timber.i(
""" """
Finished Finished
|- App is stopped: $wasApplicationStopped
|- Millis passed: ${currentTime - startTime} |- Millis passed: ${currentTime - startTime}
""".trimIndent(), """.trimIndent(),
) )
userWalletsListManager.lock() userWalletsListManager.lock()
if (wasApplicationStopped) { store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
} else {
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
}
} }
} }
} }

View file

@ -355,7 +355,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
} }
lockUserWalletsTimer = LockUserWalletsTimer( lockUserWalletsTimer = LockUserWalletsTimer(
owner = this, context = this,
settingsRepository = settingsRepository, settingsRepository = settingsRepository,
userWalletsListManager = userWalletsListManager, userWalletsListManager = userWalletsListManager,
coroutineScope = mainScope, coroutineScope = mainScope,

View file

@ -1,6 +1,8 @@
package com.tangem.tap package com.tangem.tap
import android.app.Application import android.app.Application
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import coil.ImageLoader import coil.ImageLoader
import coil.ImageLoaderFactory import coil.ImageLoaderFactory
import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.ChuckerInterceptor
@ -78,7 +80,7 @@ lateinit var store: Store<AppState>
lateinit var foregroundActivityObserver: ForegroundActivityObserver lateinit var foregroundActivityObserver: ForegroundActivityObserver
internal lateinit var derivationsFinder: DerivationsFinder internal lateinit var derivationsFinder: DerivationsFinder
abstract class TangemApplication : Application(), ImageLoaderFactory { abstract class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider {
// region DI // region DI
private val entryPoint: ApplicationEntryPoint private val entryPoint: ApplicationEntryPoint
@ -210,6 +212,14 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val uiMessageSender: UiMessageSender private val uiMessageSender: UiMessageSender
get() = entryPoint.getUiMessageSender() get() = entryPoint.getUiMessageSender()
private val workerFactory: HiltWorkerFactory
get() = entryPoint.getWorkerFactory()
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
// endregion // endregion
override fun onCreate() { override fun onCreate() {

View file

@ -19,11 +19,10 @@ internal class DefaultCacheRegistry(
private val mutexes = ConcurrentHashMap<String, Mutex>() private val mutexes = ConcurrentHashMap<String, Mutex>()
override suspend fun isExpired(key: String): Boolean { override suspend fun isExpired(key: String): Boolean {
val cacheKey = cacheKeysStore.getSyncOrNull(key) ?: return true cacheKeysStore.getSyncOrNull(key) ?: return true
return cacheKey.updatedAt // disabled cache expiration by time, update always using refresh flag
.plus(cacheKey.expiresIn) return false
.isBefore(LocalDateTime.now())
} }
override suspend fun invalidate(key: String) { override suspend fun invalidate(key: String) {

View file

@ -71,14 +71,6 @@ internal class DefaultSettingsRepository(
) )
} }
override suspend fun wasApplicationStopped(): Boolean {
return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.WAS_APPLICATION_STOPPED_KEY, default = false)
}
override suspend fun setWasApplicationStopped(value: Boolean) {
appPreferencesStore.store(key = PreferencesKeys.WAS_APPLICATION_STOPPED_KEY, value = value)
}
override suspend fun shouldOpenWelcomeScreenOnResume(): Boolean { override suspend fun shouldOpenWelcomeScreenOnResume(): Boolean {
return appPreferencesStore.getSyncOrDefault( return appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.SHOULD_OPEN_WELCOME_ON_RESUME_KEY, key = PreferencesKeys.SHOULD_OPEN_WELCOME_ON_RESUME_KEY,

View file

@ -20,10 +20,6 @@ interface SettingsRepository {
suspend fun setSendTapHelpPreviewAvailability(isEnabled: Boolean) suspend fun setSendTapHelpPreviewAvailability(isEnabled: Boolean)
suspend fun wasApplicationStopped(): Boolean
suspend fun setWasApplicationStopped(value: Boolean)
suspend fun shouldOpenWelcomeScreenOnResume(): Boolean suspend fun shouldOpenWelcomeScreenOnResume(): Boolean
suspend fun setShouldOpenWelcomeScreenOnResume(value: Boolean) suspend fun setShouldOpenWelcomeScreenOnResume(value: Boolean)

View file

@ -24,6 +24,7 @@ androidx-paging = "3.1.1"
androidx-palette = "1.0.0" androidx-palette = "1.0.0"
androidx-datastore = "1.0.0" androidx-datastore = "1.0.0"
androidxWindowManager = "1.3.0" androidxWindowManager = "1.3.0"
androidxWorkManager = "2.9.0"
# endregion AndroidX # endregion AndroidX
# region Compose # region Compose
@ -53,6 +54,8 @@ googlePlayReviewKtx = "2.0.1"
googlePlayServicesWallet = "19.1.0" googlePlayServicesWallet = "19.1.0"
hilt = "2.55" hilt = "2.55"
hilt-navigation = "1.0.0" hilt-navigation = "1.0.0"
hilt-work = "1.0.0"
hilt-compilerx = "1.0.0"
jodatime = "2.12.1" jodatime = "2.12.1"
kotlin-immutable-collections = "0.3.5" kotlin-immutable-collections = "0.3.5"
kotsonGsonExt = "2.5.0" kotsonGsonExt = "2.5.0"
@ -149,6 +152,8 @@ lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", v
lifecycle-viewModel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" } lifecycle-viewModel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" }
lifecycle-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "compose-lifecycle-runtime" } lifecycle-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "compose-lifecycle-runtime" }
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" } androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
androidx-workmanager = { module = "androidx.work:work-runtime", version.ref = "androidxWorkManager" }
hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hilt-work" }
# region AndroidX # region AndroidX
# region Compose # region Compose
@ -218,6 +223,7 @@ googlePlay-services-wallet = { module = "com.google.android.gms:play-services-wa
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" } hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" }
hilt-kapt = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } hilt-kapt = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
hilt-compilerx = { module = "androidx.hilt:hilt-compiler", version.ref = "hilt-compilerx" }
jodatime = { module = "joda-time:joda-time", version.ref = "jodatime" } jodatime = { module = "joda-time:joda-time", version.ref = "jodatime" }
kotsonGson = { module = "com.github.salomonbrys.kotson:kotson", version.ref = "kotsonGsonExt" } kotsonGson = { module = "com.github.salomonbrys.kotson:kotson", version.ref = "kotsonGsonExt" }
lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" } lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" }