Updated on 2026-08-14
This commit is contained in:
commit
2baa5365b4
94 changed files with 705 additions and 1924 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 328c13826f7e52ba783db1ba39de5b17b1b79bc5
|
||||
Subproject commit 5b216eeca70af8b9fa7b266d4d0c0f87f0c042c8
|
||||
|
|
@ -718,6 +718,26 @@
|
|||
"networkId": "sei-network/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "energy-web-chain",
|
||||
"name": "Energy Web Chain",
|
||||
"symbol": "EWT",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "energy-web-chain/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "energy-web-x",
|
||||
"name": "Energy Web X",
|
||||
"symbol": "EWT",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "energy-web-x/test"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,17 +24,17 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
|||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -106,8 +106,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getSaveBlockchainErrorUseCase(): SaveBlockchainErrorUseCase
|
||||
|
||||
fun getDetailsFeatureToggles(): DetailsFeatureToggles
|
||||
|
||||
fun getGetCardInfoUseCase(): GetCardInfoUseCase
|
||||
|
||||
fun getUrlOpener(): UrlOpener
|
||||
|
|
@ -116,7 +114,9 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getAppRouter(): AppRouter
|
||||
|
||||
fun getPushNotificationsFeatureToggles(): PushNotificationsFeatureToggles
|
||||
|
||||
fun getTangemAppLogger(): TangemAppLoggerInitializer
|
||||
|
||||
fun getHomeFeatureToggles(): HomeFeatureToggles
|
||||
|
||||
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
|
||||
}
|
||||
|
|
@ -65,7 +65,6 @@ import com.tangem.tap.common.OnActivityResultCallback
|
|||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss
|
||||
import com.tangem.tap.common.redux.NotificationsHandler
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
|
|
@ -78,7 +77,6 @@ import com.tangem.tap.features.main.model.Toast
|
|||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.routing.RoutingComponent
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||
|
|
@ -539,11 +537,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
} else {
|
||||
lifecycleScope.launch {
|
||||
val toggles = store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles)
|
||||
val isPushPermissionEnabled = toggles.isPushNotificationsEnabled
|
||||
val shouldShowTos = !cardRepository.isTangemTOSAccepted() && isPushPermissionEnabled
|
||||
val wasPushInitiallyAsked = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false }
|
||||
val shouldShowInitialPush = wasPushInitiallyAsked && isPushPermissionEnabled
|
||||
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
|
||||
val shouldShowInitialPush = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false }
|
||||
|
||||
val route = when {
|
||||
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
||||
|
|
|
|||
|
|
@ -37,14 +37,13 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
|||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
|
|
@ -60,6 +59,7 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.domain.tasks.product.DerivationsFinder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
|
|
@ -174,9 +174,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val getCardInfoUseCase: GetCardInfoUseCase
|
||||
get() = entryPoint.getGetCardInfoUseCase()
|
||||
|
||||
private val detailsFeatureToggles: DetailsFeatureToggles
|
||||
get() = entryPoint.getDetailsFeatureToggles()
|
||||
|
||||
private val urlOpener
|
||||
get() = entryPoint.getUrlOpener()
|
||||
|
||||
|
|
@ -186,11 +183,14 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val appRouter: AppRouter
|
||||
get() = entryPoint.getAppRouter()
|
||||
|
||||
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles
|
||||
get() = entryPoint.getPushNotificationsFeatureToggles()
|
||||
|
||||
private val tangemAppLoggerInitializer: TangemAppLoggerInitializer
|
||||
get() = entryPoint.getTangemAppLogger()
|
||||
|
||||
private val homeFeatureToggles: HomeFeatureToggles
|
||||
get() = entryPoint.getHomeFeatureToggles()
|
||||
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase
|
||||
get() = entryPoint.getGetUserCountryCodeUseCase()
|
||||
// endregion
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -268,11 +268,11 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
getFeedbackEmailUseCase = getFeedbackEmailUseCase,
|
||||
getCardInfoUseCase = getCardInfoUseCase,
|
||||
assetLoader = assetLoader,
|
||||
detailsFeatureToggles = detailsFeatureToggles,
|
||||
urlOpener = urlOpener,
|
||||
shareManager = shareManager,
|
||||
appRouter = appRouter,
|
||||
pushNotificationsFeatureToggles = pushNotificationsFeatureToggles,
|
||||
homeFeatureToggles = homeFeatureToggles,
|
||||
getUserCountryUseCase = getUserCountryUseCase,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.features.details.ui.details.SocialNetwork
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -14,20 +13,8 @@ sealed class Settings(
|
|||
) : AnalyticsEvent(category, event, params, error) {
|
||||
|
||||
class ScreenOpened : Settings(event = "Settings Screen Opened")
|
||||
class ButtonChat : Settings(event = "Button - Chat")
|
||||
class ButtonSendFeedback : Settings(event = "Button - Send Feedback")
|
||||
class ButtonStartWalletConnectSession : Settings(event = "Button - Start Wallet Connect Session")
|
||||
class ButtonStopWalletConnectSession : Settings(event = "Button - Stop Wallet Connect Session")
|
||||
class ButtonCardSettings : Settings(event = "Button - Card Settings")
|
||||
class ButtonAppSettings : Settings(event = "Button - App Settings")
|
||||
class ButtonCreateBackup : Settings(event = "Button - Create Backup")
|
||||
class ButtonWalletConnect : Settings(event = "Button - Wallet Connect")
|
||||
object ScanNewCard : Settings(event = "Button - Scan New Card")
|
||||
|
||||
class ButtonSocialNetwork(network: SocialNetwork) : Settings(
|
||||
event = "Button - Social Network",
|
||||
params = mapOf("Network" to network.id),
|
||||
)
|
||||
|
||||
sealed class CardSettings(
|
||||
event: String,
|
||||
|
|
@ -63,8 +50,6 @@ sealed class Settings(
|
|||
error = error,
|
||||
)
|
||||
|
||||
class AccessCodeRecoveryButton : CardSettings("Button - Access Code Recovery")
|
||||
|
||||
class AccessCodeRecoveryChanged(status: AnalyticsParam.AccessCodeRecoveryStatus) : CardSettings(
|
||||
event = "Access Code Recovery Changed",
|
||||
params = mapOf(status.key to status.value),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.common.redux
|
|||
import com.tangem.tap.common.redux.global.globalReducer
|
||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
|
||||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer
|
||||
|
|
@ -28,7 +27,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
|||
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
|
||||
twinCardsState = TwinCardsReducer.reduce(action, state),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
disclaimerState = DisclaimerReducer.reduce(action, state),
|
||||
tokensState = TokensReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import com.tangem.tap.features.details.redux.DetailsMiddleware
|
|||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerMiddleware
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware
|
||||
|
|
@ -43,7 +41,6 @@ data class AppState(
|
|||
val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
|
||||
val twinCardsState: TwinCardsState = TwinCardsState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val disclaimerState: DisclaimerState = DisclaimerState(),
|
||||
val tokensState: TokensState = TokensState(),
|
||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
|
|
@ -75,7 +72,6 @@ data class AppState(
|
|||
OnboardingOtherCardsMiddleware.handler,
|
||||
TwinCardsMiddleware.handler,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
DisclaimerMiddleware().disclaimerMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
WelcomeMiddleware().middleware,
|
||||
|
|
|
|||
|
|
@ -92,21 +92,24 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
scope.launch { exchangeManager.update() }
|
||||
}
|
||||
is GlobalAction.FetchUserCountry -> {
|
||||
scope.launch {
|
||||
// TODO("After adding DI") get dependencies by DI
|
||||
runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() }
|
||||
.onSuccess {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()),
|
||||
)
|
||||
}
|
||||
.onFailure {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.FetchUserCountry.Success(
|
||||
countryCode = Locale.getDefault().country.lowercase(),
|
||||
),
|
||||
)
|
||||
}
|
||||
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
|
||||
|
||||
if (!homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
|
||||
scope.launch {
|
||||
runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() }
|
||||
.onSuccess {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()),
|
||||
)
|
||||
}
|
||||
.onFailure {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.FetchUserCountry.Success(
|
||||
countryCode = Locale.getDefault().country.lowercase(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,9 @@ package com.tangem.tap.common.redux.global
|
|||
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.domain.redux.global.DomainGlobalAction
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.extensions.replaceBy
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -86,10 +83,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
|||
is GlobalAction.SetIfCardVerifiedOnline ->
|
||||
globalState.copy(cardVerifiedOnline = action.verified)
|
||||
is GlobalAction.FetchUserCountry.Success -> {
|
||||
store.dispatchOnMain(HomeAction.UpdateCountryCode(action.countryCode))
|
||||
globalState.copy(
|
||||
userCountryCode = action.countryCode,
|
||||
)
|
||||
globalState.copy(userCountryCode = action.countryCode)
|
||||
}
|
||||
else -> globalState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ data class GlobalState(
|
|||
val scanCardFailsCounter: Int = 0,
|
||||
val dialog: StateDialog? = null,
|
||||
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
|
||||
@Deprecated("Use GetUserCountryCodeUseCase instead")
|
||||
val userCountryCode: String? = null,
|
||||
val appThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
|
||||
) : StateType
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import com.tangem.domain.settings.repositories.AppRatingRepository
|
|||
import com.tangem.domain.settings.repositories.PermissionRepository
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
|
||||
import dagger.Module
|
||||
|
|
@ -212,4 +214,16 @@ internal object SettingsDomainModule {
|
|||
return ShouldSaveAccessCodesUseCase(settingsRepository = settingsRepository)
|
||||
}
|
||||
// endregion
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchUserCountryCodeUseCase(settingsRepository: SettingsRepository): FetchUserCountryUseCase {
|
||||
return FetchUserCountryUseCase(settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetUserCountryCodeUseCase(settingsRepository: SettingsRepository): GetUserCountryUseCase {
|
||||
return GetUserCountryUseCase(settingsRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,8 +13,6 @@ import com.tangem.operations.attestation.Attestation
|
|||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -62,7 +60,6 @@ class TapWalletManager(
|
|||
updateConfigManager(scanResponse)
|
||||
withMainContext {
|
||||
// Order is important
|
||||
store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer()))
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
|
||||
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.domain.scanCard
|
|||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.routing.AppRoute
|
||||
|
|
@ -18,9 +17,6 @@ import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
|
|||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
|
|
@ -102,6 +98,8 @@ internal object LegacyScanProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private suspend inline fun showDisclaimerIfNeed(
|
||||
scanResponse: ScanResponse,
|
||||
crossinline disclaimerWillShow: () -> Unit = {},
|
||||
|
|
@ -109,7 +107,6 @@ internal object LegacyScanProcessor {
|
|||
crossinline onFailure: suspend (error: TangemError) -> Unit,
|
||||
) {
|
||||
val disclaimer = scanResponse.card.createDisclaimer()
|
||||
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
|
||||
|
||||
if (disclaimer.isAccepted()) {
|
||||
nextHandler(scanResponse)
|
||||
|
|
@ -120,23 +117,9 @@ internal object LegacyScanProcessor {
|
|||
withContext(Dispatchers.Main.immediate) {
|
||||
disclaimerWillShow()
|
||||
|
||||
store.dispatch(
|
||||
DisclaimerAction.Show(
|
||||
from = DisclaimerSource.Home,
|
||||
callback = DisclaimerCallback(
|
||||
onAccept = {
|
||||
scope.launch(Dispatchers.Main.immediate) {
|
||||
nextHandler(scanResponse)
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
scope.launch(Dispatchers.Main.immediate) {
|
||||
onFailure(TangemSdkError.UserCancelled())
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.Disclaimer(isTosAccepted = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,11 +59,7 @@ internal object UseCaseScanProcessor {
|
|||
),
|
||||
)
|
||||
add(AnalyticsChain(Basic.CardWasScanned(analyticsSource)))
|
||||
val pushNotificationsToggles =
|
||||
store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles)
|
||||
if (pushNotificationsToggles.isPushNotificationsEnabled) {
|
||||
add(DisclaimerChain(store, disclaimerWillShow))
|
||||
}
|
||||
add(DisclaimerChain(store, disclaimerWillShow))
|
||||
add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,15 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.core.chain.ResultChain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.rekotlin.Store
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
/**
|
||||
* Handles disclaimer display after the card scanning operation.
|
||||
|
|
@ -40,31 +34,13 @@ internal class DisclaimerChain(
|
|||
previousChainResult.right()
|
||||
} else {
|
||||
disclaimerWillShow()
|
||||
showDisclaimer(disclaimer, previousChainResult)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun showDisclaimer(disclaimer: Disclaimer, response: ScanResponse): ScanChainResult {
|
||||
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
|
||||
// TODO: [REDACTED_JIRA]
|
||||
store.dispatchNavigationAction {
|
||||
push(route = AppRoute.Disclaimer(isTosAccepted = false))
|
||||
}
|
||||
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
store.dispatchOnMain(
|
||||
DisclaimerAction.Show(
|
||||
from = DisclaimerSource.Home,
|
||||
callback = DisclaimerCallback(
|
||||
onAccept = {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(response.right())
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(ScanChainException.DisclaimerWasCanceled.left())
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -239,9 +239,8 @@ class WalletConnectSdkHelper {
|
|||
private suspend fun signTransaction(data: WcTransactionData, cardId: String?): String? {
|
||||
val dataToSign = EthereumUtils.buildTransactionToSign(
|
||||
transactionData = data.transaction,
|
||||
nonce = null,
|
||||
blockchain = data.walletManager.wallet.blockchain,
|
||||
) ?: return null
|
||||
)
|
||||
|
||||
val command = SignHashCommand(
|
||||
hash = dataToSign.hash,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -14,15 +13,6 @@ sealed class DetailsAction : Action {
|
|||
val shouldSaveUserWallets: Boolean,
|
||||
) : DetailsAction()
|
||||
|
||||
data object ScanAndSaveUserWallet : DetailsAction() {
|
||||
|
||||
data object Success : DetailsAction()
|
||||
|
||||
data class Error(val error: TextReference?) : DetailsAction()
|
||||
}
|
||||
|
||||
data object DismissError : DetailsAction()
|
||||
|
||||
sealed class AppSettings : DetailsAction() {
|
||||
data class SwitchPrivacySetting(
|
||||
val enable: Boolean,
|
||||
|
|
|
|||
|
|
@ -2,36 +2,24 @@ package com.tangem.tap.features.details.redux
|
|||
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
|
@ -41,7 +29,6 @@ import kotlinx.coroutines.launch
|
|||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
|
||||
|
||||
class DetailsMiddleware {
|
||||
private val appSettingsMiddleware = AppSettingsMiddleware()
|
||||
|
|
@ -62,7 +49,6 @@ class DetailsMiddleware {
|
|||
private fun handleAction(state: DetailsState, action: Action) {
|
||||
when (action) {
|
||||
is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, action)
|
||||
is DetailsAction.ScanAndSaveUserWallet -> scanAndSaveUserWallet()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,95 +264,4 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanAndSaveUserWallet() = scope.launch(Dispatchers.IO) {
|
||||
val cardSdkConfigRepository = store.inject(DaggerGraphState::cardSdkConfigRepository)
|
||||
|
||||
val prevUseBiometricsForAccessCode = cardSdkConfigRepository.isBiometricsRequestPolicy()
|
||||
|
||||
// Update access code policy for access code saving when a card was scanned
|
||||
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = shouldSaveAccessCodes)
|
||||
|
||||
store.inject(DaggerGraphState::scanCardProcessor).scan(
|
||||
analyticsSource = CoreAnalyticsParam.ScreensSources.Settings,
|
||||
onWalletNotCreated = {
|
||||
// No need to rollback policy, continue with the policy set before the card scan
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
|
||||
},
|
||||
disclaimerWillShow = {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
createUserWallet(scanResponse)
|
||||
.doOnSuccess {
|
||||
saveUserWalletAndPopBackToWalletScreen(
|
||||
userWallet = it,
|
||||
prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode,
|
||||
)
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to create user wallet")
|
||||
handleError(error = error, prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode)
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
Timber.e(error, "Unable to scan card")
|
||||
handleError(error = error, prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse): CompletionResult<UserWallet> {
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build()
|
||||
|
||||
return if (userWallet != null) {
|
||||
CompletionResult.Success(userWallet)
|
||||
} else {
|
||||
CompletionResult.Failure(TangemSdkError.WalletIsNotCreated())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveUserWalletAndPopBackToWalletScreen(
|
||||
userWallet: UserWallet,
|
||||
prevUseBiometricsForAccessCode: Boolean,
|
||||
) {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
userWalletsListManager.save(userWallet)
|
||||
.doOnSuccess {
|
||||
store.onUserWalletSelected(userWallet)
|
||||
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
if (error is UserWalletsListError.WalletAlreadySaved) {
|
||||
userWalletsListManager.select(userWallet.walletId)
|
||||
store.onUserWalletSelected(userWallet)
|
||||
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
} else {
|
||||
Timber.e(error, "Unable to create user wallet")
|
||||
handleError(error, prevUseBiometricsForAccessCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleError(error: TangemError, prevUseBiometricsForAccessCode: Boolean) {
|
||||
val cardSdkConfigRepository = store.inject(DaggerGraphState::cardSdkConfigRepository)
|
||||
|
||||
// Rollback policy if card saving was failed
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode)
|
||||
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Error(error.toTextReference()))
|
||||
}
|
||||
|
||||
private fun TangemError.toTextReference(): TextReference? {
|
||||
if (silent) return null
|
||||
|
||||
return messageResId?.let(::resourceReference) ?: stringReference(customMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
@ -21,7 +20,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
val detailsState = state.detailsState
|
||||
return when (action) {
|
||||
is DetailsAction.PrepareScreen -> {
|
||||
handlePrepareScreen(action, state)
|
||||
handlePrepareScreen(action)
|
||||
}
|
||||
is DetailsAction.AppSettings -> {
|
||||
handlePrivacyAction(action, detailsState)
|
||||
|
|
@ -31,26 +30,12 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
selectedAppCurrency = action.currency,
|
||||
),
|
||||
)
|
||||
is DetailsAction.ScanAndSaveUserWallet -> detailsState.copy(
|
||||
isScanningInProgress = true,
|
||||
)
|
||||
is DetailsAction.ScanAndSaveUserWallet.Error -> detailsState.copy(
|
||||
isScanningInProgress = false,
|
||||
error = action.error,
|
||||
)
|
||||
is DetailsAction.ScanAndSaveUserWallet.Success -> detailsState.copy(
|
||||
isScanningInProgress = false,
|
||||
)
|
||||
is DetailsAction.DismissError -> detailsState.copy(
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppState): DetailsState {
|
||||
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsState {
|
||||
return DetailsState(
|
||||
scanResponse = action.scanResponse,
|
||||
createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
appSettingsState = AppSettingsState(
|
||||
isBiometricsAvailable = runBlocking {
|
||||
tangemSdkManager.checkCanUseBiometry()
|
||||
|
|
|
|||
|
|
@ -1,27 +1,16 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DetailsState(
|
||||
@Deprecated("Delete after onboarding refactoring")
|
||||
val scanResponse: ScanResponse? = null,
|
||||
val privacyPolicyUrl: String? = null,
|
||||
val createBackupAllowed: Boolean = false,
|
||||
val isScanningInProgress: Boolean = false,
|
||||
val error: TextReference? = null,
|
||||
val appSettingsState: AppSettingsState = AppSettingsState(),
|
||||
) : StateType
|
||||
|
||||
sealed class ResetCardDialog {
|
||||
data object StartResetDialog : ResetCardDialog()
|
||||
data object ContinueResetDialog : ResetCardDialog()
|
||||
data object InterruptedResetDialog : ResetCardDialog()
|
||||
data object CompletedResetDialog : ResetCardDialog()
|
||||
}
|
||||
|
||||
data class AppSettingsState(
|
||||
val saveWallets: Boolean = false,
|
||||
val saveAccessCodes: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
|||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -45,7 +44,6 @@ internal class AppSettingsViewModel @Inject constructor(
|
|||
private val appThemeModeRepository: AppThemeModeRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
|
||||
private val detailsFeatureToggles: DetailsFeatureToggles,
|
||||
) : ViewModel(),
|
||||
StoreSubscriber<DetailsState>,
|
||||
DefaultLifecycleObserver {
|
||||
|
|
@ -62,9 +60,7 @@ internal class AppSettingsViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
bootstrapAppCurrencyUpdates()
|
||||
if (detailsFeatureToggles.isRedesignEnabled) {
|
||||
bootstrapBiometricsUpdates()
|
||||
}
|
||||
bootstrapBiometricsUpdates()
|
||||
|
||||
subscribeToStoreChanges()
|
||||
sendItemsAnalytics()
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState> {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var walletsRepository: WalletsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var userWalletsListManager: UserWalletsListManager
|
||||
|
||||
private lateinit var detailsViewModel: DetailsViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
detailsViewModel = DetailsViewModel(
|
||||
store = store,
|
||||
walletsRepository = walletsRepository,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
)
|
||||
Analytics.send(Settings.ScreenOpened())
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
DetailsScreen(
|
||||
modifier = modifier,
|
||||
state = detailsViewModel.detailsScreenState.value,
|
||||
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: DetailsState) {
|
||||
if (activity == null || view == null) return
|
||||
detailsViewModel.detailsScreenState.value = detailsViewModel.updateState(state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,288 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.details
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.ui.common.ScreenTitle
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun DetailsScreen(state: DetailsScreenState, onBackClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
SettingsScreensScaffold(
|
||||
modifier = modifier,
|
||||
snackbarHostState = snackbarHostState,
|
||||
content = { Content(state = state) },
|
||||
onBackClick = onBackClick,
|
||||
)
|
||||
|
||||
ShowSnackbarIfNeeded(
|
||||
snackbarHostState = snackbarHostState,
|
||||
messageEvent = state.showSnackbar,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: DetailsScreenState, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
ScreenTitle(titleRes = R.string.details_title)
|
||||
SpacerH(height = TangemTheme.dimens.spacing36)
|
||||
SettingsItems(
|
||||
items = state.elements,
|
||||
)
|
||||
SpacerHMax()
|
||||
TangemSocialAccounts(
|
||||
links = state.tangemLinks,
|
||||
onSocialNetworkClick = state.onSocialNetworkClick,
|
||||
)
|
||||
SpacerH(height = TangemTheme.dimens.spacing12)
|
||||
TangemAppVersion(
|
||||
appNameRes = state.appNameRes,
|
||||
version = state.tangemVersion,
|
||||
)
|
||||
SpacerH(height = TangemTheme.dimens.spacing16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsItems(items: List<SettingsItem>) {
|
||||
items.forEach { item ->
|
||||
if (item.isLarge) {
|
||||
LargeDetailsItem(item)
|
||||
} else {
|
||||
DetailsItem(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LargeDetailsItem(item: SettingsItem) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = item.onClick)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing20)
|
||||
.heightIn(min = TangemTheme.dimens.size84)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing20),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (item.showProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
color = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = item.iconResId),
|
||||
contentDescription = item.title.resolveReference(),
|
||||
tint = TangemColorPalette.Azure,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.heightIn(min = TangemTheme.dimens.size56),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
space = TangemTheme.dimens.spacing4,
|
||||
alignment = Alignment.CenterVertically,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = item.title.resolveReference(),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
if (item.subtitle != null) {
|
||||
Text(
|
||||
text = item.subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DetailsItem(item: SettingsItem) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(enabled = !item.showProgress, onClick = item.onClick)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing20)
|
||||
.heightIn(min = TangemTheme.dimens.size56)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing20),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (item.showProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
color = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = item.iconResId),
|
||||
contentDescription = item.title.resolveReference(),
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.SpaceAround,
|
||||
) {
|
||||
Text(
|
||||
text = item.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
if (item.subtitle != null) {
|
||||
Text(
|
||||
text = item.subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemSocialAccounts(links: List<SocialNetworkLink>, onSocialNetworkClick: (SocialNetworkLink) -> Unit) {
|
||||
LazyRow(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
items(links) {
|
||||
val onClick = remember(it) {
|
||||
{ onSocialNetworkClick(it) }
|
||||
}
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing4)
|
||||
.size(TangemTheme.dimens.size32),
|
||||
onClick = onClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = it.network.iconRes),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShowSnackbarIfNeeded(snackbarHostState: SnackbarHostState, messageEvent: StateEvent<TextReference>) {
|
||||
var message: TextReference? by remember { mutableStateOf(value = null) }
|
||||
val resolvedMessage by rememberUpdatedState(newValue = message?.resolveReference())
|
||||
|
||||
LaunchedEffect(resolvedMessage) {
|
||||
resolvedMessage?.let {
|
||||
snackbarHostState.showSnackbar(it)
|
||||
}
|
||||
}
|
||||
|
||||
EventEffect(messageEvent) {
|
||||
message = it
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemAppVersion(appNameRes: Int, version: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
text = "${stringResource(id = appNameRes)} $version",
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 900)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 900, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun DetailsScreenPreview(@PreviewParameter(DetailsScreenStateProvider::class) param: DetailsScreenState) {
|
||||
TangemThemePreview {
|
||||
DetailsScreen(param, onBackClick = {})
|
||||
}
|
||||
}
|
||||
|
||||
private class DetailsScreenStateProvider : CollectionPreviewParameterProvider<DetailsScreenState>(
|
||||
collection = buildList {
|
||||
DetailsScreenState(
|
||||
elements = buildList {
|
||||
SettingsItem.WalletConnect({}).let(::add)
|
||||
SettingsItem.AddWallet(showProgress = false, {}).let(::add)
|
||||
SettingsItem.LinkMoreCards({}).let(::add)
|
||||
SettingsItem.CardSettings({}).let(::add)
|
||||
SettingsItem.AppSettings({}).let(::add)
|
||||
// removed chat in task [REDACTED_TASK_KEY]
|
||||
// SettingsItem.Chat({}).let(::add)
|
||||
SettingsItem.SendFeedback({}).let(::add)
|
||||
SettingsItem.ReferralProgram({}).let(::add)
|
||||
SettingsItem.TermsOfService({}).let(::add)
|
||||
}.toImmutableList(),
|
||||
tangemLinks = TangemSocialAccounts.accountsEn,
|
||||
tangemVersion = "Tangem 2.14.12 (343)",
|
||||
showSnackbar = consumedEvent(),
|
||||
onSocialNetworkClick = {},
|
||||
).let(::add)
|
||||
|
||||
DetailsScreenState(
|
||||
elements = buildList {
|
||||
SettingsItem.WalletConnect({}).let(::add)
|
||||
SettingsItem.AddWallet(showProgress = true, {}).let(::add)
|
||||
SettingsItem.CardSettings({}).let(::add)
|
||||
SettingsItem.AppSettings({}).let(::add)
|
||||
// removed chat in task [REDACTED_TASK_KEY]
|
||||
// SettingsItem.Chat({}).let(::add)
|
||||
SettingsItem.SendFeedback({}).let(::add)
|
||||
SettingsItem.TermsOfService({}).let(::add)
|
||||
}.toImmutableList(),
|
||||
tangemLinks = TangemSocialAccounts.accountsRu,
|
||||
tangemVersion = "Tangem 2.14.12 (343)",
|
||||
showSnackbar = consumedEvent(),
|
||||
onSocialNetworkClick = {},
|
||||
).let(::add)
|
||||
},
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.details
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Immutable
|
||||
internal data class DetailsScreenState(
|
||||
val elements: ImmutableList<SettingsItem>,
|
||||
val tangemLinks: ImmutableList<SocialNetworkLink>,
|
||||
val tangemVersion: String,
|
||||
val showSnackbar: StateEvent<TextReference>,
|
||||
val onSocialNetworkClick: (SocialNetworkLink) -> Unit,
|
||||
) {
|
||||
val appNameRes: Int = R.string.tangem_app_name
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal sealed class SettingsItem(
|
||||
@DrawableRes val iconResId: Int,
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference? = null,
|
||||
val isLarge: Boolean = false,
|
||||
) {
|
||||
|
||||
abstract val onClick: () -> Unit
|
||||
|
||||
open val showProgress: Boolean = false
|
||||
|
||||
data class WalletConnect(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_wallet_connect_24,
|
||||
title = resourceReference(R.string.wallet_connect_title),
|
||||
subtitle = resourceReference(R.string.wallet_connect_subtitle),
|
||||
isLarge = true,
|
||||
)
|
||||
|
||||
data class AddWallet(
|
||||
override val showProgress: Boolean,
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
title = resourceReference(R.string.user_wallet_list_add_button),
|
||||
)
|
||||
|
||||
data class ScanWallet(
|
||||
override val showProgress: Boolean,
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
title = resourceReference(R.string.scan_card_settings_button),
|
||||
)
|
||||
|
||||
data class LinkMoreCards(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_more_cards_24,
|
||||
title = resourceReference(R.string.details_row_title_create_backup),
|
||||
)
|
||||
|
||||
data class CardSettings(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_card_settings_24,
|
||||
title = resourceReference(R.string.card_settings_title),
|
||||
)
|
||||
|
||||
data class AppSettings(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_settings,
|
||||
title = resourceReference(R.string.app_settings_title),
|
||||
)
|
||||
|
||||
data class Chat(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_chat_24,
|
||||
title = resourceReference(R.string.details_chat),
|
||||
)
|
||||
|
||||
data class SendFeedback(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_comment_24,
|
||||
title = resourceReference(R.string.details_row_title_contact_to_support),
|
||||
)
|
||||
|
||||
data class ReferralProgram(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_add_friends_24,
|
||||
title = resourceReference(R.string.details_referral_title),
|
||||
)
|
||||
|
||||
data class TermsOfService(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_text_24,
|
||||
title = resourceReference(R.string.disclaimer_title),
|
||||
)
|
||||
|
||||
data class TesterMenu(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_alert_24,
|
||||
title = resourceReference(R.string.tester_menu),
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal data class SocialNetworkLink(
|
||||
val network: SocialNetwork,
|
||||
val url: String,
|
||||
)
|
||||
|
||||
internal sealed class EventError {
|
||||
object Empty : EventError()
|
||||
}
|
||||
|
||||
sealed class SocialNetwork(val id: String, val iconRes: Int) {
|
||||
object Twitter : SocialNetwork("Twitter", R.drawable.ic_twitter_24)
|
||||
object Telegram : SocialNetwork("Telegram", R.drawable.ic_telegram_24)
|
||||
object Discord : SocialNetwork("Discord", R.drawable.ic_discord_24)
|
||||
object Reddit : SocialNetwork("Reddit", R.drawable.ic_reddit_24)
|
||||
object Instagram : SocialNetwork("Instagram", R.drawable.ic_instagram_24)
|
||||
object GitHub : SocialNetwork("GitHub", R.drawable.ic_github_24)
|
||||
object Facebook : SocialNetwork("Facebook", R.drawable.ic_facebook_24)
|
||||
object LinkedIn : SocialNetwork("LinkedIn", R.drawable.ic_linkedin_24)
|
||||
object YouTube : SocialNetwork("YouTube", R.drawable.ic_youtube_24)
|
||||
}
|
||||
|
||||
internal object TangemSocialAccounts {
|
||||
val accountsEn: ImmutableList<SocialNetworkLink> = persistentListOf(
|
||||
SocialNetworkLink(SocialNetwork.Twitter, "https://x.com/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/tangem_chat"),
|
||||
SocialNetworkLink(SocialNetwork.Discord, "https://discord.gg/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Reddit, "https://www.reddit.com/r/Tangem/"),
|
||||
SocialNetworkLink(SocialNetwork.Instagram, "https://www.instagram.com/tangemwallet"),
|
||||
SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Facebook, "https://www.facebook.com/tangemwallet"),
|
||||
SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.YouTube, "https://youtube.com/@tangem_official"),
|
||||
)
|
||||
val accountsRu: ImmutableList<SocialNetworkLink> = persistentListOf(
|
||||
SocialNetworkLink(SocialNetwork.Twitter, "https://x.com/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/tangem_chat_ru"),
|
||||
SocialNetworkLink(SocialNetwork.Discord, "https://discord.gg/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Reddit, "https://www.reddit.com/r/Tangem/"),
|
||||
SocialNetworkLink(SocialNetwork.Instagram, "https://www.instagram.com/tangemwallet"),
|
||||
SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Facebook, "https://www.facebook.com/tangemwallet"),
|
||||
SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.YouTube, "https://youtube.com/@tangem_official"),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.details
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DetailsViewModel(
|
||||
private val store: Store<AppState>,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
) {
|
||||
|
||||
var detailsScreenState: MutableState<DetailsScreenState> = mutableStateOf(updateState(store.state.detailsState))
|
||||
private set
|
||||
|
||||
init {
|
||||
bootstrapScreenState()
|
||||
}
|
||||
|
||||
fun updateState(state: DetailsState): DetailsScreenState {
|
||||
return DetailsScreenState(
|
||||
elements = createSettingsItems(state),
|
||||
tangemLinks = getSocialLinks(),
|
||||
tangemVersion = getTangemAppVersion(),
|
||||
showSnackbar = triggerErrorSnackbarIfNeeded(state.error),
|
||||
onSocialNetworkClick = ::handleSocialNetworkClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSettingsItems(state: DetailsState): ImmutableList<SettingsItem> {
|
||||
val scanResponse = state.scanResponse ?: return persistentListOf()
|
||||
val cardTypesResolver = scanResponse.cardTypesResolver
|
||||
|
||||
return buildList {
|
||||
SettingsItem.WalletConnect(::navigateToWalletConnect)
|
||||
.takeIf { cardTypesResolver.isMultiwalletAllowed() }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.AddWallet(showProgress = state.isScanningInProgress, ::scanAndSaveUserWallet)
|
||||
.takeIf { state.appSettingsState.saveWallets }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.ScanWallet(showProgress = state.isScanningInProgress, ::scanAndSaveUserWallet)
|
||||
.takeUnless { state.appSettingsState.saveWallets }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.LinkMoreCards(::linkMoreCards)
|
||||
.takeIf { state.createBackupAllowed }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.CardSettings(::navigateToCardSettings)
|
||||
.let(::add)
|
||||
|
||||
SettingsItem.AppSettings(::navigateToAppSettings)
|
||||
.let(::add)
|
||||
|
||||
SettingsItem.SendFeedback(::sendFeedback)
|
||||
.let(::add)
|
||||
|
||||
SettingsItem.ReferralProgram(::navigateToReferralProgram)
|
||||
.takeIf { cardTypesResolver.isTangemWallet() }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.TermsOfService(::navigateToToS)
|
||||
.let(::add)
|
||||
|
||||
SettingsItem.TesterMenu(::navigateToTesterMenu)
|
||||
.takeIf { BuildConfig.TESTER_MENU_ENABLED }
|
||||
?.let(::add)
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
private fun triggerErrorSnackbarIfNeeded(text: TextReference?): StateEvent<TextReference> {
|
||||
return if (text == null) {
|
||||
consumedEvent()
|
||||
} else {
|
||||
triggeredEvent(text) {
|
||||
store.dispatch(DetailsAction.DismissError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTangemAppVersion(): String {
|
||||
val versionCode: Int = BuildConfig.VERSION_CODE
|
||||
val versionName: String = BuildConfig.VERSION_NAME
|
||||
return "$versionName ($versionCode)"
|
||||
}
|
||||
|
||||
private fun navigateToTesterMenu() {
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.TesterMenu)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToToS() {
|
||||
store.dispatchOnMain(DisclaimerAction.Show(DisclaimerSource.Details))
|
||||
}
|
||||
|
||||
private fun navigateToReferralProgram() {
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync
|
||||
?: error("Selected wallet must be not null")
|
||||
|
||||
store.dispatchNavigationAction { push(AppRoute.ReferralProgram(userWallet.walletId)) }
|
||||
}
|
||||
|
||||
private fun sendFeedback() {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Settings))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = FeedbackEmail(),
|
||||
scanResponse = userWalletsListManager.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateToAppSettings() {
|
||||
Analytics.send(Settings.ButtonAppSettings())
|
||||
store.dispatchNavigationAction { push(AppRoute.AppSettings) }
|
||||
}
|
||||
|
||||
private fun navigateToCardSettings() {
|
||||
val userWalletId = userWalletsListManager.selectedUserWalletSync?.walletId
|
||||
?: error("UserWalletId must be not null")
|
||||
Analytics.send(Settings.ButtonCardSettings())
|
||||
store.dispatchNavigationAction { push(AppRoute.CardSettings(userWalletId)) }
|
||||
}
|
||||
|
||||
private fun linkMoreCards() {
|
||||
Analytics.send(Settings.ButtonCreateBackup())
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to backup wallet, no user wallet selected")
|
||||
return
|
||||
}
|
||||
val scanResponse = selectedUserWallet.scanResponse
|
||||
Analytics.addContext(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
|
||||
}
|
||||
|
||||
private fun scanAndSaveUserWallet() {
|
||||
Analytics.send(Settings.ScanNewCard)
|
||||
store.dispatchOnMain(DetailsAction.ScanAndSaveUserWallet)
|
||||
}
|
||||
|
||||
private fun navigateToWalletConnect() {
|
||||
Analytics.send(Settings.ButtonWalletConnect())
|
||||
store.dispatchNavigationAction { push(AppRoute.WalletConnectSessions) }
|
||||
}
|
||||
|
||||
private fun handleSocialNetworkClick(link: SocialNetworkLink) {
|
||||
Analytics.send(Settings.ButtonSocialNetwork(link.network))
|
||||
store.dispatchOpenUrl(link.url)
|
||||
}
|
||||
|
||||
private fun getSocialLinks(): ImmutableList<SocialNetworkLink> {
|
||||
val locale = LocaleRegionProvider().getRegion()
|
||||
return if (locale.lowercase() == RUSSIA_COUNTRY_CODE) {
|
||||
TangemSocialAccounts.accountsRu
|
||||
} else {
|
||||
TangemSocialAccounts.accountsEn
|
||||
}
|
||||
}
|
||||
|
||||
private fun bootstrapScreenState() {
|
||||
userWalletsListManager.selectedUserWallet
|
||||
.distinctUntilChanged()
|
||||
.onEach { selectedUserWallet ->
|
||||
store.dispatchWithMain(
|
||||
DetailsAction.PrepareScreen(
|
||||
scanResponse = selectedUserWallet.scanResponse,
|
||||
shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||
),
|
||||
)
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard
|
||||
|
||||
internal sealed class ResetCardDialog {
|
||||
data object StartResetDialog : ResetCardDialog()
|
||||
data object ContinueResetDialog : ResetCardDialog()
|
||||
data object InterruptedResetDialog : ResetCardDialog()
|
||||
data object CompletedResetDialog : ResetCardDialog()
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
|||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.features.details.redux.ResetCardDialog
|
||||
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
|
||||
import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription
|
||||
import com.tangem.tap.store
|
||||
|
|
|
|||
|
|
@ -24,12 +24,6 @@ abstract class BaseDisclaimer(
|
|||
override suspend fun isAccepted(): Boolean = dataProvider.isAccepted()
|
||||
}
|
||||
|
||||
class DummyDisclaimer : Disclaimer {
|
||||
override fun getUri(): Uri = Uri.parse("https://tangem.com/tangem_tos.html")
|
||||
override suspend fun accept() {}
|
||||
override suspend fun isAccepted(): Boolean = false
|
||||
}
|
||||
|
||||
class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) {
|
||||
override fun getUri(): Uri = Uri.parse("$baseUrl/tangem_tos.html")
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class DisclaimerAction : Action {
|
||||
|
||||
data class SetDisclaimer(val disclaimer: Disclaimer) : DisclaimerAction()
|
||||
|
||||
data class Show(
|
||||
val from: DisclaimerSource,
|
||||
val callback: DisclaimerCallback? = null,
|
||||
) : DisclaimerAction()
|
||||
|
||||
object AcceptDisclaimer : DisclaimerAction()
|
||||
object OnBackPressed : DisclaimerAction()
|
||||
|
||||
data class OnProgressStateChanged(val state: ProgressState?) : DisclaimerAction()
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class DisclaimerMiddleware {
|
||||
val disclaimerMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
state()?.let { handleDisclaimerMiddleware(action, it) }
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDisclaimerMiddleware(action: Action, appState: AppState) {
|
||||
val state = appState.disclaimerState
|
||||
|
||||
when (action) {
|
||||
is DisclaimerAction.Show -> {
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.Disclaimer(isTosAccepted = action.from == DisclaimerSource.Details))
|
||||
}
|
||||
}
|
||||
is DisclaimerAction.AcceptDisclaimer -> {
|
||||
mainScope.launch {
|
||||
state.disclaimer.accept()
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
state.callback?.onAccept?.invoke()
|
||||
}
|
||||
}
|
||||
is DisclaimerAction.OnBackPressed -> {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
state.callback?.onDismiss?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
object DisclaimerReducer {
|
||||
fun reduce(action: Action, state: AppState): DisclaimerState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, state: AppState): DisclaimerState {
|
||||
if (action !is DisclaimerAction) return state.disclaimerState
|
||||
|
||||
val disclaimerState = state.disclaimerState
|
||||
|
||||
return when (action) {
|
||||
is DisclaimerAction.SetDisclaimer -> disclaimerState.copy(
|
||||
disclaimer = action.disclaimer,
|
||||
)
|
||||
is DisclaimerAction.Show -> disclaimerState.copy(
|
||||
showedFrom = action.from,
|
||||
callback = action.callback,
|
||||
)
|
||||
is DisclaimerAction.AcceptDisclaimer, is DisclaimerAction.OnBackPressed -> disclaimerState.copy(
|
||||
callback = null,
|
||||
progressState = null,
|
||||
)
|
||||
is DisclaimerAction.OnProgressStateChanged -> disclaimerState.copy(
|
||||
progressState = action.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
enum class DisclaimerSource {
|
||||
Home, Details
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.DummyDisclaimer
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DisclaimerState(
|
||||
val disclaimer: Disclaimer = DummyDisclaimer(),
|
||||
val showedFrom: DisclaimerSource = DisclaimerSource.Home,
|
||||
val callback: DisclaimerCallback? = null,
|
||||
val progressState: ProgressState? = null,
|
||||
) : StateType
|
||||
|
||||
data class DisclaimerCallback(
|
||||
val onAccept: VoidCallback? = null,
|
||||
val onDismiss: VoidCallback? = null,
|
||||
)
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.OVER_SCROLL_NEVER
|
||||
import android.webkit.WebView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.BaseFragment
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentDisclaimerBinding
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubscriber<DisclaimerState> {
|
||||
|
||||
private val binding: FragmentDisclaimerBinding by viewBinding(FragmentDisclaimerBinding::bind)
|
||||
private val webViewClient = DisclaimerWebViewClient()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(handler = this)
|
||||
|
||||
binding.apply {
|
||||
toolbar.setNavigationOnClickListener { handleOnBackPressed() }
|
||||
webView.apply {
|
||||
settings.allowFileAccess = false
|
||||
settings.javaScriptEnabled = false
|
||||
overScrollMode = OVER_SCROLL_NEVER
|
||||
webViewClient = this@DisclaimerFragment.webViewClient
|
||||
}
|
||||
webView.hide()
|
||||
groupError.hide()
|
||||
groupAccept.hide()
|
||||
groupLoading.hide()
|
||||
|
||||
btnAccept.setOnClickListener {
|
||||
store.dispatch(DisclaimerAction.AcceptDisclaimer)
|
||||
}
|
||||
btnRepeat.setOnClickListener {
|
||||
webViewClient.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setStatusBarColor(R.color.background_secondary)
|
||||
|
||||
webViewClient.onProgressStateChanged = { store.dispatch(DisclaimerAction.OnProgressStateChanged(it)) }
|
||||
store.subscribe(subscriber = this) { state ->
|
||||
state
|
||||
.skipRepeats { oldState, newState -> oldState.disclaimerState == newState.disclaimerState }
|
||||
.select(AppState::disclaimerState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
webViewClient.onProgressStateChanged = null
|
||||
store.unsubscribe(this)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun configureTransitions() {
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
when (store.state.disclaimerState.showedFrom) {
|
||||
DisclaimerSource.Home -> {
|
||||
enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom)
|
||||
exitTransition = inflater.inflateTransition(android.R.transition.slide_top)
|
||||
}
|
||||
DisclaimerSource.Details -> {
|
||||
super.configureTransitions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(DisclaimerAction.OnBackPressed)
|
||||
}
|
||||
|
||||
override fun newState(state: DisclaimerState) {
|
||||
return with(binding) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
updateUiVisibility(state.disclaimer, state.progressState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUiVisibility(disclaimer: Disclaimer, progressState: ProgressState?) = with(binding) {
|
||||
when (progressState) {
|
||||
ProgressState.Loading -> {
|
||||
root.beginDelayedTransition()
|
||||
webView.hide()
|
||||
groupError.hide()
|
||||
groupAccept.hide()
|
||||
groupLoading.show()
|
||||
}
|
||||
ProgressState.Done -> {
|
||||
root.beginDelayedTransition()
|
||||
groupError.hide()
|
||||
groupLoading.hide()
|
||||
webView.show()
|
||||
lifecycleScope.launch {
|
||||
groupAccept.show(!disclaimer.isAccepted())
|
||||
}
|
||||
}
|
||||
ProgressState.Error -> {
|
||||
root.beginDelayedTransition()
|
||||
webView.show()
|
||||
groupAccept.show()
|
||||
groupLoading.hide()
|
||||
groupError.hide()
|
||||
webView.loadLocalTermsOfServices()
|
||||
}
|
||||
else -> {
|
||||
webView.setBackgroundColor(resources.getColor(R.color.transparent, null))
|
||||
webView.loadUrl(disclaimer.getUri().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WebView.loadLocalTermsOfServices() {
|
||||
loadData(localTermsOfServices, "text/html", "UTF-8")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.ui
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.webkit.*
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
|
||||
class DisclaimerWebViewClient : WebViewClient() {
|
||||
|
||||
var onProgressStateChanged: ((ProgressState) -> Unit)? = null
|
||||
|
||||
private var loadingUrl: String? = null
|
||||
private var loadedUrl: String? = null
|
||||
|
||||
private var progressState: ProgressState = ProgressState.Loading
|
||||
set(value) {
|
||||
field = value
|
||||
onProgressStateChanged?.invoke(value)
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
loadingUrl = null
|
||||
loadedUrl = null
|
||||
progressState = ProgressState.Loading
|
||||
}
|
||||
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
super.onPageStarted(view, url, favicon)
|
||||
|
||||
if (loadingUrl != url) progressState = ProgressState.Loading
|
||||
loadingUrl = url
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
|
||||
if (loadedUrl != url) progressState = ProgressState.Done
|
||||
loadedUrl = url
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView?, resourceRequest: WebResourceRequest?, error: WebResourceError?) {
|
||||
super.onReceivedError(view, resourceRequest, error)
|
||||
error?.let { progressState = ProgressState.Error }
|
||||
}
|
||||
|
||||
override fun onReceivedHttpError(
|
||||
view: WebView?,
|
||||
resourceRequest: WebResourceRequest?,
|
||||
errorResponse: WebResourceResponse?,
|
||||
) {
|
||||
super.onReceivedHttpError(view, resourceRequest, errorResponse)
|
||||
|
||||
ifNotNull(resourceRequest, errorResponse) { request, response ->
|
||||
if (request.url?.toString() != loadingUrl || response.statusCode < RESPONSE_USER_ERROR_STATUS_CODE) return
|
||||
if (progressState != ProgressState.Done) return
|
||||
|
||||
progressState = ProgressState.Error
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RESPONSE_USER_ERROR_STATUS_CODE = 400
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.ui
|
||||
|
||||
internal val localTermsOfServices = """
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Legal Disclaimer</title>
|
||||
<style>
|
||||
@font-face{font-family:"SF Pro Display";src:url(/fonts/sf-pro/SFProDisplay-Light.eot) format("embedded-opentype"),url(/fonts/sf-pro/SFProDisplay-Light.woff2) format("woff2"),url(/fonts/sf-pro/SFProDisplay-Light.woff) format("woff"),url(/fonts/sf-pro/SFProDisplay-Light.ttf) format("truetype");font-weight:300;font-style:normal;font-display:swap}@font-face{font-family:"SF Pro Display";src:url(/fonts/sf-pro/SFProDisplay-Regular.eot) format("embedded-opentype"),url(/fonts/sf-pro/SFProDisplay-Regular.woff2) format("woff2"),url(/fonts/sf-pro/SFProDisplay-Regular.woff) format("woff"),url(/fonts/sf-pro/SFProDisplay-Regular.ttf) format("truetype");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:"SF Pro Display";src:url(/fonts/sf-pro/SFProDisplay-Medium.eot) format("embedded-opentype"),url(/fonts/sf-pro/SFProDisplay-Medium.woff2) format("woff2"),url(/fonts/sf-pro/SFProDisplay-Medium.woff) format("woff"),url(/fonts/sf-pro/SFProDisplay-Medium.ttf) format("truetype");font-weight:500;font-style:normal;font-display:swap}@font-face{font-family:"SF Pro Display";src:url(/fonts/sf-pro/SFProDisplay-Semibold.eot) format("embedded-opentype"),url(/fonts/sf-pro/SFProDisplay-Semibold.woff2) format("woff2"),url(/fonts/sf-pro/SFProDisplay-Semibold.woff) format("woff"),url(/fonts/sf-pro/SFProDisplay-Semibold.ttf) format("truetype");font-weight:600;font-style:normal;font-display:swap}@font-face{font-family:"SF Pro Display";src:url(/fonts/sf-pro/SFProDisplay-Bold.eot) format("embedded-opentype"),url(/fonts/sf-pro/SFProDisplay-Bold.woff2) format("woff2"),url(/fonts/sf-pro/SFProDisplay-Bold.woff) format("woff"),url(/fonts/sf-pro/SFProDisplay-Bold.ttf) format("truetype");font-weight:700;font-style:normal;font-display:swap}*{margin:0;padding:0}body{padding:0;margin:1rem;color:rgb(0 0 0);font-family:'SF Pro Display',-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}h1{font-weight:700;font-size:30px;line-height:1.2;margin-bottom:1rem}p{font-size:16px;line-height:20px;letter-spacing:-.24px;margin-bottom:1rem}
|
||||
</style>
|
||||
</head>
|
||||
<body><h1>Legal Disclaimer</h1>
|
||||
<p> 1. Tangem application (Software)</p>
|
||||
<p> The Software is intended for usage only with Tangem hardware wallets (Cards) via NFC interface. The
|
||||
Software DOES NOT:</p>
|
||||
<p> a) Generate, store, transmit, or have access to private (secret) cryptographic keys to blockchain
|
||||
wallets holding digital assets, including crypto-currency.</p>
|
||||
<p> b) Generate, store, transmit, or have access to secret keys, passwords, passphrases, recovery phrases
|
||||
that can be used to restore or to copy private (secret) keys to blockchain wallets holding digital assets, including
|
||||
crypto-currency.</p>
|
||||
<p> c) Provide exchange, trading, investment services on behalf of Tangem AG.</p>
|
||||
<p> 2. Risks related to the use of Software</p>
|
||||
<p> Tangem will not be responsible for any losses, damages or claims arising from events falling within the
|
||||
scope of the following five categories:</p>
|
||||
<p> a) Mistakes made by the user of any cryptocurrency-related software or service, e.g., forgotten
|
||||
passwords, payments sent to wrong addresses, and accidental deletion of blockchain wallets on Cards.</p>
|
||||
<p> b) Problems of Software and/or any blockchain- or cryptocurrency- related software or service, e.g.,
|
||||
corrupted files, incorrectly constructed transactions, unsafe cryptographic libraries, malware.</p>
|
||||
<p> c) Technical failures in the hardware of the user, including Cards, of any cryptocurrency-related
|
||||
software or service, e.g., data loss due to a faulty or damaged storage device.</p>
|
||||
<p> d) Security problems experienced by the user of any cryptocurrency-related software or service, e.g.,
|
||||
unauthorized access to users' wallets and/or accounts.</p>
|
||||
<p> e) Actions or inactions of third parties and/or events experienced by third parties, e.g., bankruptcy
|
||||
of service providers, information security attacks on service providers, and fraud conducted by third parties.</p>
|
||||
<p> 3. Trading and Investment risks</p>
|
||||
<p> There is considerable exposure to risk in any crypto-currency or other digital asset exchange
|
||||
transaction. Any transaction involving currencies involves risks including, but not limited to, the potential for
|
||||
changing economic conditions that may substantially affect the price or liquidity of a currency. Investments in
|
||||
crypto-currency exchange speculation may also be susceptible to sharp rises and falls as the relevant market values
|
||||
fluctuate. It is for this reason that when speculating in such markets it is advisable to use only risk capital.</p>
|
||||
<p> 4. Electronic Trading Risks</p>
|
||||
<p> Before you engage in transactions using an electronic system, you should carefully review the rules and
|
||||
regulations of the exchanges offering the system and/or listing the instruments you intend to trade. Online trading
|
||||
has inherent risk due to system response and access times that may vary due to market conditions, system
|
||||
performance, and other factors. You should understand these and additional risks before trading.</p>
|
||||
<p> 5. Compliance with tax obligations </p>
|
||||
<p> The users of the Software are solely responsible to determinate what, if any, taxes apply to their
|
||||
crypto-currency transactions. The owners of, or contributors to, the Software are NOT responsible for determining
|
||||
the taxes that apply to crypto-currency transactions. </p>
|
||||
<p> 6. No warranties </p>
|
||||
<p> The Software is provided on an "as is" basis without any warranties of any kind regarding the
|
||||
Software and/or any content, data, materials and/or services provided on the Software.
|
||||
</p>
|
||||
<p> 7. Limitation of liability</p>
|
||||
<p> Unless otherwise required by law, in no event shall the owners of, or contributors to, the Software be
|
||||
liable for any damages of any kind, including, but not limited to, loss of use, loss of profits, or loss of data
|
||||
arising out of or in any way connected with the use of the Software. In no way are the owners of, or contributors
|
||||
to, the Software responsible for the actions, decisions, or other behavior taken or not taken by you in reliance
|
||||
upon the Software.</p>
|
||||
<p> 8. Last amendment</p>
|
||||
<p> This disclaimer was amended for the last time on October 1st, 2020.</p></body>
|
||||
</html>
|
||||
""".trimIndent()
|
||||
|
|
@ -58,7 +58,9 @@ internal fun StoriesScreen(
|
|||
}
|
||||
|
||||
StoriesScreenContent(
|
||||
modifier = Modifier.fillMaxSize().testTag(TestTags.STORIES_SCREEN),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.testTag(TestTags.STORIES_SCREEN),
|
||||
config = StoriesScreenContentConfig(
|
||||
storiesSize = state.stories.lastIndex,
|
||||
currentStoryIndex = currentStoryIndex,
|
||||
|
|
@ -157,7 +159,7 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M
|
|||
duration = currentStoryDuration,
|
||||
)
|
||||
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
|
||||
is Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
|
||||
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
|
||||
isPaused = isPaused,
|
||||
stepDuration = currentStoryDuration,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.features.home.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomeFeatureToggles @Inject constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) {
|
||||
|
||||
val isMigrateUserCountryCodeEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "MIGRATE_USER_COUNTRY_CODE_ENABLED")
|
||||
}
|
||||
|
|
@ -15,6 +15,4 @@ sealed class HomeAction : Action {
|
|||
data class ReadCard(val scope: CoroutineScope) : HomeAction()
|
||||
|
||||
data class ScanInProgress(val scanInProgress: Boolean) : HomeAction()
|
||||
|
||||
data class UpdateCountryCode(val userCountryCode: String) : HomeAction()
|
||||
}
|
||||
|
|
@ -10,16 +10,10 @@ object HomeReducer {
|
|||
private fun internalReduce(action: Action, appState: AppState): HomeState {
|
||||
if (action !is HomeAction) return appState.homeState
|
||||
|
||||
var state = appState.homeState
|
||||
when (action) {
|
||||
return when (action) {
|
||||
is HomeAction.ScanInProgress -> {
|
||||
state = state.copy(scanInProgress = action.scanInProgress)
|
||||
appState.homeState.copy(scanInProgress = action.scanInProgress)
|
||||
}
|
||||
is HomeAction.UpdateCountryCode -> {
|
||||
state.onCountryCodeUpdate(state, action.userCountryCode)
|
||||
}
|
||||
else -> {}
|
||||
else -> appState.homeState
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
|
@ -1,56 +1,24 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.rekotlin.StateType
|
||||
import java.util.Locale
|
||||
|
||||
@Immutable
|
||||
data class HomeState(
|
||||
val scanInProgress: Boolean = false,
|
||||
val stories: List<Stories> = initDefaultStories(),
|
||||
val stories: ImmutableList<Stories> = Stories.entries.toImmutableList(),
|
||||
) : StateType {
|
||||
|
||||
val firstStory: Stories
|
||||
get() = stories[0]
|
||||
val firstStory: Stories get() = stories[0]
|
||||
|
||||
fun stepOf(story: Stories): Int = stories.indexOf(story)
|
||||
|
||||
fun onCountryCodeUpdate(homeState: HomeState, countryCode: String) {
|
||||
val isNewWalletAvailable = !(countryCode == RUSSIA_COUNTRY_CODE || countryCode == BELARUS_COUNTRY_CODE)
|
||||
homeState.stories.forEach {
|
||||
it.isNewWalletAvailable.value = isNewWalletAvailable
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RUSSIA_COUNTRY_CODE = "ru"
|
||||
private const val BELARUS_COUNTRY_CODE = "by"
|
||||
fun initDefaultStories(): List<Stories> = listOf(
|
||||
Stories.TangemIntro,
|
||||
Stories.RevolutionaryWallet,
|
||||
Stories.UltraSecureBackup,
|
||||
Stories.Currencies,
|
||||
Stories.Web3,
|
||||
Stories.WalletForEveryone,
|
||||
)
|
||||
|
||||
fun isNewWalletAvailableInit(): Boolean {
|
||||
val locale = Locale.getDefault().language
|
||||
return !(locale == RUSSIA_COUNTRY_CODE || locale == BELARUS_COUNTRY_CODE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Stories(
|
||||
val duration: Int,
|
||||
val isNewWalletAvailable: MutableState<Boolean> = mutableStateOf(HomeState.isNewWalletAvailableInit()),
|
||||
) {
|
||||
object TangemIntro : Stories(duration = 6000)
|
||||
object RevolutionaryWallet : Stories(duration = 6000)
|
||||
object UltraSecureBackup : Stories(duration = 6000)
|
||||
object Currencies : Stories(duration = 6000)
|
||||
object Web3 : Stories(duration = 6000)
|
||||
object WalletForEveryone : Stories(duration = 6000)
|
||||
enum class Stories(val duration: Int = 6000) {
|
||||
TangemIntro,
|
||||
RevolutionaryWallet,
|
||||
UltraSecureBackup,
|
||||
Currencies,
|
||||
Web3,
|
||||
WalletForEveryone,
|
||||
}
|
||||
|
|
@ -16,11 +16,13 @@ import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
|||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
|
||||
import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
|
||||
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
||||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
||||
import com.tangem.tap.features.main.model.MainScreenState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -47,6 +49,8 @@ internal class MainViewModel @Inject constructor(
|
|||
stakingFeatureToggles: StakingFeatureToggles,
|
||||
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
private val homeFeatureToggles: HomeFeatureToggles,
|
||||
private val fetchUserCountryUseCase: FetchUserCountryUseCase,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
) : ViewModel(), MainIntents {
|
||||
|
||||
|
|
@ -67,6 +71,14 @@ internal class MainViewModel @Inject constructor(
|
|||
|
||||
viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() }
|
||||
|
||||
if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
|
||||
viewModelScope.launch {
|
||||
fetchUserCountryUseCase().onLeft {
|
||||
Timber.e("Unable to fetch the user country code $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateAppCurrencies()
|
||||
observeFlips()
|
||||
displayBalancesHidingStatusToast()
|
||||
|
|
|
|||
|
|
@ -1,22 +1,31 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
@ -138,6 +147,39 @@ object OnboardingHelper {
|
|||
}
|
||||
}
|
||||
|
||||
fun handleTopUpAction(walletManager: WalletManager, scanResponse: ScanResponse, globalState: GlobalState) {
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val cryptoCurrency = CryptoCurrencyFactory().createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
) ?: return
|
||||
|
||||
val topUpUrl = walletManager.getTopUpUrl(cryptoCurrency) ?: return
|
||||
|
||||
val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain)
|
||||
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
|
||||
|
||||
scope.launch {
|
||||
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
|
||||
|
||||
val isRussia = if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
|
||||
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
|
||||
|
||||
getUserCountryCodeUseCase().isRight { it is UserCountry.Russia }
|
||||
} else {
|
||||
globalState.userCountryCode == RUSSIA_COUNTRY_CODE
|
||||
}
|
||||
|
||||
if (isRussia) {
|
||||
val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData))
|
||||
} else {
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(
|
||||
scanResponse: ScanResponse,
|
||||
backupCardsIds: List<String>?,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,8 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
|
|
@ -20,7 +16,6 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.mainScope
|
||||
|
|
@ -178,23 +173,11 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
return
|
||||
}
|
||||
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val cryptoCurrency = CryptoCurrencyFactory().createCoin(
|
||||
blockchain,
|
||||
null,
|
||||
scanResponse.derivationStyleProvider,
|
||||
) ?: return
|
||||
val topUpUrl = walletManager.getTopUpUrl(cryptoCurrency) ?: return
|
||||
|
||||
val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain)
|
||||
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
|
||||
|
||||
if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData))
|
||||
} else {
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
OnboardingHelper.handleTopUpAction(
|
||||
walletManager = walletManager,
|
||||
scanResponse = scanResponse,
|
||||
globalState = globalState,
|
||||
)
|
||||
}
|
||||
is OnboardingNoteAction.Done -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
|
|
|
|||
|
|
@ -6,16 +6,12 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
|
|
@ -26,7 +22,6 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.mainScope
|
||||
|
|
@ -301,24 +296,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
return
|
||||
}
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse ?: return
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val cryptoCurrency = CryptoCurrencyFactory().createCoin(
|
||||
blockchain,
|
||||
null,
|
||||
scanResponse.derivationStyleProvider,
|
||||
) ?: return
|
||||
val topUpUrl = walletManager.getTopUpUrl(cryptoCurrency) ?: return
|
||||
|
||||
val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain)
|
||||
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
|
||||
|
||||
if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData))
|
||||
} else {
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
OnboardingHelper.handleTopUpAction(
|
||||
walletManager = walletManager,
|
||||
scanResponse = onboardingManager?.scanResponse ?: return,
|
||||
globalState = globalState,
|
||||
)
|
||||
}
|
||||
TwinCardsAction.Done -> {
|
||||
val scanResponse = getScanResponse()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
|
|
@ -70,16 +71,26 @@ object TradeCryptoMiddleware {
|
|||
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
|
||||
)
|
||||
|
||||
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
val dialogData = topUrl?.let {
|
||||
AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl = it)
|
||||
}
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(data = dialogData))
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
|
||||
|
||||
if (currency is CryptoCurrency.Token && currency.network.isTestnet) {
|
||||
scope.launch {
|
||||
val isRussia = if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
|
||||
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
|
||||
|
||||
getUserCountryCodeUseCase().isRight { it is UserCountry.Russia }
|
||||
} else {
|
||||
state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE
|
||||
}
|
||||
|
||||
if (action.checkUserLocation && isRussia) {
|
||||
val dialogData = topUrl?.let {
|
||||
AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl = it)
|
||||
}
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(data = dialogData))
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (currency is CryptoCurrency.Token && currency.network.isTestnet) {
|
||||
val walletManager = store.inject(DaggerGraphState::walletManagersFacade)
|
||||
.getOrCreateWalletManager(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
|
|
@ -97,13 +108,13 @@ object TradeCryptoMiddleware {
|
|||
walletManager = walletManager,
|
||||
destinationAddress = currency.contractAddress,
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
topUrl?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
topUrl?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
|||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -29,8 +30,6 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
|||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
|
|
@ -41,6 +40,7 @@ import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
|||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import org.rekotlin.StateType
|
||||
|
||||
|
|
@ -80,11 +80,11 @@ data class DaggerGraphState(
|
|||
val getFeedbackEmailUseCase: GetFeedbackEmailUseCase? = null,
|
||||
val getCardInfoUseCase: GetCardInfoUseCase? = null,
|
||||
val assetLoader: AssetLoader? = null,
|
||||
val detailsFeatureToggles: DetailsFeatureToggles? = null,
|
||||
val stakingRouter: StakingRouter? = null,
|
||||
val urlOpener: UrlOpener? = null,
|
||||
val shareManager: ShareManager? = null,
|
||||
val appRouter: AppRouter? = null,
|
||||
val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles? = null,
|
||||
val pushNotificationsRouter: PushNotificationsRouter? = null,
|
||||
val homeFeatureToggles: HomeFeatureToggles? = null,
|
||||
val getUserCountryUseCase: GetUserCountryUseCase? = null,
|
||||
) : StateType
|
||||
|
|
@ -6,14 +6,12 @@ import com.tangem.feature.qrscanning.QrScanningRouter
|
|||
import com.tangem.feature.referral.ReferralFragment
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||
import com.tangem.features.managetokens.ManageTokensToggles
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
|
|
@ -25,11 +23,9 @@ import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorFragmen
|
|||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
|
||||
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment
|
||||
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryFragment
|
||||
import com.tangem.tap.features.details.ui.details.DetailsFragment
|
||||
import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment
|
||||
import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment
|
||||
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
|
||||
import com.tangem.tap.features.home.HomeFragment
|
||||
import com.tangem.tap.features.main.ui.ModalNotificationBottomSheetFragment
|
||||
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
|
||||
|
|
@ -59,8 +55,6 @@ internal class ChildFactory @Inject constructor(
|
|||
private val qrScanningRouter: QrScanningRouter,
|
||||
private val stakingRouter: StakingRouter,
|
||||
private val testerRouter: TesterRouter,
|
||||
private val detailsFeatureToggles: DetailsFeatureToggles,
|
||||
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles,
|
||||
private val manageTokensToggles: ManageTokensToggles,
|
||||
private val pushNotificationRouter: PushNotificationsRouter,
|
||||
) {
|
||||
|
|
@ -98,29 +92,21 @@ internal class ChildFactory @Inject constructor(
|
|||
route.asFragmentChild(Provider { CardSettingsFragment() })
|
||||
}
|
||||
is AppRoute.Details -> {
|
||||
if (detailsFeatureToggles.isRedesignEnabled) {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = DetailsComponent.Params(route.userWalletId),
|
||||
componentFactory = detailsComponentFactory,
|
||||
)
|
||||
} else {
|
||||
route.asFragmentChild(Provider { DetailsFragment() })
|
||||
}
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = DetailsComponent.Params(route.userWalletId),
|
||||
componentFactory = detailsComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.DetailsSecurity -> {
|
||||
route.asFragmentChild(Provider { SecurityModeFragment() })
|
||||
}
|
||||
is AppRoute.Disclaimer -> {
|
||||
if (pushNotificationsFeatureToggles.isPushNotificationsEnabled) {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = DisclaimerComponent.Params(route.isTosAccepted),
|
||||
componentFactory = disclaimerComponentFactory,
|
||||
)
|
||||
} else {
|
||||
route.asFragmentChild(Provider { DisclaimerFragment() })
|
||||
}
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = DisclaimerComponent.Params(route.isTosAccepted),
|
||||
componentFactory = disclaimerComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.Home -> {
|
||||
route.asFragmentChild(Provider { HomeFragment() })
|
||||
|
|
|
|||
|
|
@ -1,129 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/coordinator_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_secondary"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/background_secondary"
|
||||
android:fitsSystemWindows="true"
|
||||
app:liftOnScroll="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
|
||||
app:navigationIconTint="@color/icon_primary_1"
|
||||
app:titleTextColor="@color/text_primary_1"
|
||||
app:title="@string/disclaimer_title" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cl_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_secondary"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/web_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="74dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_accept"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/half_transparent_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="110dp"
|
||||
android:background="@drawable/bg_half_transparent_overlay"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:lineSpacingExtra="5sp"
|
||||
android:text="@string/disclaimer_error_loading"
|
||||
android:textAlignment="center"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btn_repeat"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_repeat"
|
||||
style="@style/TapSecondaryButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/common_retry"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_error" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_accept"
|
||||
style="@style/TapPrimaryButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/common_accept"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="btn_repeat, tv_error" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_loading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="progress" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_accept"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="btn_accept, half_transparent_overlay" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -227,13 +227,16 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data class Swap(
|
||||
val currency: CryptoCurrency,
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/swap/${currency.id.value}/${userWalletId.stringValue}"), RouteBundleParams {
|
||||
val isInitialReverseOrder: Boolean = false,
|
||||
) : AppRoute(path = "/swap/${currency.id.value}/${userWalletId.stringValue}/$isInitialReverseOrder"),
|
||||
RouteBundleParams {
|
||||
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
|
||||
companion object {
|
||||
const val CURRENCY_BUNDLE_KEY = "currency"
|
||||
const val USER_WALLET_ID_KEY = "userWalletId"
|
||||
const val IS_INITIAL_REVERSE_ORDER = "isInitialReverseOrder"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GeoResponse(
|
||||
@Json(name = "code") val code: String,
|
||||
)
|
||||
|
|
@ -7,10 +7,6 @@
|
|||
"name": "LOCAL_USER_LOGS_ENABLED",
|
||||
"version": "5.14.0"
|
||||
},
|
||||
{
|
||||
"name": "GENERATE_XPUB_ENABLED",
|
||||
"version": "5.12.0"
|
||||
},
|
||||
{
|
||||
"name": "WC_SOLANA_TX_SIGN_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -19,14 +15,6 @@
|
|||
"name": "STAKING_ENABLED",
|
||||
"version": "5.15.0"
|
||||
},
|
||||
{
|
||||
"name": "DETAILS_REDESIGN_ENABLED",
|
||||
"version": "5.13.0"
|
||||
},
|
||||
{
|
||||
"name": "PUSH_NOTIFICATIONS_ENABLED",
|
||||
"version": "5.13.0"
|
||||
},
|
||||
{
|
||||
"name": "MARKETS_ENABLED",
|
||||
"version": "5.15.0"
|
||||
|
|
@ -38,5 +26,9 @@
|
|||
{
|
||||
"name": "IS_ETHEREUM_EIP_1559_ENABLED",
|
||||
"version": "5.16.0"
|
||||
},
|
||||
{
|
||||
"name": "MIGRATE_USER_COUNTRY_CODE_ENABLED",
|
||||
"version": "5.16.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ internal class DevFeatureTogglesManager(
|
|||
) : MutableFeatureTogglesManager {
|
||||
|
||||
private var featureTogglesMap: MutableMap<String, Boolean> by Delegates.notNull()
|
||||
private var localFeatureTogglesMap: Map<String, Boolean> by Delegates.notNull()
|
||||
|
||||
override suspend fun init() {
|
||||
localFeatureTogglesStorage.init()
|
||||
|
|
@ -32,8 +33,12 @@ internal class DevFeatureTogglesManager(
|
|||
key = PreferencesKeys.FEATURE_TOGGLES_KEY,
|
||||
) ?: emptyMap()
|
||||
|
||||
featureTogglesMap = localFeatureTogglesStorage.featureToggles
|
||||
val localFeatureToggles = localFeatureTogglesStorage.featureToggles
|
||||
.associateToggles(currentVersion = versionProvider.get().orEmpty())
|
||||
|
||||
localFeatureTogglesMap = localFeatureToggles
|
||||
|
||||
featureTogglesMap = localFeatureToggles
|
||||
.mapValues { resultToggle ->
|
||||
savedFeatureToggles[resultToggle.key] ?: resultToggle.value
|
||||
}
|
||||
|
|
@ -42,16 +47,27 @@ internal class DevFeatureTogglesManager(
|
|||
|
||||
override fun isFeatureEnabled(name: String): Boolean = featureTogglesMap[name] ?: false
|
||||
|
||||
override fun isMatchLocalConfig(): Boolean = featureTogglesMap == localFeatureTogglesMap
|
||||
|
||||
override fun getFeatureToggles(): Map<String, Boolean> = featureTogglesMap
|
||||
|
||||
override suspend fun changeToggle(name: String, isEnabled: Boolean) {
|
||||
featureTogglesMap[name] ?: return
|
||||
featureTogglesMap[name] = isEnabled
|
||||
appPreferencesStore.storeObject(PreferencesKeys.FEATURE_TOGGLES_KEY, featureTogglesMap)
|
||||
appPreferencesStore.storeFeatureToggles(value = featureTogglesMap)
|
||||
}
|
||||
|
||||
override suspend fun recoverLocalConfig() {
|
||||
featureTogglesMap = localFeatureTogglesMap.toMutableMap()
|
||||
appPreferencesStore.storeFeatureToggles(value = localFeatureTogglesMap)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
fun setFeatureToggles(map: MutableMap<String, Boolean>) {
|
||||
featureTogglesMap = map
|
||||
}
|
||||
|
||||
private suspend fun AppPreferencesStore.storeFeatureToggles(value: Map<String, Boolean>) {
|
||||
storeObject(PreferencesKeys.FEATURE_TOGGLES_KEY, value)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,15 @@ package com.tangem.core.featuretoggle.manager
|
|||
*/
|
||||
interface MutableFeatureTogglesManager : FeatureTogglesManager {
|
||||
|
||||
/** Check if the current state of the feature toggles matches the local config state. */
|
||||
fun isMatchLocalConfig(): Boolean
|
||||
|
||||
/** Get feature toggles */
|
||||
fun getFeatureToggles(): Map<String, Boolean>
|
||||
|
||||
/** Change availability [isEnabled] of toggle with name [name] */
|
||||
suspend fun changeToggle(name: String, isEnabled: Boolean)
|
||||
|
||||
/** Recover local config state */
|
||||
suspend fun recoverLocalConfig()
|
||||
}
|
||||
|
|
@ -75,6 +75,8 @@ fun getActiveIconRes(blockchainId: String): Int {
|
|||
"cyber", "cyber/test" -> R.drawable.img_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.img_sei_22
|
||||
"internet-computer" -> R.drawable.img_icp_22
|
||||
"energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22
|
||||
"energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -151,6 +153,8 @@ fun getActiveIconResByNetworkId(networkId: String): Int {
|
|||
"cyber", "cyber/test" -> R.drawable.img_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.img_sei_22
|
||||
"internet-computer" -> R.drawable.img_icp_22
|
||||
"energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22
|
||||
"energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +228,8 @@ fun getActiveIconResByCoinId(coinId: String): Int {
|
|||
"cyber", "cyber/test" -> R.drawable.img_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.img_sei_22
|
||||
"internet-computer" -> R.drawable.img_icp_22
|
||||
"energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22
|
||||
"energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -300,6 +306,8 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
|
|||
"cyber", "cyber/test" -> R.drawable.ic_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.ic_sei_22
|
||||
"internet-computer" -> R.drawable.ic_icp_22
|
||||
"energy-web-chain", "energy-web-chain/test" -> R.drawable.ic_energy_web_22
|
||||
"energy-web-x", "energy-web-x/test" -> R.drawable.ic_energy_web_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -376,6 +384,8 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int {
|
|||
"cyber", "cyber/test" -> R.drawable.ic_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.ic_sei_22
|
||||
"internet-computer" -> R.drawable.ic_icp_22
|
||||
"energy-web-chain", "energy-web-chain/test" -> R.drawable.ic_energy_web_22
|
||||
"energy-web-x", "energy-web-x/test" -> R.drawable.ic_energy_web_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
9
core/ui/src/main/res/drawable/ic_energy_web_22.xml
Normal file
9
core/ui/src/main/res/drawable/ic_energy_web_22.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<path
|
||||
android:pathData="M10.426,16.82C9.756,17.299 8.955,17.56 8.131,17.568C6.707,17.566 5.405,16.793 4.418,15.369C3.531,14.007 3.04,12.425 3,10.801C3,10.77 3.01,10.74 3.028,10.715C3.046,10.691 3.071,10.672 3.1,10.662L4.071,10.229C4.089,10.22 4.108,10.215 4.128,10.215C4.149,10.214 4.169,10.218 4.187,10.225C4.206,10.233 4.223,10.244 4.237,10.258C4.251,10.273 4.262,10.29 4.269,10.308C4.269,10.328 4.288,10.347 4.288,10.367C4.286,11.982 4.718,13.51 5.489,14.639C6.217,15.708 7.167,16.283 8.156,16.284C8.691,16.286 9.214,16.128 9.659,15.831C9.691,15.814 9.729,15.809 9.764,15.817C9.8,15.824 9.833,15.844 9.856,15.872C10.053,16.129 10.251,16.366 10.467,16.603C10.48,16.639 10.483,16.679 10.476,16.717C10.469,16.755 10.452,16.791 10.426,16.82ZM17.567,15.389C16.596,16.791 15.269,17.579 13.847,17.577C12.425,17.575 11.12,16.783 10.135,15.378C9.208,14.033 8.697,12.252 8.7,10.369C8.702,8.631 9.713,7.267 10.978,7.269C12.243,7.271 13.251,8.638 13.248,10.376C13.268,11.891 12.905,13.386 12.193,14.724C12.184,14.741 12.172,14.756 12.157,14.769C12.143,14.781 12.125,14.791 12.107,14.796C12.088,14.802 12.069,14.804 12.05,14.801C12.031,14.799 12.012,14.793 11.995,14.783C11.976,14.783 11.976,14.762 11.955,14.743C11.837,14.604 11.718,14.466 11.62,14.327C11.501,14.156 11.394,13.978 11.297,13.793C11.288,13.772 11.283,13.748 11.283,13.724C11.283,13.7 11.288,13.677 11.297,13.655C11.748,12.613 11.979,11.489 11.976,10.354C11.978,9.505 11.543,8.535 10.969,8.534C10.396,8.533 9.96,9.502 9.958,10.351C9.956,11.966 10.388,13.495 11.159,14.623C11.889,15.691 12.837,16.265 13.825,16.267C14.814,16.268 15.765,15.677 16.497,14.632C17.27,13.502 17.708,11.985 17.711,10.363C17.713,8.742 17.282,7.228 16.51,6.096C16.29,5.763 16.024,5.463 15.72,5.205C15.694,5.175 15.678,5.139 15.674,5.1C15.671,5.061 15.68,5.021 15.701,4.988C15.72,4.967 15.741,4.948 15.761,4.948L16.731,4.515C16.758,4.5 16.789,4.496 16.819,4.503C16.849,4.511 16.875,4.529 16.892,4.554C17.139,4.804 17.364,5.075 17.565,5.363C18.492,6.708 19.003,8.488 19,10.372C18.997,12.255 18.498,14.047 17.567,15.389Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
19
core/ui/src/main/res/drawable/img_energy_web_22.xml
Normal file
19
core/ui/src/main/res/drawable/img_energy_web_22.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"/>
|
||||
<path
|
||||
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M0,0h22v22h-22z"
|
||||
android:fillColor="#A466FF"/>
|
||||
<path
|
||||
android:pathData="M10.426,16.82C9.756,17.299 8.955,17.56 8.131,17.568C6.707,17.566 5.405,16.793 4.418,15.369C3.531,14.007 3.04,12.425 3,10.801C3,10.77 3.01,10.74 3.028,10.715C3.046,10.691 3.071,10.672 3.1,10.662L4.071,10.229C4.089,10.22 4.108,10.215 4.128,10.215C4.149,10.214 4.169,10.218 4.187,10.225C4.206,10.233 4.223,10.244 4.237,10.258C4.251,10.273 4.262,10.29 4.269,10.308C4.269,10.328 4.288,10.347 4.288,10.367C4.286,11.982 4.718,13.51 5.489,14.639C6.217,15.708 7.167,16.283 8.156,16.284C8.691,16.286 9.214,16.128 9.659,15.831C9.691,15.814 9.729,15.809 9.764,15.817C9.8,15.824 9.833,15.844 9.856,15.872C10.053,16.129 10.251,16.366 10.467,16.603C10.48,16.639 10.483,16.679 10.476,16.717C10.469,16.755 10.452,16.791 10.426,16.82ZM17.567,15.389C16.596,16.791 15.269,17.579 13.847,17.577C12.425,17.575 11.12,16.783 10.135,15.378C9.208,14.033 8.697,12.252 8.7,10.369C8.702,8.631 9.713,7.267 10.978,7.269C12.243,7.271 13.251,8.638 13.248,10.376C13.268,11.891 12.905,13.386 12.193,14.724C12.184,14.741 12.172,14.756 12.157,14.769C12.143,14.781 12.125,14.791 12.107,14.796C12.088,14.802 12.069,14.804 12.05,14.801C12.031,14.799 12.012,14.793 11.995,14.783C11.976,14.783 11.976,14.762 11.955,14.743C11.837,14.604 11.718,14.466 11.62,14.327C11.501,14.156 11.394,13.978 11.297,13.793C11.288,13.772 11.283,13.748 11.283,13.724C11.283,13.7 11.288,13.677 11.297,13.655C11.748,12.613 11.979,11.489 11.976,10.354C11.978,9.505 11.543,8.535 10.969,8.534C10.396,8.533 9.96,9.502 9.958,10.351C9.956,11.966 10.388,13.495 11.159,14.623C11.889,15.691 12.837,16.265 13.825,16.267C14.814,16.268 15.765,15.677 16.497,14.632C17.27,13.502 17.708,11.985 17.711,10.363C17.713,8.742 17.282,7.228 16.51,6.096C16.29,5.763 16.024,5.463 15.72,5.205C15.694,5.175 15.678,5.139 15.674,5.1C15.671,5.061 15.68,5.021 15.701,4.988C15.72,4.967 15.741,4.948 15.761,4.948L16.731,4.515C16.758,4.5 16.789,4.496 16.819,4.503C16.849,4.511 16.875,4.529 16.892,4.554C17.139,4.804 17.364,5.075 17.565,5.363C18.492,6.708 19.003,8.488 19,10.372C18.997,12.255 18.498,14.047 17.567,15.389Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -30,5 +30,6 @@ dependencies {
|
|||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.timber)
|
||||
// endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,31 @@
|
|||
package com.tangem.data.settings
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.GeoResponse
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
|
||||
internal class DefaultSettingsRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val appLogsStore: AppLogsStore,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SettingsRepository {
|
||||
|
||||
private val userCountryFlow = MutableStateFlow<UserCountry?>(value = null)
|
||||
|
||||
override suspend fun shouldShowSaveUserWalletScreen(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY,
|
||||
|
|
@ -99,4 +113,38 @@ internal class DefaultSettingsRepository(
|
|||
override suspend fun setMarketsTooltipShown(value: Boolean) {
|
||||
appPreferencesStore.store(key = PreferencesKeys.SHOULD_SHOW_MARKETS_TOOLTIP_KEY, value = !value)
|
||||
}
|
||||
|
||||
override suspend fun getUserCountryCodeSync(): UserCountry? {
|
||||
// If user country code is already set, return it
|
||||
val countryCode = userCountryFlow.value
|
||||
if (countryCode != null) return countryCode
|
||||
|
||||
coroutineScope {
|
||||
launch { fetchUserCountryCode() }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun fetchUserCountryCode() {
|
||||
Timber.i("Start fetching user country code")
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
val country = runCatching { tangemTechApi.getUserCountryCode() }
|
||||
.fold(
|
||||
onSuccess = GeoResponse::code,
|
||||
onFailure = { Locale.getDefault().country },
|
||||
)
|
||||
.lowercase()
|
||||
|
||||
val code = when (country) {
|
||||
UserCountry.Russia.code -> UserCountry.Russia
|
||||
else -> UserCountry.Other(code = country)
|
||||
}
|
||||
|
||||
Timber.i("User code country is $code")
|
||||
|
||||
userCountryFlow.value = code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,14 @@ import com.tangem.data.settings.DefaultAppRatingRepository
|
|||
import com.tangem.data.settings.DefaultPermissionRepository
|
||||
import com.tangem.data.settings.DefaultPromoSettingsRepository
|
||||
import com.tangem.data.settings.DefaultSettingsRepository
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.settings.repositories.AppRatingRepository
|
||||
import com.tangem.domain.settings.repositories.PermissionRepository
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -25,8 +27,15 @@ internal object SettingsDataModule {
|
|||
fun provideSettingsRepository(
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
appLogsStore: AppLogsStore,
|
||||
tangemTechApi: TangemTechApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): SettingsRepository {
|
||||
return DefaultSettingsRepository(appPreferencesStore = appPreferencesStore, appLogsStore = appLogsStore)
|
||||
return DefaultSettingsRepository(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
appLogsStore = appLogsStore,
|
||||
tangemTechApi = tangemTechApi,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.domain.settings.repositories
|
||||
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
|
||||
interface SettingsRepository {
|
||||
|
||||
suspend fun shouldShowSaveUserWalletScreen(): Boolean
|
||||
|
|
@ -33,4 +35,8 @@ interface SettingsRepository {
|
|||
suspend fun shouldShowMarketsTooltip(): Boolean
|
||||
|
||||
suspend fun setMarketsTooltipShown(value: Boolean)
|
||||
|
||||
suspend fun getUserCountryCodeSync(): UserCountry?
|
||||
|
||||
suspend fun fetchUserCountryCode()
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.domain.settings.usercountry
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountryError
|
||||
|
||||
/**
|
||||
* Fetch user country use case
|
||||
*
|
||||
* @property settingsRepository settings repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class FetchUserCountryUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<UserCountryError, Unit> {
|
||||
return either {
|
||||
catch(
|
||||
block = { settingsRepository.fetchUserCountryCode() },
|
||||
catch = { raise(UserCountryError.DataError) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.domain.settings.usercountry
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountryError
|
||||
|
||||
/**
|
||||
* Get user country code use case
|
||||
*
|
||||
* @property settingsRepository settings repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetUserCountryUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<UserCountryError, UserCountry> {
|
||||
return either {
|
||||
val userCountryCode = catch(
|
||||
block = { settingsRepository.getUserCountryCodeSync() },
|
||||
catch = { raise(UserCountryError.DataError) },
|
||||
)
|
||||
|
||||
ensureNotNull(userCountryCode) { UserCountryError.NotSetup }
|
||||
|
||||
userCountryCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.settings.usercountry.models
|
||||
|
||||
/**
|
||||
* User country
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class UserCountry(open val code: String) {
|
||||
|
||||
data object Russia : UserCountry("ru")
|
||||
|
||||
data class Other(override val code: String) : UserCountry(code)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.settings.usercountry.models
|
||||
|
||||
/**
|
||||
* User country error
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface UserCountryError {
|
||||
|
||||
/** User country is still loading */
|
||||
data object NotSetup : UserCountryError
|
||||
|
||||
/** Data error */
|
||||
data object DataError : UserCountryError
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.features.details
|
||||
|
||||
interface DetailsFeatureToggles {
|
||||
|
||||
val isRedesignEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.features.details
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
internal class DefaultDetailsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : DetailsFeatureToggles {
|
||||
|
||||
override val isRedesignEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("DETAILS_REDESIGN_ENABLED")
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.features.details.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.features.details.DefaultDetailsFeatureToggles
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object FeatureModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): DetailsFeatureToggles {
|
||||
return DefaultDetailsFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -151,6 +151,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
|||
AppRoute.Swap(
|
||||
currency = cryptoCurrencyData.status.currency,
|
||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||
isInitialReverseOrder = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.features.pushnotifications.api.featuretoggles
|
||||
|
||||
/**
|
||||
* Push notifications feature toggles
|
||||
*/
|
||||
interface PushNotificationsFeatureToggles {
|
||||
/** Availability of push notifications */
|
||||
val isPushNotificationsEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.features.pushnotifications.impl.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.impl.featuretoggles.DefaultPushNotificationsFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* DI module provides implementation of [PushNotificationsFeatureToggles]
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object PushNotificationsFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendFeatureToggles(featureTogglesManager: FeatureTogglesManager): PushNotificationsFeatureToggles {
|
||||
return DefaultPushNotificationsFeatureToggles(featureTogglesManager = featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.features.pushnotifications.impl.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
|
||||
internal class DefaultPushNotificationsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : PushNotificationsFeatureToggles {
|
||||
override val isPushNotificationsEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("PUSH_NOTIFICATIONS_ENABLED")
|
||||
}
|
||||
|
|
@ -18,6 +18,14 @@ data class TokensDataStateExpress(
|
|||
}
|
||||
}
|
||||
|
||||
fun TokensDataStateExpress.getGroupWithReverse(isReverseFromTo: Boolean): CurrenciesGroup {
|
||||
return if (isReverseFromTo) {
|
||||
this.fromGroup
|
||||
} else {
|
||||
this.toGroup
|
||||
}
|
||||
}
|
||||
|
||||
data class CurrenciesGroup(
|
||||
val available: List<CryptoCurrencySwapInfo>,
|
||||
val unavailable: List<CryptoCurrencySwapInfo>,
|
||||
|
|
|
|||
|
|
@ -2,31 +2,34 @@ package com.tangem.feature.swap.domain
|
|||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress
|
||||
import com.tangem.feature.swap.domain.models.ui.getGroupWithReverse
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultInitialToCurrencyResolver(
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val swapTransactionRepository: SwapTransactionRepository,
|
||||
) : InitialToCurrencyResolver {
|
||||
|
||||
override suspend fun tryGetFromCache(
|
||||
userWallet: UserWallet,
|
||||
initialCryptoCurrency: CryptoCurrency,
|
||||
state: TokensDataStateExpress,
|
||||
isReverseFromTo: Boolean,
|
||||
): CryptoCurrencyStatus? {
|
||||
val selectedId = getSelectedWalletSyncUseCase().getOrNull() ?: return null
|
||||
val id = swapTransactionRepository.getLastSwappedCryptoCurrencyId(selectedId.walletId) ?: return null
|
||||
val id = swapTransactionRepository.getLastSwappedCryptoCurrencyId(userWallet.walletId) ?: return null
|
||||
|
||||
return if (id != initialCryptoCurrency.id.value) {
|
||||
state.toGroup.available.find { it.currencyStatus.currency.id.value == id }?.currencyStatus
|
||||
val group = state.getGroupWithReverse(isReverseFromTo)
|
||||
group.available.find { it.currencyStatus.currency.id.value == id }?.currencyStatus
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun tryGetWithMaxAmount(state: TokensDataStateExpress): CryptoCurrencyStatus? {
|
||||
return state.toGroup.available.maxByOrNull {
|
||||
override fun tryGetWithMaxAmount(state: TokensDataStateExpress, isReverseFromTo: Boolean): CryptoCurrencyStatus? {
|
||||
val group = state.getGroupWithReverse(isReverseFromTo)
|
||||
return group.available.maxByOrNull {
|
||||
it.currencyStatus.value.fiatAmount ?: BigDecimal.ZERO
|
||||
}?.currencyStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,17 @@ package com.tangem.feature.swap.domain
|
|||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress
|
||||
|
||||
interface InitialToCurrencyResolver {
|
||||
|
||||
suspend fun tryGetFromCache(
|
||||
userWallet: UserWallet,
|
||||
initialCryptoCurrency: CryptoCurrency,
|
||||
state: TokensDataStateExpress,
|
||||
isReverseFromTo: Boolean,
|
||||
): CryptoCurrencyStatus?
|
||||
|
||||
fun tryGetWithMaxAmount(state: TokensDataStateExpress): CryptoCurrencyStatus?
|
||||
fun tryGetWithMaxAmount(state: TokensDataStateExpress, isReverseFromTo: Boolean): CryptoCurrencyStatus?
|
||||
}
|
||||
|
|
@ -79,9 +79,10 @@ interface SwapInteractor {
|
|||
*/
|
||||
fun getTokenBalance(token: CryptoCurrencyStatus): SwapAmount
|
||||
|
||||
suspend fun selectInitialCurrencyToSwap(
|
||||
suspend fun getInitialCurrencyToSwap(
|
||||
initialCryptoCurrency: CryptoCurrency,
|
||||
state: TokensDataStateExpress,
|
||||
isReverseFromTo: Boolean,
|
||||
): CryptoCurrencyStatus?
|
||||
|
||||
fun getNativeToken(networkId: String): CryptoCurrency
|
||||
|
|
|
|||
|
|
@ -965,13 +965,15 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
return SwapAmount(token.value.amount ?: BigDecimal.ZERO, token.currency.decimals)
|
||||
}
|
||||
|
||||
override suspend fun selectInitialCurrencyToSwap(
|
||||
override suspend fun getInitialCurrencyToSwap(
|
||||
initialCryptoCurrency: CryptoCurrency,
|
||||
state: TokensDataStateExpress,
|
||||
isReverseFromTo: Boolean,
|
||||
): CryptoCurrencyStatus? {
|
||||
return initialToCurrencyResolver.tryGetFromCache(initialCryptoCurrency, state)
|
||||
?: initialToCurrencyResolver.tryGetWithMaxAmount(state)
|
||||
?: state.toGroup.available.firstOrNull()?.currencyStatus
|
||||
val group = state.getGroupWithReverse(isReverseFromTo)
|
||||
return initialToCurrencyResolver.tryGetFromCache(userWallet, initialCryptoCurrency, state, isReverseFromTo)
|
||||
?: initialToCurrencyResolver.tryGetWithMaxAmount(state, isReverseFromTo)
|
||||
?: group.available.firstOrNull()?.currencyStatus
|
||||
}
|
||||
|
||||
override fun getNativeToken(networkId: String): CryptoCurrency {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
|||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.feature.swap.domain.*
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -60,11 +58,9 @@ internal class SwapDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideInitialToCurrencyResolver(
|
||||
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
swapTransactionRepository: SwapTransactionRepository,
|
||||
): InitialToCurrencyResolver {
|
||||
return DefaultInitialToCurrencyResolver(
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
swapTransactionRepository = swapTransactionRepository,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,9 @@ internal class SwapViewModel @Inject constructor(
|
|||
?.unbundle(UserWalletId.serializer())
|
||||
?: error("no expected parameter UserWalletId found")
|
||||
|
||||
private val isInitiallyReversed: Boolean = savedStateHandle.get<Boolean>(AppRoute.Swap.IS_INITIAL_REVERSE_ORDER)
|
||||
?: false
|
||||
|
||||
private val swapInteractor = swapInteractorFactory.create(userWalletId)
|
||||
|
||||
private lateinit var initialCryptoCurrencyStatus: CryptoCurrencyStatus
|
||||
|
|
@ -137,7 +140,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
uiState = stateBuilder.addAlert(uiState = uiState, onClick = swapRouter::back)
|
||||
} else {
|
||||
initialCryptoCurrencyStatus = cryptoCurrencyStatus
|
||||
initTokens()
|
||||
initTokens(isInitiallyReversed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -180,18 +183,21 @@ internal class SwapViewModel @Inject constructor(
|
|||
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenOpened(availableTokens = isAnyAvailableTokens))
|
||||
}
|
||||
|
||||
private fun initTokens() {
|
||||
private fun initTokens(isReverseFromTo: Boolean) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
runCatching(dispatchers.io) {
|
||||
swapInteractor.getTokensDataState(initialCryptoCurrency)
|
||||
}.onSuccess { state ->
|
||||
updateTokensState(state)
|
||||
val selectedCurrency = swapInteractor.getInitialCurrencyToSwap(
|
||||
initialCryptoCurrency = initialCryptoCurrency,
|
||||
state = state,
|
||||
isReverseFromTo = isReverseFromTo,
|
||||
)
|
||||
applyInitialTokenChoice(
|
||||
state,
|
||||
swapInteractor.selectInitialCurrencyToSwap(
|
||||
initialCryptoCurrency,
|
||||
state,
|
||||
),
|
||||
state = state,
|
||||
selectedCurrency = selectedCurrency,
|
||||
isReverseFromTo = isReverseFromTo,
|
||||
)
|
||||
|
||||
(dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let {
|
||||
|
|
@ -215,6 +221,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
applyInitialTokenChoice(
|
||||
state = TokensDataStateExpress.EMPTY,
|
||||
selectedCurrency = null,
|
||||
isReverseFromTo = isReverseFromTo,
|
||||
)
|
||||
|
||||
uiState = stateBuilder.createInitialErrorState(
|
||||
|
|
@ -227,33 +234,43 @@ internal class SwapViewModel @Inject constructor(
|
|||
initialCryptoCurrency.network.backendId,
|
||||
),
|
||||
)
|
||||
initTokens()
|
||||
initTokens(isReverseFromTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyInitialTokenChoice(state: TokensDataStateExpress, selectedCurrency: CryptoCurrencyStatus?) {
|
||||
val fromCurrencyStatus = initialCryptoCurrencyStatus
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = fromCurrencyStatus,
|
||||
toCryptoCurrency = selectedCurrency,
|
||||
tokensDataState = state,
|
||||
)
|
||||
private fun applyInitialTokenChoice(
|
||||
state: TokensDataStateExpress,
|
||||
selectedCurrency: CryptoCurrencyStatus?,
|
||||
isReverseFromTo: Boolean,
|
||||
) {
|
||||
// exceptional case
|
||||
if (selectedCurrency == null) {
|
||||
analyticsEventHandler.send(SwapEvents.NoticeNoAvailableTokensToSwap)
|
||||
uiState = stateBuilder.createNoAvailableTokensToSwapState(
|
||||
uiStateHolder = uiState,
|
||||
fromToken = fromCurrencyStatus,
|
||||
)
|
||||
} else {
|
||||
startLoadingQuotes(
|
||||
fromToken = fromCurrencyStatus,
|
||||
toToken = selectedCurrency,
|
||||
amount = lastAmount.value,
|
||||
toProvidersList = findSwapProviders(fromCurrencyStatus, selectedCurrency),
|
||||
fromToken = initialCryptoCurrencyStatus,
|
||||
)
|
||||
return
|
||||
}
|
||||
isOrderReversed = isReverseFromTo
|
||||
val (fromCurrencyStatus, toCurrencyStatus) = if (isOrderReversed) {
|
||||
selectedCurrency to initialCryptoCurrencyStatus
|
||||
} else {
|
||||
initialCryptoCurrencyStatus to selectedCurrency
|
||||
}
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = fromCurrencyStatus,
|
||||
toCryptoCurrency = toCurrencyStatus,
|
||||
tokensDataState = state,
|
||||
)
|
||||
startLoadingQuotes(
|
||||
fromToken = fromCurrencyStatus,
|
||||
toToken = toCurrencyStatus,
|
||||
amount = lastAmount.value,
|
||||
toProvidersList = findSwapProviders(fromCurrencyStatus, toCurrencyStatus),
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateTokensState(tokenDataState: TokensDataStateExpress) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
|
|
@ -65,7 +68,6 @@ internal fun EnvironmentTogglesScreen(uiModel: EnvironmentTogglesScreenUM) {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun EnvironmentButtons(
|
||||
uiModel: EnvironmentTogglesScreenUM.ApiInfoUM,
|
||||
|
|
|
|||
|
|
@ -6,14 +6,37 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
/**
|
||||
* Content state of feature toggles screen
|
||||
*
|
||||
* @property topBarState top bar state
|
||||
* @property appVersion app version
|
||||
* @property featureToggles feature toggles list
|
||||
* @property onBackClick the lambda to be invoked when back button is pressed
|
||||
* @property onToggleValueChange the lambda to be invoked when switch button is pressed
|
||||
* @property onApplyChangesClick the lambda to be invoked when apply changes button is pressed
|
||||
* @property onRestartAppClick the lambda to be invoked when restart app button is pressed
|
||||
*/
|
||||
internal data class FeatureTogglesContentState(
|
||||
val topBarState: TopBarState,
|
||||
val appVersion: String,
|
||||
val featureToggles: ImmutableList<TesterFeatureToggle>,
|
||||
val onToggleValueChange: (String, Boolean) -> Unit,
|
||||
val onBackClick: () -> Unit,
|
||||
val onApplyChangesClick: () -> Unit,
|
||||
)
|
||||
val onToggleValueChange: (String, Boolean) -> Unit,
|
||||
val onRestartAppClick: () -> Unit,
|
||||
) {
|
||||
|
||||
/** Top bar state */
|
||||
sealed interface TopBarState {
|
||||
|
||||
/**
|
||||
* Config setup
|
||||
*
|
||||
* @property onBackClick the lambda to be invoked when back button is pressed
|
||||
*/
|
||||
data object ConfigSetup : TopBarState
|
||||
|
||||
/**
|
||||
* Custom setup
|
||||
*
|
||||
* @property onRecoverClick the lambda to be invoked when recover button is pressed
|
||||
*/
|
||||
data class CustomSetup(val onRecoverClick: () -> Unit) : TopBarState
|
||||
}
|
||||
}
|
||||
|
|
@ -6,17 +6,21 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.SwitchDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.tester.impl.R
|
||||
|
|
@ -37,22 +41,35 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) {
|
|||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
) {
|
||||
stickyHeader {
|
||||
AppBarWithBackButton(
|
||||
onBackClick = state.onBackClick,
|
||||
text = stringResource(id = R.string.feature_toggles),
|
||||
)
|
||||
stickyHeader { TopAppBar(state = state.topBarState, onBackClick = state.onBackClick) }
|
||||
|
||||
if (state.topBarState is FeatureTogglesContentState.TopBarState.CustomSetup) {
|
||||
item(key = "warning_notification", contentType = "warning_notification") {
|
||||
WarningNotification(
|
||||
appVersion = state.appVersion,
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
items(state.featureToggles) { featureToggle ->
|
||||
|
||||
items(
|
||||
items = state.featureToggles,
|
||||
key = TesterFeatureToggle::name,
|
||||
contentType = { "feature_toggle_item" },
|
||||
) { featureToggle ->
|
||||
FeatureToggleItem(
|
||||
toggle = featureToggle,
|
||||
onCheckedChange = { isChange -> state.onToggleValueChange(featureToggle.name, isChange) },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PrimaryButton(
|
||||
text = stringResource(id = R.string.apply_changes),
|
||||
onClick = state.onApplyChangesClick,
|
||||
text = stringResource(id = R.string.restart_app),
|
||||
onClick = state.onRestartAppClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
|
|
@ -61,12 +78,48 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopAppBar(state: FeatureTogglesContentState.TopBarState, onBackClick: () -> Unit) {
|
||||
AppBarWithBackButtonAndIcon(
|
||||
onBackClick = onBackClick,
|
||||
text = stringResource(id = R.string.feature_toggles),
|
||||
iconRes = if (state is FeatureTogglesContentState.TopBarState.CustomSetup) {
|
||||
R.drawable.ic_refresh_24
|
||||
} else {
|
||||
null
|
||||
},
|
||||
onIconClick = if (state is FeatureTogglesContentState.TopBarState.CustomSetup) {
|
||||
state.onRecoverClick
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WarningNotification(appVersion: String, modifier: Modifier = Modifier) {
|
||||
Notification(
|
||||
config = NotificationConfig(
|
||||
subtitle = resourceReference(
|
||||
id = R.string.feature_toggles_custom_setup_warning_description,
|
||||
wrappedList(appVersion),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_triangle_20,
|
||||
title = resourceReference(id = R.string.feature_toggles_custom_setup_warning_title),
|
||||
),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boolean) -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing18),
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing18,
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
@ -78,14 +131,8 @@ private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boo
|
|||
maxLines = 1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
Switch(
|
||||
checked = toggle.isEnabled,
|
||||
onCheckedChange = onCheckedChange,
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = TangemTheme.colors.control.checked,
|
||||
uncheckedThumbColor = TangemTheme.colors.control.unchecked,
|
||||
),
|
||||
)
|
||||
|
||||
TangemSwitch(onCheckedChange = onCheckedChange, checked = toggle.isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,15 +141,23 @@ private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boo
|
|||
@Composable
|
||||
private fun PreviewFeatureTogglesScreen() {
|
||||
TangemThemePreview {
|
||||
var isCustomSetup by remember { mutableStateOf(value = true) }
|
||||
|
||||
FeatureTogglesScreen(
|
||||
state = FeatureTogglesContentState(
|
||||
topBarState = if (isCustomSetup) {
|
||||
FeatureTogglesContentState.TopBarState.CustomSetup(onRecoverClick = { isCustomSetup = false })
|
||||
} else {
|
||||
FeatureTogglesContentState.TopBarState.ConfigSetup
|
||||
},
|
||||
appVersion = "5.15",
|
||||
featureToggles = persistentListOf(
|
||||
TesterFeatureToggle(name = "FEATURE_TOGGLE_1", isEnabled = true),
|
||||
TesterFeatureToggle(name = "FEATURE_TOGGLE_2", isEnabled = false),
|
||||
),
|
||||
onToggleValueChange = { _, _ -> },
|
||||
onBackClick = {},
|
||||
onApplyChangesClick = {},
|
||||
onToggleValueChange = { _, _ -> isCustomSetup = true },
|
||||
onRestartAppClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ import com.tangem.core.navigation.finisher.AppFinisher
|
|||
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState
|
||||
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -22,14 +23,14 @@ import javax.inject.Inject
|
|||
* ViewModel for screen with list of feature toggles
|
||||
*
|
||||
* @property featureTogglesManager manager for getting information about the availability of feature toggles
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
* @property appVersionProvider app version provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@HiltViewModel
|
||||
internal class FeatureTogglesViewModel @Inject constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val appVersionProvider: AppVersionProvider,
|
||||
) : ViewModel() {
|
||||
|
||||
/** Current ui state */
|
||||
|
|
@ -41,28 +42,54 @@ internal class FeatureTogglesViewModel @Inject constructor(
|
|||
"Feature toggle manager must be mutable (debug build type)"
|
||||
}
|
||||
|
||||
/** Setup navigation state property by router [router] and provides app restart method by [appRestarter] */
|
||||
/** Setup navigation state property by router [router] and provides app restart method by [appFinisher] */
|
||||
fun setupInteractions(router: InnerTesterRouter, appFinisher: AppFinisher) {
|
||||
uiState = uiState.copy(
|
||||
onBackClick = router::back,
|
||||
onApplyChangesClick = appFinisher::restart,
|
||||
onRestartAppClick = appFinisher::restart,
|
||||
)
|
||||
}
|
||||
|
||||
private fun initState(): FeatureTogglesContentState {
|
||||
return FeatureTogglesContentState(
|
||||
topBarState = getConfigSetupState(),
|
||||
appVersion = appVersionProvider.versionName,
|
||||
featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(),
|
||||
onToggleValueChange = ::onToggleValueChange,
|
||||
onBackClick = {},
|
||||
onApplyChangesClick = {},
|
||||
onRestartAppClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
private fun onToggleValueChange(name: String, isEnabled: Boolean) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
mutableFeatureTogglesManager.changeToggle(name = name, isEnabled = isEnabled)
|
||||
|
||||
uiState = uiState.copy(featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles())
|
||||
|
||||
// delay for smoothly update animations
|
||||
delay(timeMillis = 300)
|
||||
|
||||
uiState = uiState.copy(topBarState = getConfigSetupState())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfigSetupState(): FeatureTogglesContentState.TopBarState {
|
||||
return if (mutableFeatureTogglesManager.isMatchLocalConfig()) {
|
||||
FeatureTogglesContentState.TopBarState.ConfigSetup
|
||||
} else {
|
||||
FeatureTogglesContentState.TopBarState.CustomSetup(
|
||||
onRecoverClick = {
|
||||
viewModelScope.launch {
|
||||
mutableFeatureTogglesManager.recoverLocalConfig()
|
||||
|
||||
uiState = uiState.copy(
|
||||
topBarState = getConfigSetupState(),
|
||||
featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
<resources>
|
||||
<string name="tester_menu" translatable="false">Tester menu</string>
|
||||
<string name="feature_toggles" translatable="false">Feature toggles</string>
|
||||
<string name="apply_changes" translatable="false">Apply changes</string>
|
||||
<string name="feature_toggles_custom_setup_warning_title" translatable="false">Custom setup</string>
|
||||
<string name="feature_toggles_custom_setup_warning_description" translatable="false">Feature toggles differs from v%s config</string>
|
||||
<string name="restart_app" translatable="false">Restart app</string>
|
||||
<string name="environment_toggles" translatable="false">Environment toggles</string>
|
||||
<string name="tester_actions" translatable="false">Tester actions</string>
|
||||
<string name="hide_all_currencies" translatable="false">Hide all currencies</string>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.features.tokendetails.featuretoggles
|
||||
|
||||
interface TokenDetailsFeatureToggles {
|
||||
|
||||
fun isGenerateXPubEnabled(): Boolean
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.feature.tokendetails.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.feature.tokendetails.featuretoggles.DefaultTokenDetailsFeatureToggles
|
||||
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object TokenDetailsFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTokenDetailsFeatureToggles(featureTogglesManager: FeatureTogglesManager): TokenDetailsFeatureToggles {
|
||||
return DefaultTokenDetailsFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.feature.tokendetails.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
|
||||
|
||||
internal class DefaultTokenDetailsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TokenDetailsFeatureToggles {
|
||||
|
||||
override fun isGenerateXPubEnabled() = featureTogglesManager.isFeatureEnabled(name = "GENERATE_XPUB_ENABLED")
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
|
@ -28,7 +27,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
|
||||
internal class TokenDetailsSkeletonStateConverter(
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val featureToggles: TokenDetailsFeatureToggles,
|
||||
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val userWalletId: UserWalletId,
|
||||
|
|
@ -98,12 +96,14 @@ internal class TokenDetailsSkeletonStateConverter(
|
|||
cryptoCurrency: CryptoCurrency,
|
||||
) {
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return
|
||||
val isGenerateXPubEnabled = featureToggles.isGenerateXPubEnabled()
|
||||
val isBitcoin = isBitcoin(cryptoCurrency.network.id.value)
|
||||
val hasDerivations =
|
||||
networkHasDerivationUseCase(userWallet.scanResponse, cryptoCurrency.network).getOrElse { false }
|
||||
|
||||
if (isGenerateXPubEnabled && isBitcoin && hasDerivations) {
|
||||
val isBitcoin = isBitcoin(cryptoCurrency.network.id.value)
|
||||
val hasDerivations = networkHasDerivationUseCase(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
network = cryptoCurrency.network,
|
||||
).getOrElse { false }
|
||||
|
||||
if (isBitcoin && hasDerivations) {
|
||||
add(
|
||||
TokenDetailsAppBarMenuConfig.MenuItem(
|
||||
title = resourceReference(R.string.token_details_generate_xpub),
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.t
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -53,7 +52,6 @@ internal class TokenDetailsStateFactory(
|
|||
private val stakingEntryInfoProvider: Provider<StakingEntryInfo?>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val featureToggles: TokenDetailsFeatureToggles,
|
||||
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val userWalletId: UserWalletId,
|
||||
|
|
@ -65,7 +63,6 @@ internal class TokenDetailsStateFactory(
|
|||
private val skeletonStateConverter by lazy {
|
||||
TokenDetailsSkeletonStateConverter(
|
||||
clickIntents = clickIntents,
|
||||
featureToggles = featureToggles,
|
||||
networkHasDerivationUseCase = networkHasDerivationUseCase,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -390,10 +387,9 @@ internal class TokenDetailsStateFactory(
|
|||
isSupported: Boolean,
|
||||
): TokenDetailsAppBarMenuConfig? {
|
||||
if (cardTypesResolver.isSingleWalletWithToken()) return null
|
||||
val showGenerateExtendedKey = featureToggles.isGenerateXPubEnabled() && isSupported
|
||||
return copy(
|
||||
items = buildList {
|
||||
if (showGenerateExtendedKey && hasDerivations) {
|
||||
if (isSupported && hasDerivations) {
|
||||
TokenDetailsAppBarMenuConfig.MenuItem(
|
||||
title = resourceReference(R.string.token_details_generate_xpub),
|
||||
textColorProvider = { TangemTheme.colors.text.primary1 },
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
|
|
@ -124,7 +123,6 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val vibratorHapticManager: VibratorHapticManager,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
tokenDetailsFeatureToggles: TokenDetailsFeatureToggles,
|
||||
deepLinksRegistry: DeepLinksRegistry,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val appRouter: AppRouter,
|
||||
|
|
@ -159,13 +157,12 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
stakingEntryInfoProvider = Provider { stakingEntryInfo },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
clickIntents = this,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
featureToggles = tokenDetailsFeatureToggles,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
userWalletId = userWalletId,
|
||||
networkHasDerivationUseCase = networkHasDerivationUseCase,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
userWalletId = userWalletId,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
)
|
||||
|
||||
private val exchangeStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSend
|
|||
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
import com.tangem.features.markets.MarketsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -67,7 +66,6 @@ internal class WalletViewModel @Inject constructor(
|
|||
private val walletNameMigrationUseCase: WalletNameMigrationUseCase,
|
||||
private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase,
|
||||
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
|
||||
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles,
|
||||
private val marketsFeatureToggles: MarketsFeatureToggles,
|
||||
analyticsEventsHandler: AnalyticsEventHandler,
|
||||
) : ViewModel() {
|
||||
|
|
@ -166,10 +164,9 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
private fun subscribeOnPushNotificationsPermission() {
|
||||
viewModelScope.launch {
|
||||
val isPushToggled = pushNotificationsFeatureToggles.isPushNotificationsEnabled
|
||||
val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION)
|
||||
val isPushPermissionAvailable = getPushPermissionOrNull() != null
|
||||
if (!isPushToggled || !shouldRequestPush || !isPushPermissionAvailable) return@launch
|
||||
if (!shouldRequestPush || !isPushPermissionAvailable) return@launch
|
||||
|
||||
delay(timeMillis = 1_800)
|
||||
|
||||
|
|
|
|||
|
|
@ -89,9 +89,9 @@ markdownComposeView = "0.5.4"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "release-app_5.15-796"
|
||||
tangemBlockchainSdk = "develop-797"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "release-app_5.15-384"
|
||||
tangemCardSdk = "develop-385"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
tangemVico = "2.0.0-alpha.25-tangem16"
|
||||
#tangemVico = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
|||
"sei-network" -> Blockchain.Sei
|
||||
"sei-network/test" -> Blockchain.SeiTestnet
|
||||
"internet-computer" -> Blockchain.InternetComputer
|
||||
"energy-web-chain" -> Blockchain.EnergyWebChain
|
||||
"energy-web-chain/test" -> Blockchain.EnergyWebChainTestnet
|
||||
"energy-web-x" -> Blockchain.EnergyWebX
|
||||
"energy-web-x/test" -> Blockchain.EnergyWebXTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -253,6 +257,10 @@ fun Blockchain.toNetworkId(): String {
|
|||
Blockchain.Sei -> "sei-network"
|
||||
Blockchain.SeiTestnet -> "sei-network/test"
|
||||
Blockchain.InternetComputer -> "internet-computer"
|
||||
Blockchain.EnergyWebChain -> "energy-web-chain"
|
||||
Blockchain.EnergyWebChainTestnet -> "energy-web-chain/test"
|
||||
Blockchain.EnergyWebX -> "energy-web-x"
|
||||
Blockchain.EnergyWebXTestnet -> "energy-web-x/test"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -337,6 +345,8 @@ fun Blockchain.toCoinId(): String {
|
|||
Blockchain.Cyber, Blockchain.CyberTestnet -> "cyber-ethereum"
|
||||
Blockchain.Sei, Blockchain.SeiTestnet -> "sei-network"
|
||||
Blockchain.InternetComputer -> "internet-computer"
|
||||
Blockchain.EnergyWebChain, Blockchain.EnergyWebChainTestnet -> "energy-web-chain"
|
||||
Blockchain.EnergyWebX, Blockchain.EnergyWebXTestnet -> "energy-web-x"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -363,10 +373,12 @@ fun Blockchain.minimalAmount(): BigDecimal {
|
|||
private const val NODL = "NODL"
|
||||
private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5
|
||||
|
||||
// no need to add testnets
|
||||
private val excludedBlockchains = listOf(
|
||||
Blockchain.Unknown,
|
||||
Blockchain.Nexa,
|
||||
Blockchain.NexaTestnet,
|
||||
Blockchain.Sei,
|
||||
Blockchain.InternetComputer,
|
||||
Blockchain.EnergyWebChain,
|
||||
Blockchain.EnergyWebX,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue