From 7f95a6b5181c780832aab2c969153850ba85f5ea Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 May 2025 19:38:07 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/tap/ApplicationEntryPoint.kt | 3 + .../java/com/tangem/tap/TangemApplication.kt | 14 ++-- .../tangem/tap/features/main/MainViewModel.kt | 73 ++++++++++++------- .../config/managers/ApiConfigsManager.kt | 6 +- .../config/managers/DevApiConfigsManager.kt | 12 ++- .../config/managers/ProdApiConfigsManager.kt | 6 ++ .../com/tangem/datasource/di/NetworkModule.kt | 12 ++- 7 files changed, 92 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index ee95e2ba40..52a6dc9646 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -15,6 +15,7 @@ import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.data.card.TransactionSignerFactory +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.issuers.IssuersConfigStorage @@ -142,4 +143,6 @@ interface ApplicationEntryPoint { fun getOnlineCardVerifier(): OnlineCardVerifier fun getUserWalletBuilderFactory(): UserWalletBuilder.Factory + + fun getApiConfigsManager(): ApiConfigsManager } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index ed0018b6d9..03c39c9727 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -30,6 +30,7 @@ import com.tangem.core.navigation.settings.SettingsManager import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.data.card.TransactionSignerFactory import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager import com.tangem.datasource.api.common.createNetworkLoggingInterceptor import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.local.config.environment.EnvironmentConfig @@ -228,6 +229,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat private val userWalletBuilderFactory: UserWalletBuilder.Factory get() = entryPoint.getUserWalletBuilderFactory() + private val apiConfigsManager: ApiConfigsManager + get() = entryPoint.getApiConfigsManager() + // endregion private val appScope = MainScope() @@ -275,6 +279,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat } fun init() { + apiConfigsManager.initialize() + store = createReduxStore() tangemAppLoggerInitializer.initialize() @@ -285,12 +291,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat // We need to initialize the toggles and excludedBlockchainsManager before the MainActivity starts using them. runBlocking { awaitAll( - async { - featureTogglesManager.init() - }, - async { - excludedBlockchainsManager.init() - }, + async { featureTogglesManager.init() }, + async { excludedBlockchainsManager.init() }, ) initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize()) } diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 8911eebb8c..d2b77135d2 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -11,6 +11,7 @@ import com.tangem.core.analytics.models.event.TechAnalyticsEvent import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.deeplink.DeepLinksRegistry +import com.tangem.core.ui.BuildConfig import com.tangem.core.ui.R import com.tangem.core.ui.coil.ImagePreloader import com.tangem.core.ui.extensions.resourceReference @@ -40,10 +41,13 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout import timber.log.Timber import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds @Suppress("LongParameterList") @HiltViewModel @@ -57,7 +61,6 @@ internal class MainViewModel @Inject constructor( private val userWalletsListManager: UserWalletsListManager, private val dispatchers: CoroutineDispatcherProvider, private val fetchStakingTokensUseCase: FetchStakingTokensUseCase, - private val apiConfigsManager: ApiConfigsManager, private val fetchUserCountryUseCase: FetchUserCountryUseCase, @GlobalUiMessageSender private val messageSender: UiMessageSender, private val keyboardValidator: KeyboardValidator, @@ -68,6 +71,7 @@ internal class MainViewModel @Inject constructor( private val onboardingRepository: OnboardingRepository, private val deepLinksRegistry: DeepLinksRegistry, private val onrampDeepLinkFactory: OnrampDeepLink.Factory, + private val apiConfigsManager: ApiConfigsManager, routingFeatureToggle: RoutingFeatureToggle, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel() { @@ -79,25 +83,27 @@ internal class MainViewModel @Inject constructor( private set init { + /** + * Run any data initialization here that needs to happen before the app starts + * and is hidden behind the SplashScreen + */ loadApplicationResources() - viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() } + /** Run any API data load here that runs in parallel and does not block the app from starting */ + launchAPIRequests { + launch { fetchHotCryptoUseCase() } - viewModelScope.launch { - fetchUserCountryUseCase().onLeft { - Timber.e("Unable to fetch the user country code $it") - } + launch { fetchAppCurrenciesUseCase() } + + launch { fetchStakingTokens() } } - viewModelScope.launch { fetchHotCryptoUseCase() } + viewModelScope.launch { incrementAppLaunchCounterUseCase() } - updateAppCurrencies() observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() - fetchStakingTokens() - deleteDeprecatedLogsUseCase() sendKeyboardIdentifierEvent() @@ -118,16 +124,41 @@ internal class MainViewModel @Inject constructor( /** Loading the resources needed to run the application */ private fun loadApplicationResources() { - viewModelScope.launch(dispatchers.main) { - apiConfigsManager.initialize() + viewModelScope.launch { + launchAPIRequests { + launch { blockchainSDKFactory.init() } + + launch { + withTimeout(timeMillis = 1.seconds.inWholeMilliseconds) { fetchUserCountry() } + } + } - blockchainSDKFactory.init() prepareSelectedWalletFeedback() isSplashScreenShown = false } } + private suspend fun fetchUserCountry() { + fetchUserCountryUseCase().onLeft { + Timber.e("Unable to fetch the user country code $it") + } + } + + private fun launchAPIRequests(function: suspend CoroutineScope.() -> Unit) { + viewModelScope.launch { + if (BuildConfig.TESTER_MENU_ENABLED) { + apiConfigsManager.isInitialized + .filter { it } + .first() // wait until isInitialized becomes true + + function() + } else { + function() + } + } + } + private fun prepareSelectedWalletFeedback() { userWalletsListManager.selectedUserWallet .distinctUntilChanged() @@ -138,18 +169,10 @@ internal class MainViewModel @Inject constructor( .launchIn(viewModelScope) } - private fun updateAppCurrencies() { - viewModelScope.launch(dispatchers.main) { - fetchAppCurrenciesUseCase.invoke() - } - } - - private fun fetchStakingTokens() { - viewModelScope.launch(dispatchers.main) { - fetchStakingTokensUseCase() - .onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") } - .onRight { Timber.d("Staking token list was fetched successfully") } - } + private suspend fun fetchStakingTokens() { + fetchStakingTokensUseCase() + .onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") } + .onRight { Timber.d("Staking token list was fetched successfully") } } private fun observeFlips() { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ApiConfigsManager.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ApiConfigsManager.kt index e7c3054219..a111d2b2a0 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ApiConfigsManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ApiConfigsManager.kt @@ -2,6 +2,7 @@ package com.tangem.datasource.api.common.config.managers import com.tangem.datasource.api.common.config.ApiConfig import com.tangem.datasource.api.common.config.ApiEnvironmentConfig +import kotlinx.coroutines.flow.StateFlow /** * Api configs manager @@ -10,8 +11,11 @@ import com.tangem.datasource.api.common.config.ApiEnvironmentConfig */ interface ApiConfigsManager { + /** Flag that determines whether the manager is initialized */ + val isInitialized: StateFlow + /** Initialize resources */ - fun initialize() {} + fun initialize() /** Get environment config by [id] */ fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt index 7d60844b81..4327ec28df 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt @@ -29,7 +29,13 @@ internal class DevApiConfigsManager( private val _apiConfigs = MutableStateFlow(value = apiConfigs.associateWith { it.defaultEnvironment }) + override val isInitialized: StateFlow get() = _isInitialized.asStateFlow() + + private val _isInitialized = MutableStateFlow(value = false) + override fun initialize() { + _isInitialized.value = false + // We can't use appPreferencesStore.getObjectMap as base flow, // because we should keep possibility to work with configs synchronous. // See [getBaseUrl] @@ -42,8 +48,12 @@ internal class DevApiConfigsManager( savedEnvironments[config.id.name] ?: currentEnvironment } } + + if (!_isInitialized.value) { + _isInitialized.value = true + } } - .launchIn(CoroutineScope(SupervisorJob() + dispatchers.main)) + .launchIn(CoroutineScope(SupervisorJob() + dispatchers.default)) } override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManager.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManager.kt index fd5638c151..8b77808141 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManager.kt @@ -3,6 +3,8 @@ package com.tangem.datasource.api.common.config.managers import com.tangem.datasource.api.common.config.ApiConfig import com.tangem.datasource.api.common.config.ApiConfigs import com.tangem.datasource.api.common.config.ApiEnvironmentConfig +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow /** * Implementation of [ApiConfigsManager] in PROD environment @@ -13,6 +15,10 @@ internal class ProdApiConfigsManager( private val apiConfigs: ApiConfigs, ) : ApiConfigsManager { + override val isInitialized: StateFlow = MutableStateFlow(value = true) + + override fun initialize() = Unit + override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig { val config = apiConfigs.firstOrNull { it.id == id } ?: error("Api config with id [$id] not found. Check that ApiConfig with id [$id] was provided into DI") diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index a4b0eb363a..bfb13f75b1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -43,6 +43,10 @@ internal object NetworkModule { private const val TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS = 60L private const val STAKE_KIT_API_TIMEOUT_SECONDS = 60L + private val excludedApiForLogging: Set = setOf( + // ApiConfig.ID.StakeKit, + ) + @Provides @Singleton fun provideApiConfigManager( @@ -317,7 +321,7 @@ internal object NetworkModule { } b } - .addLoggers(context) + .addLoggers(context = context, id = id) .clientBuilder() .build(), ) @@ -325,6 +329,12 @@ internal object NetworkModule { .create(T::class.java) } + private fun OkHttpClient.Builder.addLoggers(context: Context, id: ApiConfig.ID): OkHttpClient.Builder { + if (id in excludedApiForLogging) return this + + return addLoggers(context) + } + private data class Timeouts( val callTimeoutSeconds: Long? = null, val connectTimeoutSeconds: Long? = null,