From 1451f23cb2ec1b89953847ba00616e8e82dbe6a8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 29 Jul 2024 14:18:41 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/common/redux/global/GlobalAction.kt | 3 - .../tap/common/redux/global/GlobalReducer.kt | 3 - .../tap/di/domain/SettingsDomainModule.kt | 6 + .../details/redux/DetailsMiddleware.kt | 2 - .../tangem/tap/features/home/HomeFragment.kt | 37 +++-- .../tangem/tap/features/home/HomeViewModel.kt | 131 ++++++++++++++++++ .../home/featuretoggles/HomeFeatureToggles.kt | 12 ++ .../tap/features/home/redux/HomeAction.kt | 13 +- .../tap/features/home/redux/HomeMiddleware.kt | 5 +- .../tap/features/home/redux/HomeReducer.kt | 7 - .../configs/feature_toggles_config.json | 4 + .../settings/ShouldSaveAccessCodesUseCase.kt | 8 ++ 12 files changed, 194 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt create mode 100644 app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt create mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/ShouldSaveAccessCodesUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index e7fba06fb7..c35ddefdd8 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -5,7 +5,6 @@ import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.models.ChatConfig import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.StateDialog import com.tangem.tap.common.feedback.FeedbackData @@ -95,6 +94,4 @@ sealed class GlobalAction : Action { object FetchUserCountry : GlobalAction() { data class Success(val countryCode: String) : GlobalAction() } - - data class ChangeAppThemeMode(val appThemeMode: AppThemeMode) : GlobalAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index d85d35f457..043426c2de 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -92,9 +92,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde userCountryCode = action.countryCode, ) } - is GlobalAction.ChangeAppThemeMode -> globalState.copy( - appThemeMode = action.appThemeMode, - ) else -> globalState } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 7ff162a0d1..af14718da8 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -221,5 +221,11 @@ internal object SettingsDomainModule { ): NeverRequestPermissionUseCase { return NeverRequestPermissionUseCase(repository = permissionRepository) } + + @Provides + @Singleton + fun provideShouldSaveAccessCodesUseCase(settingsRepository: SettingsRepository): ShouldSaveAccessCodesUseCase { + return ShouldSaveAccessCodesUseCase(settingsRepository = settingsRepository) + } // endregion } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 689263efaf..a72851ef35 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -134,8 +134,6 @@ class DetailsMiddleware { scope.launch { repository.changeAppThemeMode(appThemeMode) - - store.dispatchWithMain(GlobalAction.ChangeAppThemeMode(appThemeMode)) } } diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index f3fae68135..a39f6b2278 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -6,10 +6,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier +import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.Analytics - import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.SystemBarsIconsDisposable import com.tangem.core.ui.screen.ComposeFragment @@ -18,6 +18,7 @@ import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.home.compose.StoriesScreen +import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.store @@ -26,17 +27,21 @@ import org.rekotlin.StoreSubscriber import javax.inject.Inject @AndroidEntryPoint -class HomeFragment : ComposeFragment(), StoreSubscriber { +internal class HomeFragment : ComposeFragment(), StoreSubscriber { @Inject override lateinit var uiDependencies: UiDependencies + @Inject + lateinit var homeFeatureToggles: HomeFeatureToggles + private var homeState: MutableState = mutableStateOf(store.state.homeState) + private val viewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) store.dispatch(HomeAction.OnCreate) - store.dispatch(HomeAction.Init) } @Composable @@ -73,17 +78,29 @@ class HomeFragment : ComposeFragment(), StoreSubscriber { StoriesScreen( homeState = homeState, onScanButtonClick = { - Analytics.send(IntroductionProcess.ButtonScanCard()) - store.dispatch(action = HomeAction.ReadCard(scope = requireActivity().lifecycleScope)) + if (homeFeatureToggles.isCallbacksRefactoringEnabled) { + viewModel.onScanClick() + } else { + Analytics.send(IntroductionProcess.ButtonScanCard()) + store.dispatch(action = HomeAction.ReadCard(scope = requireActivity().lifecycleScope)) + } }, onShopButtonClick = { - Analytics.send(IntroductionProcess.ButtonBuyCards()) - store.dispatch(HomeAction.GoToShop(store.state.globalState.userCountryCode)) + if (homeFeatureToggles.isCallbacksRefactoringEnabled) { + viewModel.onShopClick() + } else { + Analytics.send(IntroductionProcess.ButtonBuyCards()) + store.dispatch(HomeAction.GoToShop) + } }, onSearchTokensClick = { - Analytics.send(IntroductionProcess.ButtonTokensList()) - store.dispatchNavigationAction { push(AppRoute.ManageTokens) } - store.dispatch(TokensAction.SetArgs.ReadAccess) + if (homeFeatureToggles.isCallbacksRefactoringEnabled) { + viewModel.onSearchClick() + } else { + Analytics.send(IntroductionProcess.ButtonTokensList()) + store.dispatchNavigationAction { push(AppRoute.ManageTokens) } + store.dispatch(TokensAction.SetArgs.ReadAccess) + } }, ) } diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt new file mode 100644 index 0000000000..f68dac30ca --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt @@ -0,0 +1,131 @@ +package com.tangem.tap.features.home + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tangem.common.routing.AppRoute +import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic +import com.tangem.core.navigation.url.UrlOpener +import com.tangem.domain.card.ScanCardProcessor +import com.tangem.domain.card.SetAccessCodeRequestPolicyUseCase +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.settings.ShouldSaveAccessCodesUseCase +import com.tangem.domain.tokens.TokensAction +import com.tangem.domain.wallets.builder.UserWalletBuilder +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase +import com.tangem.domain.wallets.usecase.SaveWalletUseCase +import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter +import com.tangem.tap.common.analytics.events.IntroductionProcess +import com.tangem.tap.common.analytics.events.Shop +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.common.extensions.onUserWalletSelected +import com.tangem.tap.features.home.redux.HIDE_PROGRESS_DELAY +import com.tangem.tap.features.home.redux.HomeAction +import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL +import com.tangem.tap.store +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList") +@HiltViewModel +internal class HomeViewModel @Inject constructor( + private val shouldSaveAccessCodesUseCase: ShouldSaveAccessCodesUseCase, + private val setAccessCodeRequestPolicyUseCase: SetAccessCodeRequestPolicyUseCase, + private val scanCardProcessor: ScanCardProcessor, + private val generateWalletNameUseCase: GenerateWalletNameUseCase, + private val saveWalletUseCase: SaveWalletUseCase, + private val urlOpener: UrlOpener, + private val analyticsEventHandler: AnalyticsEventHandler, +) : ViewModel() { + + fun onScanClick() { + analyticsEventHandler.send(IntroductionProcess.ButtonScanCard()) + scanCard() + } + + fun onShopClick() { + analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards()) + analyticsEventHandler.send(Shop.ScreenOpened()) + + urlOpener.openUrl(NEW_BUY_WALLET_URL) + } + + fun onSearchClick() { + analyticsEventHandler.send(IntroductionProcess.ButtonTokensList()) + + store.dispatch(TokensAction.SetArgs.ReadAccess) + store.dispatchNavigationAction { push(AppRoute.ManageTokens) } + } + + private fun scanCard() { + viewModelScope.launch { + setAccessCodeRequestPolicyUseCase(isBiometricsRequestPolicy = shouldSaveAccessCodesUseCase()) + + scanCardProcessor.scan( + analyticsSource = AnalyticsParam.ScreensSources.Intro, + onProgressStateChange = { showProgress -> + if (showProgress) { + store.dispatch(HomeAction.ScanInProgress(scanInProgress = true)) + } else { + delay(HIDE_PROGRESS_DELAY) + store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) + } + }, + onFailure = { + Timber.e(it, "Unable to scan card") + delay(HIDE_PROGRESS_DELAY) + store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) + }, + onSuccess = ::proceedWithScanResponse, + ) + } + } + + private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) { + val userWallet = UserWalletBuilder( + scanResponse = scanResponse, + generateWalletNameUseCase = generateWalletNameUseCase, + ).build() + + if (userWallet == null) { + Timber.e("User wallet not created") + return + } + + saveWalletUseCase(userWallet).fold( + ifLeft = { Timber.e(it.toString(), "Unable to save user wallet") }, + ifRight = { + sendSignedInCardAnalyticsEvent(scanResponse) + coroutineScope { store.onUserWalletSelected(userWallet = userWallet) } + }, + ) + + store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) + delay(HIDE_PROGRESS_DELAY) + + store.dispatchNavigationAction { push(AppRoute.Wallet) } + } + + private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) { + val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver) + + if (currency != null) { + Analytics.send( + event = Basic.SignedIn( + currency = currency, + batch = scanResponse.card.batchId, + signInType = Basic.SignedIn.SignInType.Card, + walletsCount = "1", + hasBackup = scanResponse.card.backupStatus?.isActive, + ), + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt new file mode 100644 index 0000000000..8d23463a53 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.features.home.featuretoggles + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import javax.inject.Inject + +internal class HomeFeatureToggles @Inject constructor( + private val featureTogglesManager: FeatureTogglesManager, +) { + + val isCallbacksRefactoringEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "HOME_SCREEN_CALLBACKS_REFACTORING_ENABLED") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index 2266971dad..f0171194c2 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -5,22 +5,17 @@ import org.rekotlin.Action sealed class HomeAction : Action { - object OnCreate : HomeAction() - object Init : HomeAction() - - data class InsertStory(val position: Int, val story: Stories) : HomeAction() + data object OnCreate : HomeAction() /** * Action for scanning card * - * @property scope lifecycle scope. It will be canceled when lifecycle-aware component is destroyed + * @property scope lifecycle scope. It will be canceled when lifecycle-aware component is destroyed */ - data class ReadCard( - val scope: CoroutineScope, - ) : HomeAction() + data class ReadCard(val scope: CoroutineScope) : HomeAction() data class ScanInProgress(val scanInProgress: Boolean) : HomeAction() - data class GoToShop(val userCountryCode: String?) : HomeAction() + data object GoToShop : HomeAction() data class UpdateCountryCode(val userCountryCode: String) : HomeAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 203a2f983f..a2a3757628 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -27,7 +27,7 @@ import org.rekotlin.Action import org.rekotlin.Middleware import timber.log.Timber -private const val HIDE_PROGRESS_DELAY = 400L +internal const val HIDE_PROGRESS_DELAY = 400L object HomeMiddleware { val handler = homeMiddleware @@ -49,8 +49,7 @@ private fun handleHomeAction(action: Action) { is HomeAction.OnCreate -> { Analytics.eraseContext() Analytics.send(IntroductionProcess.ScreenOpened()) - } - is HomeAction.Init -> { + store.dispatch(GlobalAction.RestoreAppCurrency) store.dispatch(GlobalAction.ExchangeManager.Init) store.dispatch(GlobalAction.FetchUserCountry) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index bc71e1fef2..f9d977fdec 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -12,13 +12,6 @@ private fun internalReduce(action: Action, appState: AppState): HomeState { var state = appState.homeState when (action) { - is HomeAction.InsertStory -> { - state = state.copy( - stories = state.stories.toMutableList().apply { - add(action.position, action.story) - }, - ) - } is HomeAction.ScanInProgress -> { state = state.copy(scanInProgress = action.scanInProgress) } diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 7def5136f7..7a5cc57245 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -38,5 +38,9 @@ { "name": "MARKETS_ENABLED", "version": "undefined" + }, + { + "name": "HOME_SCREEN_CALLBACKS_REFACTORING_ENABLED", + "version": "5.14.0" } ] diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldSaveAccessCodesUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldSaveAccessCodesUseCase.kt new file mode 100644 index 0000000000..b68ede4106 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldSaveAccessCodesUseCase.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.settings + +import com.tangem.domain.settings.repositories.SettingsRepository + +class ShouldSaveAccessCodesUseCase(private val settingsRepository: SettingsRepository) { + + suspend operator fun invoke(): Boolean = settingsRepository.shouldSaveAccessCodes() +} \ No newline at end of file