Updated on 2026-08-14
This commit is contained in:
commit
bab4923434
283 changed files with 1541 additions and 11004 deletions
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"isTopUpEnabled": true,
|
|
||||||
"isCreatingTwinCardsAllowed": true
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"isTopUpEnabled": true,
|
|
||||||
"isCreatingTwinCardsAllowed": true
|
|
||||||
}
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 328c13826f7e52ba783db1ba39de5b17b1b79bc5
|
Subproject commit 5b70b3652d06811e3f1292e41e735e00fe75783f
|
||||||
|
|
@ -718,6 +718,26 @@
|
||||||
"networkId": "sei-network/test"
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,23 @@ import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||||
import com.tangem.domain.card.ScanCardProcessor
|
import com.tangem.domain.card.ScanCardProcessor
|
||||||
import com.tangem.domain.card.repository.CardRepository
|
import com.tangem.domain.card.repository.CardRepository
|
||||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
|
||||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
|
||||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||||
|
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
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.CurrenciesRepository
|
||||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
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.common.log.TangemAppLoggerInitializer
|
||||||
|
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
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.AppStateHolder
|
||||||
import dagger.hilt.EntryPoint
|
import dagger.hilt.EntryPoint
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -56,7 +55,7 @@ interface ApplicationEntryPoint {
|
||||||
|
|
||||||
fun getNetworkConnectionManager(): NetworkConnectionManager
|
fun getNetworkConnectionManager(): NetworkConnectionManager
|
||||||
|
|
||||||
fun getCustomTokenFeatureToggles(): CustomTokenFeatureToggles
|
fun getCardScanningFeatureToggles(): CardScanningFeatureToggles
|
||||||
|
|
||||||
fun getWalletConnect2Repository(): WalletConnect2Repository
|
fun getWalletConnect2Repository(): WalletConnect2Repository
|
||||||
|
|
||||||
|
|
@ -94,20 +93,16 @@ interface ApplicationEntryPoint {
|
||||||
|
|
||||||
fun getCardRepository(): CardRepository
|
fun getCardRepository(): CardRepository
|
||||||
|
|
||||||
fun getFeedbackManagerFeatureToggles(): FeedbackManagerFeatureToggles
|
|
||||||
|
|
||||||
fun getTangemSdkLogger(): TangemSdkLogger
|
fun getTangemSdkLogger(): TangemSdkLogger
|
||||||
|
|
||||||
fun getSettingsRepository(): SettingsRepository
|
fun getSettingsRepository(): SettingsRepository
|
||||||
|
|
||||||
fun getBlockchainSDKFactory(): BlockchainSDKFactory
|
fun getBlockchainSDKFactory(): BlockchainSDKFactory
|
||||||
|
|
||||||
fun getGetFeedbackEmailUseCase(): GetFeedbackEmailUseCase
|
fun getSendFeedbackEmailUseCase(): SendFeedbackEmailUseCase
|
||||||
|
|
||||||
fun getSaveBlockchainErrorUseCase(): SaveBlockchainErrorUseCase
|
fun getSaveBlockchainErrorUseCase(): SaveBlockchainErrorUseCase
|
||||||
|
|
||||||
fun getDetailsFeatureToggles(): DetailsFeatureToggles
|
|
||||||
|
|
||||||
fun getGetCardInfoUseCase(): GetCardInfoUseCase
|
fun getGetCardInfoUseCase(): GetCardInfoUseCase
|
||||||
|
|
||||||
fun getUrlOpener(): UrlOpener
|
fun getUrlOpener(): UrlOpener
|
||||||
|
|
@ -116,7 +111,9 @@ interface ApplicationEntryPoint {
|
||||||
|
|
||||||
fun getAppRouter(): AppRouter
|
fun getAppRouter(): AppRouter
|
||||||
|
|
||||||
fun getPushNotificationsFeatureToggles(): PushNotificationsFeatureToggles
|
|
||||||
|
|
||||||
fun getTangemAppLogger(): TangemAppLoggerInitializer
|
fun getTangemAppLogger(): TangemAppLoggerInitializer
|
||||||
|
|
||||||
|
fun getHomeFeatureToggles(): HomeFeatureToggles
|
||||||
|
|
||||||
|
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +67,6 @@ import com.tangem.tap.common.OnActivityResultCallback
|
||||||
import com.tangem.tap.common.SnackbarHandler
|
import com.tangem.tap.common.SnackbarHandler
|
||||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
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.extensions.showFragmentAllowingStateLoss
|
||||||
import com.tangem.tap.common.redux.NotificationsHandler
|
import com.tangem.tap.common.redux.NotificationsHandler
|
||||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||||
|
|
@ -80,7 +79,6 @@ import com.tangem.tap.features.main.model.Toast
|
||||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
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.RoutingComponent
|
||||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||||
|
|
@ -550,11 +548,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val toggles = store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles)
|
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
|
||||||
val isPushPermissionEnabled = toggles.isPushNotificationsEnabled
|
val shouldShowInitialPush = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false }
|
||||||
val shouldShowTos = !cardRepository.isTangemTOSAccepted() && isPushPermissionEnabled
|
|
||||||
val wasPushInitiallyAsked = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false }
|
|
||||||
val shouldShowInitialPush = wasPushInitiallyAsked && isPushPermissionEnabled
|
|
||||||
|
|
||||||
val route = when {
|
val route = when {
|
||||||
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
package com.tangem.tap
|
package com.tangem.tap
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.ImageLoaderFactory
|
import coil.ImageLoaderFactory
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||||
import com.tangem.Log
|
import com.tangem.Log
|
||||||
import com.tangem.LogFormat
|
|
||||||
import com.tangem.TangemSdkLogger
|
import com.tangem.TangemSdkLogger
|
||||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||||
|
|
@ -19,7 +16,6 @@ import com.tangem.datasource.api.common.MoshiConverter
|
||||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||||
import com.tangem.datasource.asset.loader.AssetLoader
|
import com.tangem.datasource.asset.loader.AssetLoader
|
||||||
import com.tangem.datasource.config.ConfigManager
|
import com.tangem.datasource.config.ConfigManager
|
||||||
import com.tangem.datasource.config.FeaturesLocalLoader
|
|
||||||
import com.tangem.datasource.config.models.Config
|
import com.tangem.datasource.config.models.Config
|
||||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
|
@ -30,36 +26,32 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||||
import com.tangem.domain.card.ScanCardProcessor
|
import com.tangem.domain.card.ScanCardProcessor
|
||||||
import com.tangem.domain.card.repository.CardRepository
|
import com.tangem.domain.card.repository.CardRepository
|
||||||
import com.tangem.domain.common.LogConfig
|
import com.tangem.domain.common.LogConfig
|
||||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
|
||||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
|
||||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||||
|
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
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.CurrenciesRepository
|
||||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
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.AnalyticsFactory
|
||||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||||
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
|
|
||||||
import com.tangem.tap.common.feedback.LegacyFeedbackManager
|
|
||||||
import com.tangem.tap.common.images.createCoilImageLoader
|
import com.tangem.tap.common.images.createCoilImageLoader
|
||||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||||
import com.tangem.tap.common.log.TangemLogCollector
|
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.common.redux.appReducer
|
import com.tangem.tap.common.redux.appReducer
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
|
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||||
import com.tangem.tap.domain.tasks.product.DerivationsFinder
|
import com.tangem.tap.domain.tasks.product.DerivationsFinder
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
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.AppStateHolder
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||||
|
|
@ -96,8 +88,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
private val networkConnectionManager: NetworkConnectionManager
|
private val networkConnectionManager: NetworkConnectionManager
|
||||||
get() = entryPoint.getNetworkConnectionManager()
|
get() = entryPoint.getNetworkConnectionManager()
|
||||||
|
|
||||||
private val customTokenFeatureToggles: CustomTokenFeatureToggles
|
private val cardScanningFeatureToggles: CardScanningFeatureToggles
|
||||||
get() = entryPoint.getCustomTokenFeatureToggles()
|
get() = entryPoint.getCardScanningFeatureToggles()
|
||||||
|
|
||||||
private val walletConnect2Repository: WalletConnect2Repository
|
private val walletConnect2Repository: WalletConnect2Repository
|
||||||
get() = entryPoint.getWalletConnect2Repository()
|
get() = entryPoint.getWalletConnect2Repository()
|
||||||
|
|
@ -153,9 +145,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
private val cardRepository: CardRepository
|
private val cardRepository: CardRepository
|
||||||
get() = entryPoint.getCardRepository()
|
get() = entryPoint.getCardRepository()
|
||||||
|
|
||||||
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles
|
|
||||||
get() = entryPoint.getFeedbackManagerFeatureToggles()
|
|
||||||
|
|
||||||
private val tangemSdkLogger: TangemSdkLogger
|
private val tangemSdkLogger: TangemSdkLogger
|
||||||
get() = entryPoint.getTangemSdkLogger()
|
get() = entryPoint.getTangemSdkLogger()
|
||||||
|
|
||||||
|
|
@ -165,8 +154,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
private val blockchainSDKFactory: BlockchainSDKFactory
|
private val blockchainSDKFactory: BlockchainSDKFactory
|
||||||
get() = entryPoint.getBlockchainSDKFactory()
|
get() = entryPoint.getBlockchainSDKFactory()
|
||||||
|
|
||||||
private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase
|
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase
|
||||||
get() = entryPoint.getGetFeedbackEmailUseCase()
|
get() = entryPoint.getSendFeedbackEmailUseCase()
|
||||||
|
|
||||||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase
|
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase
|
||||||
get() = entryPoint.getSaveBlockchainErrorUseCase()
|
get() = entryPoint.getSaveBlockchainErrorUseCase()
|
||||||
|
|
@ -174,9 +163,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
private val getCardInfoUseCase: GetCardInfoUseCase
|
private val getCardInfoUseCase: GetCardInfoUseCase
|
||||||
get() = entryPoint.getGetCardInfoUseCase()
|
get() = entryPoint.getGetCardInfoUseCase()
|
||||||
|
|
||||||
private val detailsFeatureToggles: DetailsFeatureToggles
|
|
||||||
get() = entryPoint.getDetailsFeatureToggles()
|
|
||||||
|
|
||||||
private val urlOpener
|
private val urlOpener
|
||||||
get() = entryPoint.getUrlOpener()
|
get() = entryPoint.getUrlOpener()
|
||||||
|
|
||||||
|
|
@ -186,11 +172,14 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
private val appRouter: AppRouter
|
private val appRouter: AppRouter
|
||||||
get() = entryPoint.getAppRouter()
|
get() = entryPoint.getAppRouter()
|
||||||
|
|
||||||
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles
|
|
||||||
get() = entryPoint.getPushNotificationsFeatureToggles()
|
|
||||||
|
|
||||||
private val tangemAppLoggerInitializer: TangemAppLoggerInitializer
|
private val tangemAppLoggerInitializer: TangemAppLoggerInitializer
|
||||||
get() = entryPoint.getTangemAppLogger()
|
get() = entryPoint.getTangemAppLogger()
|
||||||
|
|
||||||
|
private val homeFeatureToggles: HomeFeatureToggles
|
||||||
|
get() = entryPoint.getHomeFeatureToggles()
|
||||||
|
|
||||||
|
private val getUserCountryUseCase: GetUserCountryUseCase
|
||||||
|
get() = entryPoint.getGetUserCountryCodeUseCase()
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
|
@ -212,10 +201,11 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
// [REDACTED_JIRA]
|
// [REDACTED_JIRA]
|
||||||
runBlocking {
|
runBlocking {
|
||||||
featureTogglesManager.init()
|
featureTogglesManager.init()
|
||||||
initConfigManager(
|
|
||||||
loader = FeaturesLocalLoader(assetLoader, BuildConfig.ENVIRONMENT),
|
val config = configManager.initialize()
|
||||||
onComplete = ::initWithConfigDependency,
|
store.dispatch(GlobalAction.SetConfigManager(configManager))
|
||||||
)
|
|
||||||
|
initWithConfigDependency(config = config)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNativeLibraries()
|
loadNativeLibraries()
|
||||||
|
|
@ -233,7 +223,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
)
|
)
|
||||||
appStateHolder.mainStore = store
|
appStateHolder.mainStore = store
|
||||||
|
|
||||||
walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId)
|
walletConnect2Repository.init(projectId = configManager.getConfigSync().walletConnectProjectId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createReduxStore(): Store<AppState> {
|
private fun createReduxStore(): Store<AppState> {
|
||||||
|
|
@ -243,7 +233,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
state = AppState(
|
state = AppState(
|
||||||
daggerGraphState = DaggerGraphState(
|
daggerGraphState = DaggerGraphState(
|
||||||
networkConnectionManager = networkConnectionManager,
|
networkConnectionManager = networkConnectionManager,
|
||||||
customTokenFeatureToggles = customTokenFeatureToggles,
|
cardScanningFeatureToggles = cardScanningFeatureToggles,
|
||||||
walletConnectRepository = walletConnect2Repository,
|
walletConnectRepository = walletConnect2Repository,
|
||||||
walletConnectSessionsRepository = walletConnectSessionsRepository,
|
walletConnectSessionsRepository = walletConnectSessionsRepository,
|
||||||
scanCardProcessor = scanCardProcessor,
|
scanCardProcessor = scanCardProcessor,
|
||||||
|
|
@ -260,19 +250,18 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
|
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
|
||||||
generateWalletNameUseCase = generateWalletNameUseCase,
|
generateWalletNameUseCase = generateWalletNameUseCase,
|
||||||
cardRepository = cardRepository,
|
cardRepository = cardRepository,
|
||||||
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
|
|
||||||
tangemSdkLogger = tangemSdkLogger,
|
tangemSdkLogger = tangemSdkLogger,
|
||||||
settingsRepository = settingsRepository,
|
settingsRepository = settingsRepository,
|
||||||
blockchainSDKFactory = blockchainSDKFactory,
|
blockchainSDKFactory = blockchainSDKFactory,
|
||||||
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
|
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
|
||||||
getFeedbackEmailUseCase = getFeedbackEmailUseCase,
|
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
|
||||||
getCardInfoUseCase = getCardInfoUseCase,
|
getCardInfoUseCase = getCardInfoUseCase,
|
||||||
assetLoader = assetLoader,
|
assetLoader = assetLoader,
|
||||||
detailsFeatureToggles = detailsFeatureToggles,
|
|
||||||
urlOpener = urlOpener,
|
urlOpener = urlOpener,
|
||||||
shareManager = shareManager,
|
shareManager = shareManager,
|
||||||
appRouter = appRouter,
|
appRouter = appRouter,
|
||||||
pushNotificationsFeatureToggles = pushNotificationsFeatureToggles,
|
homeFeatureToggles = homeFeatureToggles,
|
||||||
|
getUserCountryUseCase = getUserCountryUseCase,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -289,16 +278,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
System.loadLibrary("TrustWalletCore")
|
System.loadLibrary("TrustWalletCore")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initConfigManager(loader: FeaturesLocalLoader, onComplete: (Config) -> Unit) {
|
|
||||||
configManager.load(loader) { config ->
|
|
||||||
store.dispatch(GlobalAction.SetConfigManager(configManager))
|
|
||||||
onComplete(config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initWithConfigDependency(config: Config) {
|
private fun initWithConfigDependency(config: Config) {
|
||||||
initAnalytics(this, config)
|
initAnalytics(this, config)
|
||||||
initFeedbackManager(this, store)
|
Log.addLogger(logger = tangemSdkLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initAnalytics(application: Application, config: Config) {
|
private fun initAnalytics(application: Application, config: Config) {
|
||||||
|
|
@ -318,51 +300,4 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
factory.build(Analytics, buildData)
|
factory.build(Analytics, buildData)
|
||||||
// ExceptionHandler.append(blockchainExceptionHandler) TODO: [REDACTED_JIRA]
|
// ExceptionHandler.append(blockchainExceptionHandler) TODO: [REDACTED_JIRA]
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initFeedbackManager(context: Context, store: Store<AppState>) {
|
|
||||||
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo {
|
|
||||||
return AdditionalFeedbackInfo().apply {
|
|
||||||
appVersion = try {
|
|
||||||
// TODO don't use deprecated method
|
|
||||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
|
||||||
pInfo.versionName
|
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
"x.y.z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun initTangemLogCollector(): TangemLogCollector {
|
|
||||||
val logLevels = listOf(
|
|
||||||
Log.Level.ApduCommand,
|
|
||||||
Log.Level.Apdu,
|
|
||||||
Log.Level.Tlv,
|
|
||||||
Log.Level.Nfc,
|
|
||||||
Log.Level.Command,
|
|
||||||
Log.Level.Session,
|
|
||||||
Log.Level.View,
|
|
||||||
Log.Level.Network,
|
|
||||||
Log.Level.Error,
|
|
||||||
Log.Level.Biometric,
|
|
||||||
Log.Level.Info,
|
|
||||||
)
|
|
||||||
return TangemLogCollector(logLevels, LogFormat.StairsFormatter())
|
|
||||||
}
|
|
||||||
|
|
||||||
val additionalFeedbackInfo = initAdditionalFeedbackInfo(context)
|
|
||||||
val tangemLogCollector = initTangemLogCollector()
|
|
||||||
|
|
||||||
Log.addLogger(
|
|
||||||
logger = if (feedbackManagerFeatureToggles.isLocalLogsEnabled) tangemSdkLogger else tangemLogCollector,
|
|
||||||
)
|
|
||||||
|
|
||||||
val feedbackManager = LegacyFeedbackManager(
|
|
||||||
infoHolder = additionalFeedbackInfo,
|
|
||||||
logCollector = tangemLogCollector,
|
|
||||||
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
|
|
||||||
getFeedbackEmailUseCase = getFeedbackEmailUseCase,
|
|
||||||
)
|
|
||||||
store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
package com.tangem.tap.common.analytics.events
|
|
||||||
|
|
||||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
|
||||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
|
||||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
|
||||||
import com.tangem.tap.common.extensions.filterNotNull
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
sealed class ManageTokens(
|
|
||||||
event: String,
|
|
||||||
params: Map<String, String> = mapOf(),
|
|
||||||
) : AnalyticsEvent("Manage Tokens", event, params) {
|
|
||||||
|
|
||||||
class ScreenOpened : ManageTokens("Manage Tokens Screen Opened")
|
|
||||||
class TokenSearched : ManageTokens("Token Searched")
|
|
||||||
|
|
||||||
class TokenSwitcherChanged(
|
|
||||||
type: AnalyticsParam.CurrencyType,
|
|
||||||
state: AnalyticsParam.OnOffState,
|
|
||||||
) : ManageTokens(
|
|
||||||
"Token Switcher Changed",
|
|
||||||
params = mapOf(
|
|
||||||
"Token" to type.value,
|
|
||||||
"State" to state.value,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
class ButtonSaveChanges : ManageTokens("Button - Save Changes")
|
|
||||||
class ButtonCustomToken : ManageTokens("Button - Custom Token")
|
|
||||||
|
|
||||||
sealed class CustomToken(
|
|
||||||
event: String,
|
|
||||||
params: Map<String, String> = mapOf(),
|
|
||||||
) : ManageTokens(event, params) {
|
|
||||||
|
|
||||||
object ScreenOpened : ManageTokens(event = "Custom Token Screen Opened")
|
|
||||||
|
|
||||||
// TODO("Get rid of strong binding (CustomCurrency)
|
|
||||||
open class TokenWasAdded(customCurrency: CustomCurrency) : ManageTokens(
|
|
||||||
event = "Custom Token Was Added",
|
|
||||||
params = convertToParam(customCurrency),
|
|
||||||
) {
|
|
||||||
|
|
||||||
data class Token(
|
|
||||||
val symbol: String,
|
|
||||||
val derivationPath: String?,
|
|
||||||
val blockchain: com.tangem.blockchain.common.Blockchain,
|
|
||||||
val contractAddress: String,
|
|
||||||
) : ManageTokens(
|
|
||||||
event = "Custom Token Was Added",
|
|
||||||
params = mapOf(
|
|
||||||
"Token" to symbol,
|
|
||||||
"Derivation Path" to derivationPath,
|
|
||||||
"Network Id" to blockchain.currency,
|
|
||||||
"Contract Address" to contractAddress,
|
|
||||||
).filterNotNull(),
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Blockchain(
|
|
||||||
val derivationPath: String?,
|
|
||||||
val blockchain: com.tangem.blockchain.common.Blockchain,
|
|
||||||
) : ManageTokens(
|
|
||||||
event = "Custom Token Was Added",
|
|
||||||
params = mapOf(
|
|
||||||
"Token" to blockchain.currency,
|
|
||||||
"Derivation Path" to derivationPath,
|
|
||||||
).filterNotNull(),
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private fun convertToParam(customCurrency: CustomCurrency): Map<String, String> = with(customCurrency) {
|
|
||||||
return when (this) {
|
|
||||||
is CustomCurrency.CustomBlockchain -> mapOf(
|
|
||||||
"Token" to network.currency,
|
|
||||||
"Derivation Path" to derivationPath?.rawPath,
|
|
||||||
).filterNotNull()
|
|
||||||
|
|
||||||
is CustomCurrency.CustomToken -> mapOf(
|
|
||||||
"Token" to token.symbol,
|
|
||||||
"Derivation Path" to derivationPath?.rawPath,
|
|
||||||
"Network Id" to network.toNetworkId(),
|
|
||||||
"Contract Address" to token.contractAddress,
|
|
||||||
).filterNotNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.tangem.tap.common.analytics.events
|
package com.tangem.tap.common.analytics.events
|
||||||
|
|
||||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
import com.tangem.tap.features.details.ui.details.SocialNetwork
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
|
|
@ -14,20 +13,8 @@ sealed class Settings(
|
||||||
) : AnalyticsEvent(category, event, params, error) {
|
) : AnalyticsEvent(category, event, params, error) {
|
||||||
|
|
||||||
class ScreenOpened : Settings(event = "Settings Screen Opened")
|
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 ButtonStartWalletConnectSession : Settings(event = "Button - Start Wallet Connect Session")
|
||||||
class ButtonStopWalletConnectSession : Settings(event = "Button - Stop 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(
|
sealed class CardSettings(
|
||||||
event: String,
|
event: String,
|
||||||
|
|
@ -63,8 +50,6 @@ sealed class Settings(
|
||||||
error = error,
|
error = error,
|
||||||
)
|
)
|
||||||
|
|
||||||
class AccessCodeRecoveryButton : CardSettings("Button - Access Code Recovery")
|
|
||||||
|
|
||||||
class AccessCodeRecoveryChanged(status: AnalyticsParam.AccessCodeRecoveryStatus) : CardSettings(
|
class AccessCodeRecoveryChanged(status: AnalyticsParam.AccessCodeRecoveryStatus) : CardSettings(
|
||||||
event = "Access Code Recovery Changed",
|
event = "Access Code Recovery Changed",
|
||||||
params = mapOf(status.key to status.value),
|
params = mapOf(status.key to status.value),
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,4 @@ fun BigDecimal.toFormattedString(
|
||||||
df.isGroupingUsed = true
|
df.isGroupingUsed = true
|
||||||
df.roundingMode = roundingMode
|
df.roundingMode = roundingMode
|
||||||
return df.format(this)
|
return df.format(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
|
|
||||||
|
|
||||||
fun BigDecimal.isPositive(): Boolean = this.signum() == 1
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
package com.tangem.tap.common.feedback
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import com.tangem.blockchain.common.*
|
|
||||||
import com.tangem.blockchain.common.address.Address
|
|
||||||
import com.tangem.crypto.NetworkType
|
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
|
||||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
|
||||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
|
||||||
|
|
||||||
class AdditionalFeedbackInfo {
|
|
||||||
|
|
||||||
class EmailWalletInfo(
|
|
||||||
val blockchain: Blockchain = Blockchain.Unknown,
|
|
||||||
val derivationPath: String = "",
|
|
||||||
val outputsCount: String? = null,
|
|
||||||
val host: String = "",
|
|
||||||
val addresses: String = "",
|
|
||||||
val explorerLink: String = "",
|
|
||||||
val tokens: List<EmailTokenInfo> = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
class EmailTokenInfo(
|
|
||||||
val id: String?,
|
|
||||||
val name: String,
|
|
||||||
val contractAddress: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
var appVersion: String = ""
|
|
||||||
|
|
||||||
// card
|
|
||||||
var cardId: String = ""
|
|
||||||
var cardFirmwareVersion: String = ""
|
|
||||||
var cardIssuer: String = ""
|
|
||||||
var cardBlockchain: String = ""
|
|
||||||
var userWalletId: String = ""
|
|
||||||
var extendedPublicKey: String = ""
|
|
||||||
|
|
||||||
// wallets
|
|
||||||
val walletsInfo = CopyOnWriteArrayList<EmailWalletInfo>()
|
|
||||||
var onSendErrorWalletInfo: EmailWalletInfo? = null
|
|
||||||
private set
|
|
||||||
var signedHashesCount: String = ""
|
|
||||||
private set
|
|
||||||
|
|
||||||
// device
|
|
||||||
var phoneModel: String = Build.MODEL
|
|
||||||
private set
|
|
||||||
var osVersion: String = Build.VERSION.SDK_INT.toString()
|
|
||||||
private set
|
|
||||||
|
|
||||||
// send error
|
|
||||||
var destinationAddress: String = ""
|
|
||||||
private set
|
|
||||||
var amount: String = ""
|
|
||||||
private set
|
|
||||||
var fee: String = ""
|
|
||||||
private set
|
|
||||||
var token: String = ""
|
|
||||||
private set
|
|
||||||
|
|
||||||
private val Address.name: String
|
|
||||||
get() = type.javaClass.simpleName
|
|
||||||
|
|
||||||
@Deprecated("Don't use it directly")
|
|
||||||
fun setCardInfo(data: ScanResponse) {
|
|
||||||
cardId = data.card.cardId
|
|
||||||
cardBlockchain = data.walletData?.blockchain ?: ""
|
|
||||||
cardFirmwareVersion = data.card.firmwareVersion.stringValue
|
|
||||||
cardIssuer = data.card.issuer.name
|
|
||||||
signedHashesCount = formatSignedHashes(data.card.wallets)
|
|
||||||
userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: ""
|
|
||||||
extendedPublicKey = runCatching {
|
|
||||||
data.card.wallets.firstOrNull { it.extendedPublicKey != null }
|
|
||||||
?.extendedPublicKey
|
|
||||||
?.serialize(networkType = NetworkType.Mainnet).orEmpty()
|
|
||||||
}.getOrDefault("couldn't retrieve extended public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Don't use it directly")
|
|
||||||
fun setWalletsInfo(walletManagers: List<WalletManager>) {
|
|
||||||
synchronized(walletsInfo) {
|
|
||||||
walletsInfo.clear()
|
|
||||||
walletsInfo.addAll(elements = walletManagers.map(::createEmailWalletInfo))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateOnSendError(
|
|
||||||
walletManager: WalletManager,
|
|
||||||
amountToSend: Amount?,
|
|
||||||
feeAmount: Amount?,
|
|
||||||
destinationAddress: String?,
|
|
||||||
) {
|
|
||||||
onSendErrorWalletInfo = createEmailWalletInfo(walletManager)
|
|
||||||
this.destinationAddress = destinationAddress ?: "0"
|
|
||||||
amount = amountToSend?.value?.stripZeroPlainString() ?: "0"
|
|
||||||
fee = if (feeAmount != null) {
|
|
||||||
feeAmount.value?.stripZeroPlainString() ?: "0"
|
|
||||||
} else {
|
|
||||||
"Unable to receive"
|
|
||||||
}
|
|
||||||
token = if (amountToSend?.type is AmountType.Token) amountToSend.currencySymbol else ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createEmailWalletInfo(walletManager: WalletManager): EmailWalletInfo {
|
|
||||||
return EmailWalletInfo(
|
|
||||||
blockchain = walletManager.wallet.blockchain,
|
|
||||||
derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath ?: "",
|
|
||||||
outputsCount = walletManager.outputsCount?.toString(),
|
|
||||||
host = walletManager.currentHost,
|
|
||||||
addresses = formatAddresses(walletManager.wallet),
|
|
||||||
explorerLink = formatExploreUrls(walletManager.wallet),
|
|
||||||
tokens = walletManager.cardTokens.map { token ->
|
|
||||||
EmailTokenInfo(token.id, token.name, token.contractAddress)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatSignedHashes(wallets: List<CardDTO.Wallet>): String {
|
|
||||||
return wallets.joinToString("\n") { "Signed hashes: ${it.curve.curve} - ${it.totalSignedHashes}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatAddresses(wallet: Wallet): String {
|
|
||||||
return wallet.formatAddressWith("Multiple address:") {
|
|
||||||
"${it.name} - ${it.value}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatExploreUrls(wallet: Wallet): String {
|
|
||||||
return wallet.formatAddressWith("Multiple explorers links:") {
|
|
||||||
"${it.name} - ${wallet.getExploreUrl(it.value)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
|
||||||
private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String {
|
|
||||||
return if (addresses.size == 1) {
|
|
||||||
getExploreUrl(address)
|
|
||||||
} else {
|
|
||||||
addresses.map { mapAddress(it) }.toMutableList()
|
|
||||||
.apply { add(0, with) }
|
|
||||||
.joinToString("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package com.tangem.tap.common.feedback
|
|
||||||
|
|
||||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
|
||||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
|
||||||
|
|
||||||
internal class DefaultFeedbackManagerFeatureToggles(
|
|
||||||
private val featureTogglesManager: FeatureTogglesManager,
|
|
||||||
) : FeedbackManagerFeatureToggles {
|
|
||||||
|
|
||||||
override val isLocalLogsEnabled: Boolean
|
|
||||||
get() = featureTogglesManager.isFeatureEnabled(name = "LOCAL_USER_LOGS_ENABLED")
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
package com.tangem.tap.common.feedback
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.tangem.domain.common.TapWorkarounds
|
|
||||||
import com.tangem.tap.common.extensions.breakLine
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
|
|
||||||
interface FeedbackData {
|
|
||||||
val subjectResId: Int
|
|
||||||
val mainMessageResId: Int
|
|
||||||
|
|
||||||
fun getDataCollectionMessageResId(): Int = R.string.feedback_data_collection_message
|
|
||||||
|
|
||||||
fun prepare(infoHolder: AdditionalFeedbackInfo) {}
|
|
||||||
|
|
||||||
fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String
|
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
|
||||||
fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String {
|
|
||||||
return StringBuilder().apply {
|
|
||||||
append(context.getString(mainMessageResId))
|
|
||||||
breakLine(3)
|
|
||||||
append(context.getString(getDataCollectionMessageResId()))
|
|
||||||
breakLine()
|
|
||||||
append(createOptionalMessage(infoHolder))
|
|
||||||
}.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RateCanBeBetterEmail : FeedbackData {
|
|
||||||
override val subjectResId: Int = R.string.feedback_subject_rate_negative
|
|
||||||
override val mainMessageResId: Int = R.string.feedback_preface_rate_negative
|
|
||||||
|
|
||||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
|
|
||||||
.appendCardInfo()
|
|
||||||
.breakLine()
|
|
||||||
.appendPhoneInfo()
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScanFailsEmail : FeedbackData {
|
|
||||||
|
|
||||||
override val subjectResId: Int = R.string.feedback_subject_scan_failed
|
|
||||||
override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
|
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
|
||||||
override fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String = StringBuilder().apply {
|
|
||||||
append(context.getString(mainMessageResId))
|
|
||||||
breakLine(4)
|
|
||||||
append(createOptionalMessage(infoHolder))
|
|
||||||
}.toString()
|
|
||||||
|
|
||||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
|
|
||||||
.appendPhoneInfo()
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
class SendTransactionFailedEmail(
|
|
||||||
val error: String,
|
|
||||||
) : FeedbackData {
|
|
||||||
|
|
||||||
override val subjectResId: Int = R.string.feedback_subject_tx_failed
|
|
||||||
override val mainMessageResId: Int = R.string.feedback_preface_tx_failed
|
|
||||||
|
|
||||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
|
|
||||||
.appendCardInfo()
|
|
||||||
.appendDelimiter()
|
|
||||||
.appendTxFailedBlockchainInfo(error)
|
|
||||||
.breakLine()
|
|
||||||
.appendPhoneInfo()
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
class FeedbackEmail : FeedbackData {
|
|
||||||
override val subjectResId: Int
|
|
||||||
get() = if (isS2CCard) R.string.feedback_subject_support else R.string.feedback_subject_support_tangem
|
|
||||||
|
|
||||||
override val mainMessageResId: Int = R.string.feedback_preface_support
|
|
||||||
|
|
||||||
private var isS2CCard = false
|
|
||||||
|
|
||||||
override fun prepare(infoHolder: AdditionalFeedbackInfo) {
|
|
||||||
isS2CCard = TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
|
|
||||||
.appendCardInfo()
|
|
||||||
.appendWalletsInfo()
|
|
||||||
.breakLine()
|
|
||||||
.appendPhoneInfo()
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
class SupportInfo : FeedbackData {
|
|
||||||
override val subjectResId: Int = R.string.details_chat
|
|
||||||
override val mainMessageResId: Int = R.string.details_chat
|
|
||||||
|
|
||||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String {
|
|
||||||
return FeedbackDataBuilder(infoHolder)
|
|
||||||
.appendCardInfo()
|
|
||||||
.appendDelimiter()
|
|
||||||
.appendPhoneInfo()
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package com.tangem.tap.common.feedback
|
|
||||||
|
|
||||||
import com.tangem.tap.common.extensions.breakLine
|
|
||||||
|
|
||||||
class FeedbackDataBuilder(
|
|
||||||
private val infoHolder: AdditionalFeedbackInfo,
|
|
||||||
) {
|
|
||||||
val builder = StringBuilder()
|
|
||||||
|
|
||||||
fun appendDelimiter(): FeedbackDataBuilder {
|
|
||||||
builder.appendDelimiter()
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun breakLine(count: Int = 1): FeedbackDataBuilder {
|
|
||||||
builder.breakLine(count)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun appendCardInfo(): FeedbackDataBuilder {
|
|
||||||
builder.appendKeyValue("Card ID", infoHolder.cardId)
|
|
||||||
builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
|
|
||||||
builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain)
|
|
||||||
builder.appendKeyValue("", infoHolder.signedHashesCount)
|
|
||||||
builder.appendKeyValue("User Wallet ID", infoHolder.userWalletId)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun appendWalletsInfo(): FeedbackDataBuilder {
|
|
||||||
infoHolder.walletsInfo.forEach { walletInfo ->
|
|
||||||
builder.appendDelimiter()
|
|
||||||
builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
|
|
||||||
builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
|
|
||||||
builder.appendKeyValue("Outputs count", walletInfo.outputsCount)
|
|
||||||
|
|
||||||
if (walletInfo.tokens.isNotEmpty()) {
|
|
||||||
builder.append("Tokens:")
|
|
||||||
breakLine()
|
|
||||||
walletInfo.tokens.forEach { token ->
|
|
||||||
builder.appendKeyValue("ID", token.id ?: "[custom token]")
|
|
||||||
builder.appendKeyValue("Name", token.name)
|
|
||||||
builder.appendKeyValue("Contract address", token.contractAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.appendKeyValue("Host", walletInfo.host)
|
|
||||||
builder.appendKeyValue("Wallet address", walletInfo.addresses)
|
|
||||||
builder.appendKeyValue("Explorer link", walletInfo.explorerLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun appendTxFailedBlockchainInfo(error: String): FeedbackDataBuilder {
|
|
||||||
val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalFeedbackInfo.EmailWalletInfo()
|
|
||||||
builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
|
|
||||||
builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
|
|
||||||
builder.appendKeyValue("Host", walletInfo.host)
|
|
||||||
builder.appendKeyValue("Token", infoHolder.token)
|
|
||||||
builder.appendKeyValue("Error", error)
|
|
||||||
builder.appendDelimiter()
|
|
||||||
builder.appendKeyValue("Source address", walletInfo.addresses)
|
|
||||||
builder.appendKeyValue("Destination address", infoHolder.destinationAddress)
|
|
||||||
builder.appendKeyValue("Amount", infoHolder.amount)
|
|
||||||
builder.appendKeyValue("Fee", infoHolder.fee)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun appendPhoneInfo(): FeedbackDataBuilder {
|
|
||||||
builder.appendKeyValue("Phone model", infoHolder.phoneModel)
|
|
||||||
builder.appendKeyValue("OS version", infoHolder.osVersion)
|
|
||||||
builder.appendKeyValue("App version", infoHolder.appVersion)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun build(): String = builder.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun StringBuilder.appendKeyValue(key: String, value: String?): StringBuilder = when {
|
|
||||||
value.isNullOrBlank() -> this
|
|
||||||
key.isBlank() -> this.append("$value\n")
|
|
||||||
else -> this.append("$key: $value\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n")
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
package com.tangem.tap.common.feedback
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.tangem.core.navigation.email.EmailSender
|
|
||||||
import com.tangem.domain.common.TapWorkarounds
|
|
||||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
|
||||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
|
||||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
|
||||||
import com.tangem.tap.common.extensions.inject
|
|
||||||
import com.tangem.tap.common.extensions.sendEmail
|
|
||||||
import com.tangem.tap.common.log.TangemLogCollector
|
|
||||||
import com.tangem.tap.foregroundActivityObserver
|
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
|
||||||
import com.tangem.tap.scope
|
|
||||||
import com.tangem.tap.store
|
|
||||||
import com.tangem.tap.withForegroundActivity
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileWriter
|
|
||||||
import java.io.StringWriter
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
class LegacyFeedbackManager(
|
|
||||||
val infoHolder: AdditionalFeedbackInfo,
|
|
||||||
private val logCollector: TangemLogCollector,
|
|
||||||
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
|
|
||||||
private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private var sessionLogsFile: File? = null
|
|
||||||
|
|
||||||
fun sendEmail(feedbackData: FeedbackData, scanResponse: ScanResponse?) {
|
|
||||||
if (feedbackManagerFeatureToggles.isLocalLogsEnabled) {
|
|
||||||
scope.launch {
|
|
||||||
val getCardInfo = suspend {
|
|
||||||
scanResponse ?: error("ScanResponse must be not null")
|
|
||||||
store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()
|
|
||||||
?: error("CardInfo must be not null")
|
|
||||||
}
|
|
||||||
|
|
||||||
val email = getFeedbackEmailUseCase(
|
|
||||||
type = when (feedbackData) {
|
|
||||||
is FeedbackEmail -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
|
|
||||||
is RateCanBeBetterEmail -> FeedbackEmailType.RateCanBeBetter(cardInfo = getCardInfo())
|
|
||||||
is ScanFailsEmail -> FeedbackEmailType.ScanningProblem
|
|
||||||
is SendTransactionFailedEmail -> {
|
|
||||||
FeedbackEmailType.TransactionSendingProblem(cardInfo = getCardInfo())
|
|
||||||
}
|
|
||||||
else -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
store.inject(DaggerGraphState::emailSender).send(
|
|
||||||
email = EmailSender.Email(
|
|
||||||
address = email.address,
|
|
||||||
subject = email.subject,
|
|
||||||
message = email.message,
|
|
||||||
attachment = email.file,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
feedbackData.prepare(infoHolder)
|
|
||||||
foregroundActivityObserver.withForegroundActivity { activity ->
|
|
||||||
activity.sendEmail(
|
|
||||||
email = getSupportEmail(),
|
|
||||||
subject = activity.getString(feedbackData.subjectResId),
|
|
||||||
message = feedbackData.joinTogether(activity, infoHolder),
|
|
||||||
file = getLogFile(activity),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendEmail(type: FeedbackEmailType) {
|
|
||||||
if (!feedbackManagerFeatureToggles.isLocalLogsEnabled) error("LOCAL_LOGS feature toggle must be enabled")
|
|
||||||
|
|
||||||
scope.launch {
|
|
||||||
val email = getFeedbackEmailUseCase(type = type)
|
|
||||||
|
|
||||||
store.inject(DaggerGraphState::emailSender).send(
|
|
||||||
email = EmailSender.Email(
|
|
||||||
address = email.address,
|
|
||||||
subject = email.subject,
|
|
||||||
message = email.message,
|
|
||||||
attachment = email.file,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLogFile(context: Context): File? {
|
|
||||||
return try {
|
|
||||||
if (sessionLogsFile != null) {
|
|
||||||
return sessionLogsFile
|
|
||||||
}
|
|
||||||
val file = File(context.filesDir, LOGS_FILE)
|
|
||||||
file.delete()
|
|
||||||
file.createNewFile()
|
|
||||||
|
|
||||||
val stringWriter = StringWriter()
|
|
||||||
logCollector.getLogs().forEach { stringWriter.append(it) }
|
|
||||||
val fileWriter = FileWriter(file)
|
|
||||||
fileWriter.write(stringWriter.toString())
|
|
||||||
fileWriter.close()
|
|
||||||
logCollector.clearLogs()
|
|
||||||
if (file.exists()) {
|
|
||||||
sessionLogsFile = file
|
|
||||||
sessionLogsFile
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
Timber.e(ex, "Can't create the logs file")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSupportEmail(): String {
|
|
||||||
return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) {
|
|
||||||
S2C_SUPPORT_EMAIL
|
|
||||||
} else {
|
|
||||||
DEFAULT_SUPPORT_EMAIL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
|
|
||||||
const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
|
|
||||||
const val LOGS_FILE = "logs.txt"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package com.tangem.tap.common.feedback
|
|
||||||
|
|
||||||
import com.tangem.domain.feedback.FeedbackManager
|
|
||||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
|
||||||
import com.tangem.tap.store
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
internal class ProxyFeedbackManager : FeedbackManager {
|
|
||||||
|
|
||||||
override fun sendEmail(type: FeedbackEmailType) {
|
|
||||||
val manager = store.state.globalState.feedbackManager
|
|
||||||
|
|
||||||
if (manager == null) {
|
|
||||||
Timber.e("Feedback manager is not initialized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.sendEmail(type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package com.tangem.tap.common.log
|
|
||||||
|
|
||||||
import com.tangem.Log
|
|
||||||
import com.tangem.LogFormat
|
|
||||||
import com.tangem.TangemSdkLogger
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class TangemLogCollector(
|
|
||||||
private val levels: List<Log.Level>,
|
|
||||||
private val messageFormatter: LogFormat,
|
|
||||||
) : TangemSdkLogger {
|
|
||||||
|
|
||||||
private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS")
|
|
||||||
private val logs = mutableListOf<String>()
|
|
||||||
private val mutex = Object()
|
|
||||||
|
|
||||||
override fun log(message: () -> String, level: Log.Level) {
|
|
||||||
if (!levels.contains(level)) return
|
|
||||||
|
|
||||||
synchronized(mutex) {
|
|
||||||
val formattedMessage = messageFormatter.format(message, level)
|
|
||||||
val logMessage = "${dateFormatter.format(Date())}: $formattedMessage"
|
|
||||||
logs.add("$logMessage\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLogs(): List<String> = synchronized(mutex) { logs.toList() }
|
|
||||||
|
|
||||||
fun clearLogs() = synchronized(mutex) { logs.clear() }
|
|
||||||
}
|
|
||||||
|
|
@ -3,14 +3,12 @@ package com.tangem.tap.common.redux
|
||||||
import com.tangem.tap.common.redux.global.globalReducer
|
import com.tangem.tap.common.redux.global.globalReducer
|
||||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
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.home.redux.HomeReducer
|
||||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer
|
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer
|
||||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer
|
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer
|
||||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer
|
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer
|
||||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
|
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
|
||||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer
|
import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer
|
||||||
import com.tangem.tap.features.tokens.legacy.redux.TokensReducer
|
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||||
|
|
@ -28,8 +26,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
||||||
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
|
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
|
||||||
twinCardsState = TwinCardsReducer.reduce(action, state),
|
twinCardsState = TwinCardsReducer.reduce(action, state),
|
||||||
detailsState = DetailsReducer.reduce(action, state),
|
detailsState = DetailsReducer.reduce(action, state),
|
||||||
disclaimerState = DisclaimerReducer.reduce(action, state),
|
|
||||||
tokensState = TokensReducer.reduce(action, state),
|
|
||||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||||
welcomeState = WelcomeReducer.reduce(action, state),
|
welcomeState = WelcomeReducer.reduce(action, state),
|
||||||
saveWalletState = SaveWalletReducer.reduce(action, state),
|
saveWalletState = SaveWalletReducer.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.DetailsState
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
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.HomeMiddleware
|
||||||
import com.tangem.tap.features.home.redux.HomeState
|
import com.tangem.tap.features.home.redux.HomeState
|
||||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware
|
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware
|
||||||
|
|
@ -25,7 +23,6 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWallet
|
||||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware
|
import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware
|
||||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
|
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
|
||||||
import com.tangem.tap.features.tokens.legacy.redux.TokensState
|
|
||||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||||
|
|
@ -43,8 +40,6 @@ data class AppState(
|
||||||
val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
|
val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
|
||||||
val twinCardsState: TwinCardsState = TwinCardsState(),
|
val twinCardsState: TwinCardsState = TwinCardsState(),
|
||||||
val detailsState: DetailsState = DetailsState(),
|
val detailsState: DetailsState = DetailsState(),
|
||||||
val disclaimerState: DisclaimerState = DisclaimerState(),
|
|
||||||
val tokensState: TokensState = TokensState(),
|
|
||||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||||
val welcomeState: WelcomeState = WelcomeState(),
|
val welcomeState: WelcomeState = WelcomeState(),
|
||||||
val saveWalletState: SaveWalletState = SaveWalletState(),
|
val saveWalletState: SaveWalletState = SaveWalletState(),
|
||||||
|
|
@ -75,7 +70,6 @@ data class AppState(
|
||||||
OnboardingOtherCardsMiddleware.handler,
|
OnboardingOtherCardsMiddleware.handler,
|
||||||
TwinCardsMiddleware.handler,
|
TwinCardsMiddleware.handler,
|
||||||
DetailsMiddleware().detailsMiddleware,
|
DetailsMiddleware().detailsMiddleware,
|
||||||
DisclaimerMiddleware().disclaimerMiddleware,
|
|
||||||
WalletConnectMiddleware().walletConnectMiddleware,
|
WalletConnectMiddleware().walletConnectMiddleware,
|
||||||
BackupMiddleware().backupMiddleware,
|
BackupMiddleware().backupMiddleware,
|
||||||
WelcomeMiddleware().middleware,
|
WelcomeMiddleware().middleware,
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ import com.tangem.datasource.config.ConfigManager
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.redux.StateDialog
|
import com.tangem.domain.redux.StateDialog
|
||||||
import com.tangem.tap.common.feedback.FeedbackData
|
|
||||||
import com.tangem.tap.common.feedback.LegacyFeedbackManager
|
|
||||||
import com.tangem.tap.common.redux.DebugErrorAction
|
import com.tangem.tap.common.redux.DebugErrorAction
|
||||||
import com.tangem.tap.common.redux.ErrorAction
|
import com.tangem.tap.common.redux.ErrorAction
|
||||||
import com.tangem.tap.common.redux.NotificationAction
|
import com.tangem.tap.common.redux.NotificationAction
|
||||||
|
|
@ -70,9 +68,6 @@ sealed class GlobalAction : Action {
|
||||||
) : GlobalAction()
|
) : GlobalAction()
|
||||||
|
|
||||||
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
|
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
|
||||||
data class SetFeedbackManager(val feedbackManager: LegacyFeedbackManager) : GlobalAction()
|
|
||||||
|
|
||||||
data class SendEmail(val feedbackData: FeedbackData, val scanResponse: ScanResponse?) : GlobalAction()
|
|
||||||
|
|
||||||
object ExchangeManager : GlobalAction() {
|
object ExchangeManager : GlobalAction() {
|
||||||
object Init : GlobalAction() {
|
object Init : GlobalAction() {
|
||||||
|
|
|
||||||
|
|
@ -55,15 +55,9 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
||||||
is GlobalAction.RestoreAppCurrency -> {
|
is GlobalAction.RestoreAppCurrency -> {
|
||||||
restoreAppCurrency()
|
restoreAppCurrency()
|
||||||
}
|
}
|
||||||
is GlobalAction.SendEmail -> {
|
|
||||||
store.state.globalState.feedbackManager?.sendEmail(
|
|
||||||
feedbackData = action.feedbackData,
|
|
||||||
scanResponse = action.scanResponse,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is GlobalAction.ExchangeManager.Init -> {
|
is GlobalAction.ExchangeManager.Init -> {
|
||||||
val appStateSafe = appState() ?: return
|
val appStateSafe = appState() ?: return
|
||||||
val config = appStateSafe.globalState.configManager?.config ?: return
|
val config = appStateSafe.globalState.configManager?.getConfigSync() ?: return
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val scanResponseProvider: () -> ScanResponse? = {
|
val scanResponseProvider: () -> ScanResponse? = {
|
||||||
|
|
@ -92,21 +86,24 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
||||||
scope.launch { exchangeManager.update() }
|
scope.launch { exchangeManager.update() }
|
||||||
}
|
}
|
||||||
is GlobalAction.FetchUserCountry -> {
|
is GlobalAction.FetchUserCountry -> {
|
||||||
scope.launch {
|
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
|
||||||
// TODO("After adding DI") get dependencies by DI
|
|
||||||
runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() }
|
if (!homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
|
||||||
.onSuccess {
|
scope.launch {
|
||||||
store.dispatchOnMain(
|
runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() }
|
||||||
GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()),
|
.onSuccess {
|
||||||
)
|
store.dispatchOnMain(
|
||||||
}
|
GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()),
|
||||||
.onFailure {
|
)
|
||||||
store.dispatchOnMain(
|
}
|
||||||
GlobalAction.FetchUserCountry.Success(
|
.onFailure {
|
||||||
countryCode = Locale.getDefault().country.lowercase(),
|
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.domainStore
|
||||||
import com.tangem.domain.redux.global.DomainGlobalAction
|
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.common.redux.AppState
|
||||||
import com.tangem.tap.features.home.redux.HomeAction
|
|
||||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
import com.tangem.tap.store
|
|
||||||
import com.tangem.utils.extensions.replaceBy
|
import com.tangem.utils.extensions.replaceBy
|
||||||
import org.rekotlin.Action
|
import org.rekotlin.Action
|
||||||
|
|
||||||
|
|
@ -71,9 +68,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
||||||
)
|
)
|
||||||
globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance))
|
globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance))
|
||||||
}
|
}
|
||||||
is GlobalAction.SetFeedbackManager -> {
|
|
||||||
globalState.copy(feedbackManager = action.feedbackManager)
|
|
||||||
}
|
|
||||||
is GlobalAction.ShowDialog -> {
|
is GlobalAction.ShowDialog -> {
|
||||||
globalState.copy(dialog = action.stateDialog)
|
globalState.copy(dialog = action.stateDialog)
|
||||||
}
|
}
|
||||||
|
|
@ -86,10 +80,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
||||||
is GlobalAction.SetIfCardVerifiedOnline ->
|
is GlobalAction.SetIfCardVerifiedOnline ->
|
||||||
globalState.copy(cardVerifiedOnline = action.verified)
|
globalState.copy(cardVerifiedOnline = action.verified)
|
||||||
is GlobalAction.FetchUserCountry.Success -> {
|
is GlobalAction.FetchUserCountry.Success -> {
|
||||||
store.dispatchOnMain(HomeAction.UpdateCountryCode(action.countryCode))
|
globalState.copy(userCountryCode = action.countryCode)
|
||||||
globalState.copy(
|
|
||||||
userCountryCode = action.countryCode,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
else -> globalState
|
else -> globalState
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.redux.StateDialog
|
import com.tangem.domain.redux.StateDialog
|
||||||
import com.tangem.tap.common.feedback.LegacyFeedbackManager
|
|
||||||
import com.tangem.tap.domain.TapWalletManager
|
import com.tangem.tap.domain.TapWalletManager
|
||||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||||
|
|
@ -18,11 +17,11 @@ data class GlobalState(
|
||||||
val cardVerifiedOnline: Boolean = false,
|
val cardVerifiedOnline: Boolean = false,
|
||||||
val tapWalletManager: TapWalletManager = TapWalletManager(),
|
val tapWalletManager: TapWalletManager = TapWalletManager(),
|
||||||
val configManager: ConfigManager? = null,
|
val configManager: ConfigManager? = null,
|
||||||
val feedbackManager: LegacyFeedbackManager? = null,
|
|
||||||
val appCurrency: AppCurrency = AppCurrency.Default,
|
val appCurrency: AppCurrency = AppCurrency.Default,
|
||||||
val scanCardFailsCounter: Int = 0,
|
val scanCardFailsCounter: Int = 0,
|
||||||
val dialog: StateDialog? = null,
|
val dialog: StateDialog? = null,
|
||||||
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
|
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
|
||||||
|
@Deprecated("Use GetUserCountryCodeUseCase instead")
|
||||||
val userCountryCode: String? = null,
|
val userCountryCode: String? = null,
|
||||||
val appThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
|
val appThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
|
||||||
) : StateType
|
) : StateType
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
package com.tangem.tap.common.redux.legacy
|
package com.tangem.tap.common.redux.legacy
|
||||||
|
|
||||||
import com.tangem.blockchain.common.AmountType
|
|
||||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
|
||||||
import com.tangem.domain.redux.LegacyAction
|
import com.tangem.domain.redux.LegacyAction
|
||||||
import com.tangem.domain.utils.convertToSdkAmount
|
|
||||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||||
import com.tangem.tap.common.extensions.inject
|
import com.tangem.tap.common.extensions.inject
|
||||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
|
||||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
|
||||||
import com.tangem.tap.common.feedback.RateCanBeBetterEmail
|
|
||||||
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
import com.tangem.tap.features.details.redux.DetailsAction
|
import com.tangem.tap.features.details.redux.DetailsAction
|
||||||
|
|
@ -23,7 +16,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.rekotlin.Middleware
|
import org.rekotlin.Middleware
|
||||||
|
|
||||||
internal object LegacyMiddleware {
|
internal object LegacyMiddleware {
|
||||||
|
|
@ -33,68 +25,13 @@ internal object LegacyMiddleware {
|
||||||
{ next ->
|
{ next ->
|
||||||
{ action ->
|
{ action ->
|
||||||
when (action) {
|
when (action) {
|
||||||
is LegacyAction.SendEmailRateCanBeBetter -> {
|
|
||||||
store.state.globalState.feedbackManager?.sendEmail(
|
|
||||||
feedbackData = RateCanBeBetterEmail(),
|
|
||||||
scanResponse = action.scanResponse,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is LegacyAction.SendEmailSupport -> {
|
|
||||||
store.state.globalState.feedbackManager?.sendEmail(
|
|
||||||
feedbackData = FeedbackEmail(),
|
|
||||||
scanResponse = action.scanResponse,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is LegacyAction.StartOnboardingProcess -> {
|
is LegacyAction.StartOnboardingProcess -> {
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
GlobalAction.Onboarding.Start(action.scanResponse, canSkipBackup = action.canSkipBackup),
|
GlobalAction.Onboarding.Start(
|
||||||
)
|
|
||||||
}
|
|
||||||
is LegacyAction.SendEmailTransactionFailed -> {
|
|
||||||
if (store.inject(DaggerGraphState::feedbackManagerFeatureToggles).isLocalLogsEnabled) {
|
|
||||||
|
|
||||||
val amount = action.amount?.convertToSdkAmount(action.cryptoCurrency)
|
|
||||||
store.inject(DaggerGraphState::saveBlockchainErrorUseCase).invoke(
|
|
||||||
error = BlockchainErrorInfo(
|
|
||||||
errorMessage = action.errorMessage,
|
|
||||||
blockchainId = action.cryptoCurrency.network.id.value,
|
|
||||||
derivationPath = action.cryptoCurrency.network.derivationPath.value,
|
|
||||||
destinationAddress = action.destinationAddress.orEmpty(),
|
|
||||||
tokenSymbol = if (amount?.type is AmountType.Token) {
|
|
||||||
amount.currencySymbol
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
},
|
|
||||||
amount = amount?.value?.stripZeroPlainString() ?: "unknown",
|
|
||||||
fee = action.fee?.convertToSdkAmount(action.cryptoCurrency)
|
|
||||||
?.value?.stripZeroPlainString() ?: "unknown",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
store.state.globalState.feedbackManager?.sendEmail(
|
|
||||||
feedbackData = SendTransactionFailedEmail(action.errorMessage),
|
|
||||||
scanResponse = action.scanResponse,
|
scanResponse = action.scanResponse,
|
||||||
)
|
canSkipBackup = action.canSkipBackup,
|
||||||
} else {
|
),
|
||||||
scope.launch {
|
)
|
||||||
store.inject(DaggerGraphState::walletManagersFacade)
|
|
||||||
.getOrCreateWalletManager(
|
|
||||||
userWalletId = action.userWalletId,
|
|
||||||
network = action.cryptoCurrency.network,
|
|
||||||
)?.let { walletManager ->
|
|
||||||
store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
|
|
||||||
walletManager = walletManager,
|
|
||||||
amountToSend = action.amount?.convertToSdkAmount(action.cryptoCurrency),
|
|
||||||
feeAmount = action.fee?.convertToSdkAmount(action.cryptoCurrency),
|
|
||||||
destinationAddress = action.destinationAddress,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
store.state.globalState.feedbackManager?.sendEmail(
|
|
||||||
feedbackData = SendTransactionFailedEmail(action.errorMessage),
|
|
||||||
scanResponse = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is LegacyAction.PrepareDetailsScreen -> {
|
is LegacyAction.PrepareDetailsScreen -> {
|
||||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,19 @@ import androidx.core.view.isVisible
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
import com.tangem.core.analytics.models.Basic
|
import com.tangem.core.analytics.models.Basic
|
||||||
|
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||||
import com.tangem.domain.redux.StateDialog
|
import com.tangem.domain.redux.StateDialog
|
||||||
import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics
|
import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics
|
||||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||||
import com.tangem.tap.common.feedback.ScanFailsEmail
|
import com.tangem.tap.common.extensions.inject
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
|
||||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
|
import com.tangem.tap.scope
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
|
|
@ -65,7 +68,11 @@ internal object ScanFailsDialog {
|
||||||
}
|
}
|
||||||
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
|
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
|
||||||
Analytics.send(Basic.ButtonSupport(sourceAnalytics))
|
Analytics.send(Basic.ButtonSupport(sourceAnalytics))
|
||||||
store.dispatch(GlobalAction.SendEmail(feedbackData = ScanFailsEmail(), scanResponse = null))
|
|
||||||
|
scope.launch {
|
||||||
|
store.inject(DaggerGraphState::sendFeedbackEmailUseCase)
|
||||||
|
.invoke(type = FeedbackEmailType.ScanningProblem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
customView.findViewById<TextView>(R.id.cancel_button)?.setOnClickListener {
|
customView.findViewById<TextView>(R.id.cancel_button)?.setOnClickListener {
|
||||||
store.dispatchDialogHide()
|
store.dispatchDialogHide()
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,34 @@
|
||||||
package com.tangem.core.navigation.email
|
package com.tangem.tap.core.navigation.email
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ShareCompat
|
import androidx.core.app.ShareCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import com.tangem.core.navigation.email.EmailSender
|
||||||
|
import com.tangem.tap.foregroundActivityObserver
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of email sender for Android
|
* Implementation of email sender for Android
|
||||||
*
|
*
|
||||||
* @property context context
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
internal class AndroidEmailSender(private val context: Context) : EmailSender {
|
internal class AndroidEmailSender : EmailSender {
|
||||||
|
|
||||||
override fun send(email: EmailSender.Email, onFail: ((Exception) -> Unit)?) {
|
override fun send(email: EmailSender.Email, onFail: ((Exception) -> Unit)?) {
|
||||||
val originalIntent = createEmailShareIntent(email)
|
val activity = foregroundActivityObserver.foregroundActivity
|
||||||
|
|
||||||
|
if (activity == null) {
|
||||||
|
Timber.e("Foreground activity not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val originalIntent = createEmailShareIntent(activity, email)
|
||||||
val emailFilterIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"))
|
val emailFilterIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"))
|
||||||
|
|
||||||
val packageManager = context.packageManager
|
val packageManager = activity.packageManager
|
||||||
val originalIntentResults = packageManager.queryIntentActivities(originalIntent, 0)
|
val originalIntentResults = packageManager.queryIntentActivities(originalIntent, 0)
|
||||||
val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0)
|
val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0)
|
||||||
|
|
||||||
|
|
@ -32,7 +39,7 @@ internal class AndroidEmailSender(private val context: Context) : EmailSender {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map {
|
.map {
|
||||||
createEmailShareIntent(email).apply { setPackage(it.activityInfo.packageName) }
|
createEmailShareIntent(activity, email).apply { setPackage(it.activityInfo.packageName) }
|
||||||
}
|
}
|
||||||
.toMutableList()
|
.toMutableList()
|
||||||
|
|
||||||
|
|
@ -41,14 +48,14 @@ internal class AndroidEmailSender(private val context: Context) : EmailSender {
|
||||||
putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
|
putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextCompat.startActivity(context, chooserIntent, null)
|
ContextCompat.startActivity(activity, chooserIntent, null)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Timber.e("Failed to send email: $ex")
|
Timber.e("Failed to send email: $ex")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEmailShareIntent(email: EmailSender.Email): Intent {
|
private fun createEmailShareIntent(activity: AppCompatActivity, email: EmailSender.Email): Intent {
|
||||||
val builder = ShareCompat.IntentBuilder(context)
|
val builder = ShareCompat.IntentBuilder(activity)
|
||||||
.setType("message/rfc822")
|
.setType("message/rfc822")
|
||||||
.setEmailTo(arrayOf(email.address))
|
.setEmailTo(arrayOf(email.address))
|
||||||
.setSubject(email.subject)
|
.setSubject(email.subject)
|
||||||
|
|
@ -56,7 +63,7 @@ internal class AndroidEmailSender(private val context: Context) : EmailSender {
|
||||||
|
|
||||||
email.attachment?.let {
|
email.attachment?.let {
|
||||||
builder.setStream(
|
builder.setStream(
|
||||||
FileProvider.getUriForFile(context, "${context.packageName}.provider", it),
|
FileProvider.getUriForFile(activity, "${activity.packageName}.provider", it),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package com.tangem.tap.di
|
|
||||||
|
|
||||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
|
||||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
|
||||||
import com.tangem.tap.common.feedback.DefaultFeedbackManagerFeatureToggles
|
|
||||||
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 FeedbackManagerFeatureTogglesModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideFeedbackManagerFeatureToggles(
|
|
||||||
featureTogglesManager: FeatureTogglesManager,
|
|
||||||
): FeedbackManagerFeatureToggles {
|
|
||||||
return DefaultFeedbackManagerFeatureToggles(featureTogglesManager)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,8 +5,6 @@ import com.tangem.core.navigation.finisher.AppFinisher
|
||||||
import com.tangem.core.navigation.settings.SettingsManager
|
import com.tangem.core.navigation.settings.SettingsManager
|
||||||
import com.tangem.core.navigation.share.ShareManager
|
import com.tangem.core.navigation.share.ShareManager
|
||||||
import com.tangem.core.navigation.url.UrlOpener
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
import com.tangem.domain.feedback.FeedbackManager
|
|
||||||
import com.tangem.tap.common.feedback.ProxyFeedbackManager
|
|
||||||
import com.tangem.tap.common.finisher.AndroidAppFinisher
|
import com.tangem.tap.common.finisher.AndroidAppFinisher
|
||||||
import com.tangem.tap.common.settings.IntentSettingsManager
|
import com.tangem.tap.common.settings.IntentSettingsManager
|
||||||
import com.tangem.tap.common.share.IntentShareManager
|
import com.tangem.tap.common.share.IntentShareManager
|
||||||
|
|
@ -30,10 +28,6 @@ internal object UtilsModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideUrlOpener(): UrlOpener = CustomTabsUrlOpener()
|
fun provideUrlOpener(): UrlOpener = CustomTabsUrlOpener()
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideFeedbackManager(): FeedbackManager = ProxyFeedbackManager()
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAppFinisher(@ApplicationContext context: Context): AppFinisher = AndroidAppFinisher(context)
|
fun provideAppFinisher(@ApplicationContext context: Context): AppFinisher = AndroidAppFinisher(context)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.tangem.tap.di.core.navigation.email
|
||||||
|
|
||||||
|
import com.tangem.core.navigation.email.EmailSender
|
||||||
|
import com.tangem.tap.core.navigation.email.AndroidEmailSender
|
||||||
|
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 EmailSenderModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideEmailSender(): EmailSender = AndroidEmailSender()
|
||||||
|
}
|
||||||
|
|
@ -22,14 +22,6 @@ import javax.inject.Singleton
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
internal object CardDomainModule {
|
internal object CardDomainModule {
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideGetBiometricsStatusUseCase(
|
|
||||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
|
||||||
): GetBiometricsStatusUseCase {
|
|
||||||
return GetBiometricsStatusUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSetAccessCodeRequestPolicyUseCase(
|
fun provideSetAccessCodeRequestPolicyUseCase(
|
||||||
|
|
@ -38,12 +30,6 @@ internal object CardDomainModule {
|
||||||
return SetAccessCodeRequestPolicyUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
|
return SetAccessCodeRequestPolicyUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideWasWalletAlreadySignedHashesConfirmedUseCase(cardRepository: CardRepository): WasCardScannedUseCase {
|
|
||||||
return WasCardScannedUseCase(cardRepository = cardRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSetCardWasScannedUseCase(cardRepository: CardRepository): SetCardWasScannedUseCase {
|
fun provideSetCardWasScannedUseCase(cardRepository: CardRepository): SetCardWasScannedUseCase {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import com.tangem.domain.card.*
|
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||||
|
import com.tangem.domain.card.ScanCardProcessor
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||||
|
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||||
import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor
|
import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -14,6 +16,12 @@ import javax.inject.Singleton
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
internal object CardLegacyDomainModule {
|
internal object CardLegacyDomainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideCardScanningFeatureToggles(featureTogglesManager: FeatureTogglesManager): CardScanningFeatureToggles {
|
||||||
|
return CardScanningFeatureToggles(featureTogglesManager)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideScanCardProcessor(): ScanCardProcessor = DefaultScanCardProcessor()
|
fun provideScanCardProcessor(): ScanCardProcessor = DefaultScanCardProcessor()
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
|
||||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||||
|
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -27,8 +27,8 @@ internal object FeedbackDomainModule {
|
||||||
fun provideGetFeedbackEmailUseCase(
|
fun provideGetFeedbackEmailUseCase(
|
||||||
feedbackRepository: FeedbackRepository,
|
feedbackRepository: FeedbackRepository,
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
): GetFeedbackEmailUseCase {
|
): SendFeedbackEmailUseCase {
|
||||||
return GetFeedbackEmailUseCase(feedbackRepository = feedbackRepository, resources = context.resources)
|
return SendFeedbackEmailUseCase(feedbackRepository = feedbackRepository, resources = context.resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import com.tangem.domain.settings.repositories.AppRatingRepository
|
||||||
import com.tangem.domain.settings.repositories.PermissionRepository
|
import com.tangem.domain.settings.repositories.PermissionRepository
|
||||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
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.sdk.TangemSdkManager
|
||||||
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
|
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -212,4 +214,16 @@ internal object SettingsDomainModule {
|
||||||
return ShouldSaveAccessCodesUseCase(settingsRepository = settingsRepository)
|
return ShouldSaveAccessCodesUseCase(settingsRepository = settingsRepository)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideFetchUserCountryCodeUseCase(settingsRepository: SettingsRepository): FetchUserCountryUseCase {
|
||||||
|
return FetchUserCountryUseCase(settingsRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetUserCountryCodeUseCase(settingsRepository: SettingsRepository): GetUserCountryUseCase {
|
||||||
|
return GetUserCountryUseCase(settingsRepository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -287,12 +287,6 @@ internal object TokensDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideGetCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
|
|
||||||
return GetCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideIsCryptoCurrencyCoinCouldHideUseCase(
|
fun provideIsCryptoCurrencyCoinCouldHideUseCase(
|
||||||
|
|
@ -313,68 +307,6 @@ internal object TokensDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideHasMissedAddressesCryptoCurrenciesUseCase(
|
|
||||||
currenciesRepository: CurrenciesRepository,
|
|
||||||
): GetMissedAddressesCryptoCurrenciesUseCase {
|
|
||||||
return GetMissedAddressesCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideGetGlobalTokenListUseCase(tokensListRepository: TokensListRepository): GetGlobalTokenListUseCase {
|
|
||||||
return GetGlobalTokenListUseCase(repository = tokensListRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideCheckTokenCompatibilityUseCase(
|
|
||||||
networksCompatibilityRepository: NetworksCompatibilityRepository,
|
|
||||||
): CheckCurrencyCompatibilityUseCase {
|
|
||||||
return CheckCurrencyCompatibilityUseCase(networksCompatibilityRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideNeedHardenedDerivationUseCase(
|
|
||||||
networksCompatibilityRepository: NetworksCompatibilityRepository,
|
|
||||||
): RequiresHardenedDerivationOnlyUseCase {
|
|
||||||
return RequiresHardenedDerivationOnlyUseCase(networksCompatibilityRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideFindTokenByContractAddressUseCase(
|
|
||||||
tokensListRepository: TokensListRepository,
|
|
||||||
): FindTokenByContractAddressUseCase {
|
|
||||||
return FindTokenByContractAddressUseCase(repository = tokensListRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideValidateContractAddressUseCase(
|
|
||||||
tokensListRepository: TokensListRepository,
|
|
||||||
): ValidateContractAddressUseCase {
|
|
||||||
return ValidateContractAddressUseCase(tokensListRepository = tokensListRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideAreTokensSupportedByNetworkUseCase(
|
|
||||||
repository: NetworksCompatibilityRepository,
|
|
||||||
): AreTokensSupportedByNetworkUseCase {
|
|
||||||
return AreTokensSupportedByNetworkUseCase(repository = repository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideGetNetworksSupportedByWallet(
|
|
||||||
repository: NetworksCompatibilityRepository,
|
|
||||||
): GetNetworksSupportedByWallet {
|
|
||||||
return GetNetworksSupportedByWallet(repository = repository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetBalanceNotEnoughForFeeWarningUseCase(
|
fun provideGetBalanceNotEnoughForFeeWarningUseCase(
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,12 @@ import com.tangem.blockchain.common.Token
|
||||||
import com.tangem.blockchain.common.Wallet
|
import com.tangem.blockchain.common.Wallet
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.analytics.models.Basic
|
import com.tangem.core.analytics.models.Basic
|
||||||
import com.tangem.datasource.config.ConfigManager
|
|
||||||
import com.tangem.domain.common.extensions.withMainContext
|
import com.tangem.domain.common.extensions.withMainContext
|
||||||
import com.tangem.domain.common.util.cardTypesResolver
|
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
|
||||||
import com.tangem.domain.wallets.models.UserWallet
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
import com.tangem.operations.attestation.Attestation
|
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.extensions.setContext
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
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.features.onboarding.products.twins.redux.TwinCardsAction
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.tap.tangemSdkManager
|
import com.tangem.tap.tangemSdkManager
|
||||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||||
|
|
@ -54,30 +47,13 @@ class TapWalletManager(
|
||||||
|
|
||||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
|
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
|
||||||
|
|
||||||
val featureToggles = store.inject(DaggerGraphState::feedbackManagerFeatureToggles)
|
|
||||||
if (!featureToggles.isLocalLogsEnabled) {
|
|
||||||
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateConfigManager(scanResponse)
|
|
||||||
withMainContext {
|
withMainContext {
|
||||||
// Order is important
|
// Order is important
|
||||||
store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer()))
|
|
||||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||||
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
|
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
|
||||||
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
|
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateConfigManager(data: ScanResponse) {
|
|
||||||
val configManager = store.state.globalState.configManager
|
|
||||||
|
|
||||||
if (data.cardTypesResolver.isStart2Coin()) {
|
|
||||||
configManager?.turnOff(ConfigManager.IS_TOP_UP_ENABLED)
|
|
||||||
} else {
|
|
||||||
configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Wallet.getFirstToken(): Token? = getTokens().toList().getOrNull(index = 0)
|
fun Wallet.getFirstToken(): Token? = getTokens().toList().getOrNull(index = 0)
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.tangem.tap.domain.scanCard
|
||||||
|
|
||||||
|
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom token feature toggles
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
class CardScanningFeatureToggles internal constructor(
|
||||||
|
private val featureTogglesManager: FeatureTogglesManager,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val isNewCardScanningEnabled: Boolean
|
||||||
|
get() = featureTogglesManager.isFeatureEnabled(name = "NEW_CARD_SCANNING_ENABLED")
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ import com.tangem.tap.store
|
||||||
// TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead
|
// TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead
|
||||||
internal class DefaultScanCardProcessor : ScanCardProcessor {
|
internal class DefaultScanCardProcessor : ScanCardProcessor {
|
||||||
private val isNewCardScanningEnabled: Boolean
|
private val isNewCardScanningEnabled: Boolean
|
||||||
get() = store.inject(DaggerGraphState::customTokenFeatureToggles).isNewCardScanningEnabled
|
get() = store.inject(DaggerGraphState::cardScanningFeatureToggles).isNewCardScanningEnabled
|
||||||
|
|
||||||
override suspend fun scan(
|
override suspend fun scan(
|
||||||
cardId: String?,
|
cardId: String?,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.domain.scanCard
|
||||||
|
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.core.TangemError
|
import com.tangem.common.core.TangemError
|
||||||
import com.tangem.common.core.TangemSdkError
|
|
||||||
import com.tangem.common.doOnFailure
|
import com.tangem.common.doOnFailure
|
||||||
import com.tangem.common.doOnSuccess
|
import com.tangem.common.doOnSuccess
|
||||||
import com.tangem.common.routing.AppRoute
|
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.extensions.*
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
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.OnboardingHelper
|
||||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
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(
|
private suspend inline fun showDisclaimerIfNeed(
|
||||||
scanResponse: ScanResponse,
|
scanResponse: ScanResponse,
|
||||||
crossinline disclaimerWillShow: () -> Unit = {},
|
crossinline disclaimerWillShow: () -> Unit = {},
|
||||||
|
|
@ -109,7 +107,6 @@ internal object LegacyScanProcessor {
|
||||||
crossinline onFailure: suspend (error: TangemError) -> Unit,
|
crossinline onFailure: suspend (error: TangemError) -> Unit,
|
||||||
) {
|
) {
|
||||||
val disclaimer = scanResponse.card.createDisclaimer()
|
val disclaimer = scanResponse.card.createDisclaimer()
|
||||||
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
|
|
||||||
|
|
||||||
if (disclaimer.isAccepted()) {
|
if (disclaimer.isAccepted()) {
|
||||||
nextHandler(scanResponse)
|
nextHandler(scanResponse)
|
||||||
|
|
@ -120,23 +117,9 @@ internal object LegacyScanProcessor {
|
||||||
withContext(Dispatchers.Main.immediate) {
|
withContext(Dispatchers.Main.immediate) {
|
||||||
disclaimerWillShow()
|
disclaimerWillShow()
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatchNavigationAction {
|
||||||
DisclaimerAction.Show(
|
push(AppRoute.Disclaimer(isTosAccepted = false))
|
||||||
from = DisclaimerSource.Home,
|
}
|
||||||
callback = DisclaimerCallback(
|
|
||||||
onAccept = {
|
|
||||||
scope.launch(Dispatchers.Main.immediate) {
|
|
||||||
nextHandler(scanResponse)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
scope.launch(Dispatchers.Main.immediate) {
|
|
||||||
onFailure(TangemSdkError.UserCancelled())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,10 +132,6 @@ internal object LegacyScanProcessor {
|
||||||
crossinline onWalletNotCreated: suspend () -> Unit,
|
crossinline onWalletNotCreated: suspend () -> Unit,
|
||||||
crossinline onSuccess: suspend (ScanResponse) -> Unit,
|
crossinline onSuccess: suspend (ScanResponse) -> Unit,
|
||||||
) {
|
) {
|
||||||
val globalState = store.state.globalState
|
|
||||||
val tapWalletManager = globalState.tapWalletManager
|
|
||||||
tapWalletManager.updateConfigManager(scanResponse)
|
|
||||||
|
|
||||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||||
|
|
||||||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,8 @@ internal object UseCaseScanProcessor {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
add(AnalyticsChain(Basic.CardWasScanned(analyticsSource)))
|
add(AnalyticsChain(Basic.CardWasScanned(analyticsSource)))
|
||||||
val pushNotificationsToggles =
|
add(DisclaimerChain(store, disclaimerWillShow))
|
||||||
store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles)
|
add(CheckForOnboardingChain(store))
|
||||||
if (pushNotificationsToggles.isPushNotificationsEnabled) {
|
|
||||||
add(DisclaimerChain(store, disclaimerWillShow))
|
|
||||||
}
|
|
||||||
add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scanCardUseCase(cardId, afterScanChains = chains).fold(
|
scanCardUseCase(cardId, afterScanChains = chains).fold(
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import com.tangem.tap.common.extensions.inject
|
||||||
import com.tangem.tap.common.extensions.setContext
|
import com.tangem.tap.common.extensions.setContext
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
import com.tangem.tap.domain.TapWalletManager
|
|
||||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
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.TwinCardsAction
|
||||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||||
|
|
@ -32,17 +31,14 @@ import org.rekotlin.Store
|
||||||
* - [ScanChainException.OnboardingNeeded] if onboarding required.
|
* - [ScanChainException.OnboardingNeeded] if onboarding required.
|
||||||
*
|
*
|
||||||
* @param store the [Store] that holds the state of the app.
|
* @param store the [Store] that holds the state of the app.
|
||||||
* @param tapWalletManager manager responsible for handling operations related to the Wallet.
|
|
||||||
*
|
*
|
||||||
* @see Chain for more information about the Chain interface.
|
* @see Chain for more information about the Chain interface.
|
||||||
*/
|
*/
|
||||||
class CheckForOnboardingChain(
|
class CheckForOnboardingChain(
|
||||||
private val store: Store<AppState>,
|
private val store: Store<AppState>,
|
||||||
private val tapWalletManager: TapWalletManager,
|
|
||||||
) : ResultChain<ScanCardException, ScanResponse>() {
|
) : ResultChain<ScanCardException, ScanResponse>() {
|
||||||
|
|
||||||
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
|
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
|
||||||
tapWalletManager.updateConfigManager(previousChainResult)
|
|
||||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult))
|
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult))
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,15 @@
|
||||||
package com.tangem.tap.domain.scanCard.chains
|
package com.tangem.tap.domain.scanCard.chains
|
||||||
|
|
||||||
import arrow.core.left
|
|
||||||
import arrow.core.right
|
import arrow.core.right
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.domain.card.ScanCardException
|
import com.tangem.domain.card.ScanCardException
|
||||||
import com.tangem.domain.core.chain.Chain
|
import com.tangem.domain.core.chain.Chain
|
||||||
import com.tangem.domain.core.chain.ResultChain
|
import com.tangem.domain.core.chain.ResultChain
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
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.common.redux.AppState
|
||||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
|
||||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
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 org.rekotlin.Store
|
||||||
import kotlin.coroutines.resume
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles disclaimer display after the card scanning operation.
|
* Handles disclaimer display after the card scanning operation.
|
||||||
|
|
@ -40,31 +34,13 @@ internal class DisclaimerChain(
|
||||||
previousChainResult.right()
|
previousChainResult.right()
|
||||||
} else {
|
} else {
|
||||||
disclaimerWillShow()
|
disclaimerWillShow()
|
||||||
showDisclaimer(disclaimer, previousChainResult)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun showDisclaimer(disclaimer: Disclaimer, response: ScanResponse): ScanChainResult {
|
// TODO: [REDACTED_JIRA]
|
||||||
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
|
store.dispatchNavigationAction {
|
||||||
|
push(route = AppRoute.Disclaimer(isTosAccepted = false))
|
||||||
|
}
|
||||||
|
|
||||||
return suspendCancellableCoroutine { continuation ->
|
previousChainResult.right()
|
||||||
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())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,12 +4,12 @@ import com.github.salomonbrys.kotson.registerTypeAdapter
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.common.extensions.calculateSha256
|
import com.tangem.common.extensions.calculateSha256
|
||||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
|
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
|
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.tradeOrderSerializer
|
import com.tangem.tap.domain.walletconnect2.domain.models.binance.tradeOrderSerializer
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.TradeData
|
import com.tangem.tap.features.details.redux.walletconnect.TradeData
|
||||||
|
import com.tangem.utils.extensions.stripZeroPlainString
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
internal object BnbHelper {
|
internal object BnbHelper {
|
||||||
|
|
|
||||||
|
|
@ -239,9 +239,8 @@ class WalletConnectSdkHelper {
|
||||||
private suspend fun signTransaction(data: WcTransactionData, cardId: String?): String? {
|
private suspend fun signTransaction(data: WcTransactionData, cardId: String?): String? {
|
||||||
val dataToSign = EthereumUtils.buildTransactionToSign(
|
val dataToSign = EthereumUtils.buildTransactionToSign(
|
||||||
transactionData = data.transaction,
|
transactionData = data.transaction,
|
||||||
nonce = null,
|
|
||||||
blockchain = data.walletManager.wallet.blockchain,
|
blockchain = data.walletManager.wallet.blockchain,
|
||||||
) ?: return null
|
)
|
||||||
|
|
||||||
val command = SignHashCommand(
|
val command = SignHashCommand(
|
||||||
hash = dataToSign.hash,
|
hash = dataToSign.hash,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.api.featuretoggles
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom token feature toggles
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
interface CustomTokenFeatureToggles {
|
|
||||||
|
|
||||||
val isNewCardScanningEnabled: Boolean
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.data
|
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
|
||||||
import com.tangem.datasource.api.common.response.getOrThrow
|
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|
||||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
|
||||||
import com.tangem.domain.common.util.cardTypesResolver
|
|
||||||
import com.tangem.tap.features.customtoken.impl.data.converters.FoundTokenConverter
|
|
||||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenRepository
|
|
||||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default implementation of custom token repository
|
|
||||||
*
|
|
||||||
* @property tangemTechApi TangemTech API
|
|
||||||
* @property dispatchers coroutine dispatchers provider
|
|
||||||
* @property reduxStateHolder redux state holder
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
class DefaultCustomTokenRepository(
|
|
||||||
private val tangemTechApi: TangemTechApi,
|
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
|
||||||
private val reduxStateHolder: AppStateHolder,
|
|
||||||
) : CustomTokenRepository {
|
|
||||||
|
|
||||||
override suspend fun findToken(address: String, networkId: String?): FoundToken {
|
|
||||||
val scanResponse = requireNotNull(reduxStateHolder.scanResponse)
|
|
||||||
val supportedTokenNetworkIds = requireNotNull(scanResponse.card)
|
|
||||||
.supportedBlockchains(scanResponse.cardTypesResolver)
|
|
||||||
.filter(Blockchain::canHandleTokens)
|
|
||||||
.map(Blockchain::toNetworkId)
|
|
||||||
|
|
||||||
return withContext(dispatchers.io) {
|
|
||||||
val foundCoin = tangemTechApi.getCoins(
|
|
||||||
contractAddress = address,
|
|
||||||
networkIds = networkId ?: supportedTokenNetworkIds.joinToString(separator = ","),
|
|
||||||
)
|
|
||||||
.getOrThrow()
|
|
||||||
.coins.firstNotNullOfOrNull { coin ->
|
|
||||||
val networksWithTheSameAddress = coin.networks.filter { network ->
|
|
||||||
(network.contractAddress != null || network.decimalCount != null) &&
|
|
||||||
network.contractAddress?.equals(address, ignoreCase = true) == true &&
|
|
||||||
supportedTokenNetworkIds.contains(network.networkId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networksWithTheSameAddress.isNotEmpty()) {
|
|
||||||
coin.copy(networks = networksWithTheSameAddress)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foundCoin?.let(FoundTokenConverter::convert) ?: error("Token not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.data.converters
|
|
||||||
|
|
||||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
|
||||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
|
||||||
import com.tangem.utils.converter.Converter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converter between data model [CoinsResponse.Coin] and domain model [FoundToken]
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
object FoundTokenConverter : Converter<CoinsResponse.Coin, FoundToken> {
|
|
||||||
|
|
||||||
override fun convert(value: CoinsResponse.Coin): FoundToken {
|
|
||||||
return FoundToken(
|
|
||||||
id = value.id,
|
|
||||||
name = value.name,
|
|
||||||
symbol = value.symbol,
|
|
||||||
isActive = value.active,
|
|
||||||
network = value.networks.firstOrNull()?.let { network ->
|
|
||||||
FoundToken.Network(
|
|
||||||
id = network.networkId,
|
|
||||||
contractAddress = requireNotNull(network.contractAddress),
|
|
||||||
decimalCount = requireNotNull(network.decimalCount).toString(),
|
|
||||||
)
|
|
||||||
} ?: error("Found token networks is empty"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.di
|
|
||||||
|
|
||||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
|
||||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
|
||||||
import com.tangem.tap.features.customtoken.impl.featuretoggles.DefaultCustomTokenFeatureToggles
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
internal object CustomTokenFeatureTogglesModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun providesCustomTokenFeatureToggles(featureTogglesManager: FeatureTogglesManager): CustomTokenFeatureToggles {
|
|
||||||
return DefaultCustomTokenFeatureToggles(featureTogglesManager)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.di
|
|
||||||
|
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|
||||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
|
||||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
|
||||||
import com.tangem.tap.features.customtoken.impl.data.DefaultCustomTokenRepository
|
|
||||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
|
|
||||||
import com.tangem.tap.features.customtoken.impl.domain.DefaultCustomTokenInteractor
|
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
|
||||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.components.ViewModelComponent
|
|
||||||
import dagger.hilt.android.scopes.ViewModelScoped
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Module
|
|
||||||
@InstallIn(ViewModelComponent::class)
|
|
||||||
internal object CustomTokenInteractorModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@ViewModelScoped
|
|
||||||
fun provideCustomTokenInteractor(
|
|
||||||
tangemTechApi: TangemTechApi,
|
|
||||||
appCoroutineDispatcherProvider: AppCoroutineDispatcherProvider,
|
|
||||||
reduxStateHolder: AppStateHolder,
|
|
||||||
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
|
||||||
derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
|
||||||
): CustomTokenInteractor {
|
|
||||||
return DefaultCustomTokenInteractor(
|
|
||||||
featureRepository = DefaultCustomTokenRepository(
|
|
||||||
tangemTechApi = tangemTechApi,
|
|
||||||
dispatchers = appCoroutineDispatcherProvider,
|
|
||||||
reduxStateHolder = reduxStateHolder,
|
|
||||||
),
|
|
||||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
|
||||||
derivePublicKeysUseCase = derivePublicKeysUseCase,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.di
|
|
||||||
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.routers.CustomTokenRouter
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.routers.DefaultCustomTokenRouter
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.components.ViewModelComponent
|
|
||||||
import dagger.hilt.android.scopes.ViewModelScoped
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Module
|
|
||||||
@InstallIn(ViewModelComponent::class)
|
|
||||||
internal object CustomTokenRouterModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@ViewModelScoped
|
|
||||||
fun provideAddCustomTokenRouter(): CustomTokenRouter = DefaultCustomTokenRouter()
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.domain
|
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.common.core.TangemError
|
|
||||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
|
||||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom token interactor
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
interface CustomTokenInteractor {
|
|
||||||
|
|
||||||
/** Find token by [address] and [blockchain] */
|
|
||||||
suspend fun findToken(address: String, blockchain: Blockchain): FoundToken
|
|
||||||
|
|
||||||
/** Save token [customCurrency] */
|
|
||||||
@Throws(TangemError::class)
|
|
||||||
suspend fun saveToken(customCurrency: CustomCurrency): Result<Unit>
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.domain
|
|
||||||
|
|
||||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom token repository
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
interface CustomTokenRepository {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find token by [address] and [networkId]
|
|
||||||
*
|
|
||||||
* @throws com.tangem.datasource.api.common.response.ApiResponseError
|
|
||||||
* */
|
|
||||||
suspend fun findToken(address: String, networkId: String?): FoundToken
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.domain
|
|
||||||
|
|
||||||
import arrow.core.getOrElse
|
|
||||||
import arrow.core.raise.result
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
|
||||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
|
||||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
|
||||||
import com.tangem.domain.common.util.derivationStyleProvider
|
|
||||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
|
||||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
|
||||||
import com.tangem.tap.common.extensions.inject
|
|
||||||
import com.tangem.tap.domain.model.Currency
|
|
||||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
|
||||||
import com.tangem.tap.store
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default implementation of custom token interactor
|
|
||||||
*
|
|
||||||
* @property featureRepository feature repository
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
class DefaultCustomTokenInteractor(
|
|
||||||
private val featureRepository: CustomTokenRepository,
|
|
||||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
|
||||||
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
|
||||||
) : CustomTokenInteractor {
|
|
||||||
|
|
||||||
// TODO: Move to DI
|
|
||||||
private val addCryptoCurrenciesUseCase by lazy(LazyThreadSafetyMode.NONE) {
|
|
||||||
val currenciesRepository = store.inject(DaggerGraphState::currenciesRepository)
|
|
||||||
val networksRepository = store.inject(DaggerGraphState::networksRepository)
|
|
||||||
|
|
||||||
AddCryptoCurrenciesUseCase(currenciesRepository, networksRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findToken(address: String, blockchain: Blockchain): FoundToken {
|
|
||||||
return featureRepository.findToken(
|
|
||||||
address = address,
|
|
||||||
networkId = if (blockchain != Blockchain.Unknown) blockchain.toNetworkId() else null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun saveToken(customCurrency: CustomCurrency): Result<Unit> {
|
|
||||||
return result {
|
|
||||||
val userWallet = getSelectedWalletSyncUseCase().getOrElse {
|
|
||||||
error("Failed to get selected wallet: $it")
|
|
||||||
}
|
|
||||||
|
|
||||||
val currency = Currency.fromCustomCurrency(customCurrency)
|
|
||||||
val currencies = listOfNotNull(element = currency.toCryptoCurrency(userWallet.scanResponse))
|
|
||||||
|
|
||||||
derivePublicKeysUseCase(userWalletId = userWallet.walletId, currencies = currencies).bind()
|
|
||||||
addCryptoCurrenciesUseCase(userWalletId = userWallet.walletId, currencies = currencies).bind()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Currency.toCryptoCurrency(scanResponse: ScanResponse): CryptoCurrency? {
|
|
||||||
val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
|
||||||
|
|
||||||
return when (this) {
|
|
||||||
is Currency.Blockchain -> {
|
|
||||||
cryptoCurrencyFactory.createCoin(
|
|
||||||
blockchain = blockchain,
|
|
||||||
extraDerivationPath = derivationPath,
|
|
||||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is Currency.Token -> {
|
|
||||||
cryptoCurrencyFactory.createToken(
|
|
||||||
sdkToken = token,
|
|
||||||
blockchain = blockchain,
|
|
||||||
extraDerivationPath = derivationPath,
|
|
||||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.domain.models
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Found token model
|
|
||||||
*
|
|
||||||
* @property id id
|
|
||||||
* @property name name
|
|
||||||
* @property symbol symbol
|
|
||||||
* @property isActive flag that determines status of token
|
|
||||||
* @property network network
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
data class FoundToken(
|
|
||||||
val id: String,
|
|
||||||
val name: String,
|
|
||||||
val symbol: String,
|
|
||||||
val isActive: Boolean,
|
|
||||||
val network: Network,
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Found token network
|
|
||||||
*
|
|
||||||
* @property id id
|
|
||||||
* @property contractAddress address
|
|
||||||
* @property decimalCount decimal count
|
|
||||||
*/
|
|
||||||
data class Network(val id: String, val contractAddress: String, val decimalCount: String)
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.featuretoggles
|
|
||||||
|
|
||||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
|
||||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default implementation of CustomToken feature toggles
|
|
||||||
*
|
|
||||||
* @property featureTogglesManager manager for getting information about the availability of feature toggles
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
internal class DefaultCustomTokenFeatureToggles(
|
|
||||||
private val featureTogglesManager: FeatureTogglesManager,
|
|
||||||
) : CustomTokenFeatureToggles {
|
|
||||||
|
|
||||||
override val isNewCardScanningEnabled: Boolean
|
|
||||||
get() = featureTogglesManager.isFeatureEnabled(name = "NEW_CARD_SCANNING_ENABLED")
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import com.tangem.core.ui.UiDependencies
|
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
|
||||||
import com.tangem.core.ui.screen.ComposeFragment
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenScreen
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.viewmodels.AddCustomTokenViewModel
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom token screen
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@AndroidEntryPoint
|
|
||||||
internal class AddCustomTokenFragment : ComposeFragment() {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
override lateinit var uiDependencies: UiDependencies
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ScreenContent(modifier: Modifier) {
|
|
||||||
val viewModel = hiltViewModel<AddCustomTokenViewModel>().apply {
|
|
||||||
LocalLifecycleOwner.current.lifecycle.addObserver(this)
|
|
||||||
}
|
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
AddCustomTokenScreen(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(TangemTheme.colors.background.primary)
|
|
||||||
.systemBarsPadding(),
|
|
||||||
stateHolder = state,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,324 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.models
|
|
||||||
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toolbar model of add custom token screen
|
|
||||||
*
|
|
||||||
* @property title title
|
|
||||||
* @property onBackButtonClick lambda be invoked when back button is been pressed
|
|
||||||
*/
|
|
||||||
internal data class AddCustomTokensToolbar(val title: TextReference, val onBackButtonClick: () -> Unit)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model of block with fields for testing
|
|
||||||
*
|
|
||||||
* @property chooseTokenButtonText choose token button text
|
|
||||||
* @property clearButtonText clear button text
|
|
||||||
* @property resetButtonText reset button text
|
|
||||||
* @property onClearAddressButtonClick lambda be invoked when clear address button is been pressed
|
|
||||||
* @property onResetButtonClick lambda be invoked when reset form fields button is been pressed
|
|
||||||
*/
|
|
||||||
internal data class AddCustomTokenTestBlock(
|
|
||||||
val chooseTokenButtonText: String,
|
|
||||||
val clearButtonText: String,
|
|
||||||
val resetButtonText: String,
|
|
||||||
val onClearAddressButtonClick: () -> Unit,
|
|
||||||
val onResetButtonClick: () -> Unit,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bottom sheet model for choose custom token
|
|
||||||
*
|
|
||||||
* @property categoriesBlocks tokens categories
|
|
||||||
* @property onTestTokenClick lambda be invoked when token is been pressed
|
|
||||||
*/
|
|
||||||
internal data class AddCustomTokenChooseTokenBottomSheet(
|
|
||||||
val categoriesBlocks: List<TokensCategoryBlock>,
|
|
||||||
val onTestTokenClick: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tokens category model
|
|
||||||
*
|
|
||||||
* @property name category name
|
|
||||||
* @property items category items
|
|
||||||
*/
|
|
||||||
data class TokensCategoryBlock(val name: String, val items: List<TestTokenItem>)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test token model
|
|
||||||
*
|
|
||||||
* @property name token name
|
|
||||||
* @property address token address
|
|
||||||
*/
|
|
||||||
data class TestTokenItem(val name: String, val address: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Form with fields model of add custom token screen
|
|
||||||
*
|
|
||||||
* @property contractAddressInputField input field to enter the contract address
|
|
||||||
* @property networkSelectorField selector field to select the token network
|
|
||||||
* @property tokenNameInputField input field to enter the token name
|
|
||||||
* @property tokenSymbolInputField input field to enter the token symbol
|
|
||||||
* @property decimalsInputField input field to enter the token decimals
|
|
||||||
* @property derivationPathSelectorField selector field to select the derivation path
|
|
||||||
* @property derivationPathInputField input field for a custom derivation path
|
|
||||||
* @property showTokenFields if token fields should be shown
|
|
||||||
*/
|
|
||||||
internal data class AddCustomTokenForm(
|
|
||||||
val contractAddressInputField: AddCustomTokenInputField.ContactAddress,
|
|
||||||
val networkSelectorField: AddCustomTokenSelectorField.Network,
|
|
||||||
val tokenNameInputField: AddCustomTokenInputField.TokenName,
|
|
||||||
val tokenSymbolInputField: AddCustomTokenInputField.TokenSymbol,
|
|
||||||
val decimalsInputField: AddCustomTokenInputField.Decimals,
|
|
||||||
val derivationPathSelectorField: AddCustomTokenSelectorField.DerivationPath?,
|
|
||||||
val derivationPathInputField: AddCustomTokenInputField.DerivationPath?,
|
|
||||||
val showTokenFields: Boolean = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Base input field model of add custom token screen */
|
|
||||||
internal sealed interface AddCustomTokenInputField {
|
|
||||||
|
|
||||||
/** Current value */
|
|
||||||
val value: String
|
|
||||||
|
|
||||||
/** Lambda be invoked when value is been changed */
|
|
||||||
val onValueChange: (String) -> Unit
|
|
||||||
|
|
||||||
/** Keyboard options */
|
|
||||||
val keyboardOptions: KeyboardOptions
|
|
||||||
|
|
||||||
/** Label */
|
|
||||||
val label: TextReference
|
|
||||||
|
|
||||||
/** Placeholder (hint) */
|
|
||||||
val placeholder: TextReference
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input field model to enter the contract address
|
|
||||||
*
|
|
||||||
* @property value current value
|
|
||||||
* @property onValueChange lambda be invoked when value is been changed
|
|
||||||
* @property keyboardOptions keyboard options
|
|
||||||
* @property label label
|
|
||||||
* @property placeholder placeholder (hint)
|
|
||||||
* @property isLoading flag that determine the processing of current value
|
|
||||||
* @property isError flag that determine if current value has error
|
|
||||||
* @property error error description
|
|
||||||
*/
|
|
||||||
data class ContactAddress(
|
|
||||||
override val value: String,
|
|
||||||
override val onValueChange: (String) -> Unit,
|
|
||||||
override val keyboardOptions: KeyboardOptions,
|
|
||||||
override val label: TextReference,
|
|
||||||
override val placeholder: TextReference,
|
|
||||||
val isLoading: Boolean,
|
|
||||||
val isError: Boolean,
|
|
||||||
val error: TextReference? = null,
|
|
||||||
) : AddCustomTokenInputField
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input field model to enter the token name
|
|
||||||
*
|
|
||||||
* @property value current value
|
|
||||||
* @property onValueChange lambda be invoked when value is been changed
|
|
||||||
* @property keyboardOptions keyboard options
|
|
||||||
* @property label label
|
|
||||||
* @property placeholder placeholder (hint)
|
|
||||||
* @property isEnabled input availability
|
|
||||||
*/
|
|
||||||
data class TokenName(
|
|
||||||
override val value: String,
|
|
||||||
override val onValueChange: (String) -> Unit,
|
|
||||||
override val keyboardOptions: KeyboardOptions,
|
|
||||||
override val label: TextReference,
|
|
||||||
override val placeholder: TextReference,
|
|
||||||
val isEnabled: Boolean,
|
|
||||||
) : AddCustomTokenInputField
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input field model to enter the token symbol
|
|
||||||
*
|
|
||||||
* @property value current value
|
|
||||||
* @property onValueChange lambda be invoked when value is been changed
|
|
||||||
* @property keyboardOptions keyboard options
|
|
||||||
* @property label label
|
|
||||||
* @property placeholder placeholder (hint)
|
|
||||||
* @property isEnabled input availability
|
|
||||||
*/
|
|
||||||
data class TokenSymbol(
|
|
||||||
override val value: String,
|
|
||||||
override val onValueChange: (String) -> Unit,
|
|
||||||
override val keyboardOptions: KeyboardOptions,
|
|
||||||
override val label: TextReference,
|
|
||||||
override val placeholder: TextReference,
|
|
||||||
val isEnabled: Boolean,
|
|
||||||
) : AddCustomTokenInputField
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input field model to enter the token decimals
|
|
||||||
*
|
|
||||||
* @property value current value
|
|
||||||
* @property onValueChange lambda be invoked when value is been changed
|
|
||||||
* @property keyboardOptions keyboard options
|
|
||||||
* @property label label
|
|
||||||
* @property placeholder placeholder (hint)
|
|
||||||
* @property isEnabled input availability
|
|
||||||
*/
|
|
||||||
data class Decimals(
|
|
||||||
override val value: String,
|
|
||||||
override val onValueChange: (String) -> Unit,
|
|
||||||
override val keyboardOptions: KeyboardOptions,
|
|
||||||
override val label: TextReference,
|
|
||||||
override val placeholder: TextReference,
|
|
||||||
val isEnabled: Boolean,
|
|
||||||
) : AddCustomTokenInputField
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input field model to enter a custom derivation path
|
|
||||||
*
|
|
||||||
* @property value current value
|
|
||||||
* @property onValueChange lambda be invoked when value is been changed
|
|
||||||
* @property keyboardOptions keyboard options
|
|
||||||
* @property label label
|
|
||||||
* @property placeholder placeholder (hint)
|
|
||||||
* @property showField whether the field should be shown
|
|
||||||
*/
|
|
||||||
data class DerivationPath(
|
|
||||||
override val value: String,
|
|
||||||
override val onValueChange: (String) -> Unit,
|
|
||||||
override val keyboardOptions: KeyboardOptions,
|
|
||||||
override val label: TextReference,
|
|
||||||
override val placeholder: TextReference,
|
|
||||||
val showField: Boolean = false,
|
|
||||||
) : AddCustomTokenInputField
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Base selector field model of add custom token screen */
|
|
||||||
internal sealed interface AddCustomTokenSelectorField {
|
|
||||||
|
|
||||||
/** Label */
|
|
||||||
val label: TextReference
|
|
||||||
|
|
||||||
/** Selected menu item */
|
|
||||||
val selectedItem: SelectorItem
|
|
||||||
|
|
||||||
/** Menu items */
|
|
||||||
val items: List<SelectorItem>
|
|
||||||
|
|
||||||
/** Lambda be invoked when menu item is been selected */
|
|
||||||
val onMenuItemClick: (Int) -> Unit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Network selector model
|
|
||||||
*
|
|
||||||
* @property label label
|
|
||||||
* @property selectedItem selected menu item
|
|
||||||
* @property items menu items
|
|
||||||
* @property onMenuItemClick lambda be invoked when menu item is been selected
|
|
||||||
*/
|
|
||||||
data class Network(
|
|
||||||
override val label: TextReference,
|
|
||||||
override val selectedItem: SelectorItem.Title,
|
|
||||||
override val items: List<SelectorItem.Title>,
|
|
||||||
override val onMenuItemClick: (Int) -> Unit,
|
|
||||||
) : AddCustomTokenSelectorField
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derivation path selector model
|
|
||||||
*
|
|
||||||
* @property label label
|
|
||||||
* @property selectedItem selected menu item
|
|
||||||
* @property items menu items
|
|
||||||
* @property onMenuItemClick lambda be invoked when menu item is been selected
|
|
||||||
* @property isEnabled selection availability
|
|
||||||
*/
|
|
||||||
data class DerivationPath(
|
|
||||||
override val label: TextReference,
|
|
||||||
override val selectedItem: SelectorItem.TitleWithSubtitle,
|
|
||||||
override val items: List<SelectorItem.TitleWithSubtitle>,
|
|
||||||
override val onMenuItemClick: (Int) -> Unit,
|
|
||||||
val isEnabled: Boolean,
|
|
||||||
) : AddCustomTokenSelectorField
|
|
||||||
|
|
||||||
/** Base menu item model */
|
|
||||||
sealed interface SelectorItem {
|
|
||||||
|
|
||||||
/** Title */
|
|
||||||
val title: TextReference
|
|
||||||
|
|
||||||
/** Blockchain */
|
|
||||||
val blockchain: Blockchain
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Menu item with title
|
|
||||||
*
|
|
||||||
* @property title title text
|
|
||||||
* @property blockchain blockchain
|
|
||||||
*/
|
|
||||||
data class Title(override val title: TextReference, override val blockchain: Blockchain) : SelectorItem
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Menu item with title ans subtitle
|
|
||||||
*
|
|
||||||
* @property title title text
|
|
||||||
* @property blockchain blockchain
|
|
||||||
* @property subtitle subtitle text
|
|
||||||
*/
|
|
||||||
data class TitleWithSubtitle(
|
|
||||||
override val title: TextReference,
|
|
||||||
override val blockchain: Blockchain,
|
|
||||||
val subtitle: TextReference,
|
|
||||||
val type: DerivationPathSelectorType = DerivationPathSelectorType.BLOCKCHAIN,
|
|
||||||
) : SelectorItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class DerivationPathSelectorType {
|
|
||||||
DEFAULT, CUSTOM, BLOCKCHAIN
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Warning model of add custom token screen
|
|
||||||
*
|
|
||||||
* @property description warning description
|
|
||||||
*/
|
|
||||||
internal sealed class AddCustomTokenWarning(val description: TextReference) {
|
|
||||||
|
|
||||||
/** Potential scam warning */
|
|
||||||
data object PotentialScamToken : AddCustomTokenWarning(
|
|
||||||
description = TextReference.Res(R.string.custom_token_validation_error_not_found),
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Token already added warning */
|
|
||||||
data object TokenAlreadyAdded : AddCustomTokenWarning(
|
|
||||||
description = TextReference.Res(R.string.custom_token_validation_error_already_added),
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Unsupported token warning */
|
|
||||||
data class UnsupportedToken(val networkName: String) : AddCustomTokenWarning(
|
|
||||||
description = TextReference.Res(R.string.alert_manage_tokens_unsupported_message, networkName),
|
|
||||||
)
|
|
||||||
|
|
||||||
data object WrongDerivationPath : AddCustomTokenWarning(
|
|
||||||
description = TextReference.Res(R.string.custom_token_invalid_derivation_path),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Floating button of add custom token screen
|
|
||||||
*
|
|
||||||
* @property isEnabled button availability
|
|
||||||
* @property showProgress whether circle progress indication is enabled
|
|
||||||
* @property onClick lambda be invoked when button is been pressed
|
|
||||||
*/
|
|
||||||
internal data class AddCustomTokenFloatingButton(
|
|
||||||
val isEnabled: Boolean,
|
|
||||||
val showProgress: Boolean,
|
|
||||||
val onClick: () -> Unit,
|
|
||||||
)
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.models
|
|
||||||
|
|
||||||
/** Custom token type */
|
|
||||||
enum class CustomTokenType { TOKEN, BLOCKCHAIN }
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.models
|
|
||||||
|
|
||||||
internal enum class SupportBlockchainType {
|
|
||||||
|
|
||||||
SUPPORTED, UNSUPPORTED, UNABLE_TO_DETERMINE
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.routers
|
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom token feature router
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
internal interface CustomTokenRouter {
|
|
||||||
|
|
||||||
/** Return to last screen */
|
|
||||||
fun popBackStack()
|
|
||||||
|
|
||||||
/** Open wallet (main) screen */
|
|
||||||
fun openWalletScreen()
|
|
||||||
|
|
||||||
/** Open alert if solana network is unsupported
|
|
||||||
*
|
|
||||||
* @param blockchain blockchain to show alert
|
|
||||||
*/
|
|
||||||
fun openUnsupportedNetworkAlert(blockchain: Blockchain)
|
|
||||||
|
|
||||||
fun showGenericErrorAlertAndPopBack()
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.routers
|
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.common.routing.AppRoute
|
|
||||||
import com.tangem.common.routing.AppRouter
|
|
||||||
import com.tangem.common.routing.utils.popTo
|
|
||||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
|
||||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
|
||||||
import com.tangem.tap.common.redux.AppDialog
|
|
||||||
import com.tangem.tap.store
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
|
|
||||||
/** Default implementation of custom token feature router */
|
|
||||||
internal class DefaultCustomTokenRouter : CustomTokenRouter {
|
|
||||||
|
|
||||||
override fun popBackStack() {
|
|
||||||
store.dispatchNavigationAction(AppRouter::pop)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openWalletScreen() {
|
|
||||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openUnsupportedNetworkAlert(blockchain: Blockchain) {
|
|
||||||
val alert = AppDialog.SimpleOkDialogRes(
|
|
||||||
headerId = R.string.common_warning,
|
|
||||||
messageId = R.string.alert_manage_tokens_unsupported_curve_message,
|
|
||||||
args = listOf(blockchain.getNetworkName()),
|
|
||||||
)
|
|
||||||
store.dispatchDialogShow(alert)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showGenericErrorAlertAndPopBack() {
|
|
||||||
val alert = AppDialog.SimpleOkDialogRes(
|
|
||||||
headerId = R.string.common_error,
|
|
||||||
messageId = R.string.common_unknown_error,
|
|
||||||
onOk = { store.dispatchNavigationAction(AppRouter::pop) },
|
|
||||||
)
|
|
||||||
store.dispatchDialogShow(alert)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.states
|
|
||||||
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
|
|
||||||
|
|
||||||
/**
|
|
||||||
* State holder of add custom token screen
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
internal sealed interface AddCustomTokenStateHolder {
|
|
||||||
|
|
||||||
/** Lambda be invoked when system back action is been called */
|
|
||||||
val onBackButtonClick: () -> Unit
|
|
||||||
|
|
||||||
/** Toolbar model */
|
|
||||||
val toolbar: AddCustomTokensToolbar
|
|
||||||
|
|
||||||
/** Form model */
|
|
||||||
val form: AddCustomTokenForm
|
|
||||||
|
|
||||||
/** Warnings */
|
|
||||||
val warnings: Set<AddCustomTokenWarning>
|
|
||||||
|
|
||||||
/** Floating button model */
|
|
||||||
val floatingButton: AddCustomTokenFloatingButton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Util function that allow to make a copy
|
|
||||||
*
|
|
||||||
* @param onBackButtonClick lambda be invoked when system back action is been called
|
|
||||||
* @param toolbar toolbar model
|
|
||||||
* @param form form model
|
|
||||||
* @param warnings warnings
|
|
||||||
* @param floatingButton floating button model
|
|
||||||
*/
|
|
||||||
fun copySealed(
|
|
||||||
onBackButtonClick: () -> Unit = this.onBackButtonClick,
|
|
||||||
toolbar: AddCustomTokensToolbar = this.toolbar,
|
|
||||||
form: AddCustomTokenForm = this.form,
|
|
||||||
warnings: Set<AddCustomTokenWarning> = this.warnings,
|
|
||||||
floatingButton: AddCustomTokenFloatingButton = this.floatingButton,
|
|
||||||
): AddCustomTokenStateHolder {
|
|
||||||
return when (this) {
|
|
||||||
is Content -> copy(onBackButtonClick, toolbar, form, warnings, floatingButton)
|
|
||||||
is TestContent -> copy(onBackButtonClick, toolbar, form, warnings, floatingButton)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Content state
|
|
||||||
*
|
|
||||||
* @property onBackButtonClick lambda be invoked when system back action is been called
|
|
||||||
* @property toolbar toolbar model
|
|
||||||
* @property form form model
|
|
||||||
* @property warnings warnings
|
|
||||||
* @property floatingButton floating button model
|
|
||||||
*/
|
|
||||||
data class Content(
|
|
||||||
override val onBackButtonClick: () -> Unit,
|
|
||||||
override val toolbar: AddCustomTokensToolbar,
|
|
||||||
override val form: AddCustomTokenForm,
|
|
||||||
override val warnings: Set<AddCustomTokenWarning>,
|
|
||||||
override val floatingButton: AddCustomTokenFloatingButton,
|
|
||||||
) : AddCustomTokenStateHolder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Content state with fields for testing
|
|
||||||
*
|
|
||||||
* @property onBackButtonClick lambda be invoked when system back action is been called
|
|
||||||
* @property toolbar toolbar model
|
|
||||||
* @property form form model
|
|
||||||
* @property warnings warnings
|
|
||||||
* @property floatingButton floating button model
|
|
||||||
* @property testBlock test block model
|
|
||||||
* @property bottomSheet bottom sheet model
|
|
||||||
*/
|
|
||||||
data class TestContent(
|
|
||||||
override val onBackButtonClick: () -> Unit,
|
|
||||||
override val toolbar: AddCustomTokensToolbar,
|
|
||||||
override val form: AddCustomTokenForm,
|
|
||||||
override val warnings: Set<AddCustomTokenWarning>,
|
|
||||||
override val floatingButton: AddCustomTokenFloatingButton,
|
|
||||||
val testBlock: AddCustomTokenTestBlock,
|
|
||||||
val bottomSheet: AddCustomTokenChooseTokenBottomSheet,
|
|
||||||
) : AddCustomTokenStateHolder
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.FabPosition
|
|
||||||
import androidx.compose.material.Scaffold
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom token content
|
|
||||||
*
|
|
||||||
* @param state screen state
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content, modifier: Modifier = Modifier) {
|
|
||||||
BackHandler(onBack = state.onBackButtonClick)
|
|
||||||
|
|
||||||
var floatingButtonHeight by remember { mutableStateOf(0.dp) }
|
|
||||||
Scaffold(
|
|
||||||
modifier = modifier,
|
|
||||||
topBar = {
|
|
||||||
AddCustomTokenToolbar(
|
|
||||||
title = state.toolbar.title,
|
|
||||||
onBackButtonClick = state.toolbar.onBackButtonClick,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
val verticalPadding = TangemTheme.dimens.spacing32
|
|
||||||
AddCustomTokenFloatingButton(
|
|
||||||
model = state.floatingButton,
|
|
||||||
modifier = Modifier.onSizeChanged {
|
|
||||||
floatingButtonHeight = with(density) { it.height.toDp() + verticalPadding }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
floatingActionButtonPosition = FabPosition.Center,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(paddingValues = it)
|
|
||||||
.padding(bottom = floatingButtonHeight)
|
|
||||||
.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
AddCustomTokenForm(model = state.form)
|
|
||||||
|
|
||||||
AddCustomTokenWarnings(warnings = state.warnings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun Preview_AddCustomTokenContent() {
|
|
||||||
TangemThemePreview {
|
|
||||||
AddCustomTokenContent(state = AddCustomTokenPreviewData.createContent())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
|
||||||
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.*
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
internal object AddCustomTokenPreviewData {
|
|
||||||
|
|
||||||
fun createWarnings(): Set<AddCustomTokenWarning> {
|
|
||||||
return setOf(
|
|
||||||
AddCustomTokenWarning.PotentialScamToken,
|
|
||||||
AddCustomTokenWarning.TokenAlreadyAdded,
|
|
||||||
AddCustomTokenWarning.UnsupportedToken(networkName = "Solana"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createDefaultForm(): AddCustomTokenForm {
|
|
||||||
return AddCustomTokenForm(
|
|
||||||
contractAddressInputField = AddCustomTokenInputField.ContactAddress(
|
|
||||||
value = "",
|
|
||||||
onValueChange = {},
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
|
||||||
label = TextReference.Res(R.string.custom_token_contract_address_input_title),
|
|
||||||
placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000"),
|
|
||||||
isLoading = false,
|
|
||||||
isError = false,
|
|
||||||
error = null,
|
|
||||||
),
|
|
||||||
networkSelectorField = AddCustomTokenSelectorField.Network(
|
|
||||||
label = TextReference.Res(R.string.custom_token_network_input_title),
|
|
||||||
selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
|
|
||||||
title = TextReference.Res(R.string.custom_token_network_input_not_selected),
|
|
||||||
blockchain = Blockchain.Unknown,
|
|
||||||
),
|
|
||||||
items = emptyList(),
|
|
||||||
onMenuItemClick = {},
|
|
||||||
),
|
|
||||||
tokenNameInputField = AddCustomTokenInputField.TokenName(
|
|
||||||
value = "",
|
|
||||||
onValueChange = {},
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
|
||||||
label = TextReference.Res(R.string.custom_token_name_input_title),
|
|
||||||
placeholder = TextReference.Res(id = R.string.custom_token_name_input_placeholder),
|
|
||||||
isEnabled = false,
|
|
||||||
),
|
|
||||||
tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
|
|
||||||
value = "",
|
|
||||||
onValueChange = {},
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
|
||||||
label = TextReference.Res(R.string.custom_token_token_symbol_input_title_old),
|
|
||||||
placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder),
|
|
||||||
isEnabled = false,
|
|
||||||
),
|
|
||||||
decimalsInputField = AddCustomTokenInputField.Decimals(
|
|
||||||
value = "",
|
|
||||||
onValueChange = {},
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next),
|
|
||||||
label = TextReference.Res(R.string.custom_token_decimals_input_title),
|
|
||||||
placeholder = TextReference.Str(value = "8"),
|
|
||||||
isEnabled = false,
|
|
||||||
),
|
|
||||||
derivationPathSelectorField = AddCustomTokenSelectorField.DerivationPath(
|
|
||||||
label = TextReference.Res(R.string.custom_token_derivation_path_input_title),
|
|
||||||
selectedItem = AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle(
|
|
||||||
title = TextReference.Res(R.string.custom_token_derivation_path_default),
|
|
||||||
subtitle = TextReference.Res(R.string.custom_token_derivation_path_default),
|
|
||||||
blockchain = Blockchain.Unknown,
|
|
||||||
),
|
|
||||||
items = emptyList(),
|
|
||||||
onMenuItemClick = {},
|
|
||||||
isEnabled = true,
|
|
||||||
),
|
|
||||||
derivationPathInputField = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createTestContent(): AddCustomTokenStateHolder.TestContent {
|
|
||||||
return AddCustomTokenStateHolder.TestContent(
|
|
||||||
onBackButtonClick = {},
|
|
||||||
toolbar = AddCustomTokensToolbar(
|
|
||||||
title = TextReference.Res(R.string.add_custom_token_title),
|
|
||||||
onBackButtonClick = {},
|
|
||||||
),
|
|
||||||
form = createDefaultForm(),
|
|
||||||
warnings = createWarnings(),
|
|
||||||
floatingButton = AddCustomTokenFloatingButton(
|
|
||||||
isEnabled = false,
|
|
||||||
showProgress = false,
|
|
||||||
onClick = {},
|
|
||||||
),
|
|
||||||
testBlock = AddCustomTokenTestBlock(
|
|
||||||
chooseTokenButtonText = "Choose token",
|
|
||||||
clearButtonText = "Clear address",
|
|
||||||
resetButtonText = "Reset",
|
|
||||||
onClearAddressButtonClick = {},
|
|
||||||
onResetButtonClick = {},
|
|
||||||
),
|
|
||||||
bottomSheet = AddCustomTokenChooseTokenBottomSheet(categoriesBlocks = emptyList(), onTestTokenClick = {}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createContent(): AddCustomTokenStateHolder.Content {
|
|
||||||
return AddCustomTokenStateHolder.Content(
|
|
||||||
onBackButtonClick = {},
|
|
||||||
toolbar = AddCustomTokensToolbar(
|
|
||||||
title = TextReference.Res(R.string.add_custom_token_title),
|
|
||||||
onBackButtonClick = {},
|
|
||||||
),
|
|
||||||
form = createDefaultForm(),
|
|
||||||
warnings = createWarnings(),
|
|
||||||
floatingButton = AddCustomTokenFloatingButton(
|
|
||||||
isEnabled = false,
|
|
||||||
showProgress = false,
|
|
||||||
onClick = {},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
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.res.TangemThemePreview
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom token screen
|
|
||||||
*
|
|
||||||
* @param stateHolder state holder
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder, modifier: Modifier = Modifier) {
|
|
||||||
when (stateHolder) {
|
|
||||||
is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(stateHolder, modifier)
|
|
||||||
is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(stateHolder, modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showSystemUi = true)
|
|
||||||
@Preview(showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Composable
|
|
||||||
private fun Preview_AddCustomTokenScreen(
|
|
||||||
@PreviewParameter(AddCustomTokenScreenProvider::class) stateHolder: AddCustomTokenStateHolder,
|
|
||||||
) {
|
|
||||||
TangemThemePreview {
|
|
||||||
AddCustomTokenScreen(stateHolder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AddCustomTokenScreenProvider : CollectionPreviewParameterProvider<AddCustomTokenStateHolder>(
|
|
||||||
collection = listOf(
|
|
||||||
AddCustomTokenPreviewData.createContent(),
|
|
||||||
AddCustomTokenPreviewData.createTestContent(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
@ -1,249 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
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.ExperimentalComposeUiApi
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
||||||
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.SpacerH8
|
|
||||||
import com.tangem.core.ui.components.atoms.Hand
|
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom token content for testing
|
|
||||||
*
|
|
||||||
* @param state screen state
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
|
||||||
@Composable
|
|
||||||
internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestContent, modifier: Modifier = Modifier) {
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
|
|
||||||
bottomSheetState = BottomSheetState(initialValue = BottomSheetValue.Collapsed, LocalDensity.current),
|
|
||||||
)
|
|
||||||
|
|
||||||
BackHandler(
|
|
||||||
onBack = {
|
|
||||||
onBackButtonClicked(
|
|
||||||
coroutineScope = coroutineScope,
|
|
||||||
bottomSheetScaffoldState = bottomSheetScaffoldState,
|
|
||||||
defaultAction = state.onBackButtonClick,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
var floatingButtonHeight by remember { mutableStateOf(0.dp) }
|
|
||||||
BottomSheetScaffold(
|
|
||||||
modifier = modifier,
|
|
||||||
sheetContent = {
|
|
||||||
SheetContent(
|
|
||||||
coroutineScope = coroutineScope,
|
|
||||||
bottomSheetScaffoldState = bottomSheetScaffoldState,
|
|
||||||
model = state.bottomSheet,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
scaffoldState = bottomSheetScaffoldState,
|
|
||||||
topBar = {
|
|
||||||
AddCustomTokenToolbar(
|
|
||||||
title = state.toolbar.title,
|
|
||||||
onBackButtonClick = {
|
|
||||||
onBackButtonClicked(
|
|
||||||
coroutineScope,
|
|
||||||
bottomSheetScaffoldState,
|
|
||||||
defaultAction = state.toolbar.onBackButtonClick,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
val verticalPadding = TangemTheme.dimens.spacing32
|
|
||||||
AddCustomTokenFloatingButton(
|
|
||||||
model = state.floatingButton,
|
|
||||||
modifier = Modifier.onSizeChanged {
|
|
||||||
floatingButtonHeight = with(density) { it.height.toDp() + verticalPadding }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
floatingActionButtonPosition = FabPosition.Center,
|
|
||||||
sheetBackgroundColor = TangemTheme.colors.background.secondary,
|
|
||||||
sheetPeekHeight = TangemTheme.dimens.size0,
|
|
||||||
backgroundColor = TangemTheme.colors.background.primary,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(paddingValues = it)
|
|
||||||
.padding(bottom = floatingButtonHeight)
|
|
||||||
.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
TestBlock(
|
|
||||||
state.testBlock,
|
|
||||||
coroutineScope,
|
|
||||||
bottomSheetScaffoldState,
|
|
||||||
)
|
|
||||||
|
|
||||||
AddCustomTokenForm(model = state.form)
|
|
||||||
|
|
||||||
AddCustomTokenWarnings(warnings = state.warnings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
|
||||||
private fun onBackButtonClicked(
|
|
||||||
coroutineScope: CoroutineScope,
|
|
||||||
bottomSheetScaffoldState: BottomSheetScaffoldState,
|
|
||||||
defaultAction: () -> Unit,
|
|
||||||
) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
if (bottomSheetScaffoldState.bottomSheetState.isExpanded) {
|
|
||||||
bottomSheetScaffoldState.bottomSheetState.collapse()
|
|
||||||
} else {
|
|
||||||
defaultAction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
|
||||||
@Composable
|
|
||||||
private fun SheetContent(
|
|
||||||
model: AddCustomTokenChooseTokenBottomSheet,
|
|
||||||
coroutineScope: CoroutineScope,
|
|
||||||
bottomSheetScaffoldState: BottomSheetScaffoldState,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(LocalConfiguration.current.screenHeightDp.dp - TangemTheme.dimens.spacing16),
|
|
||||||
) {
|
|
||||||
Hand()
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
) {
|
|
||||||
model.categoriesBlocks.forEachIndexed { index, categoryBlock ->
|
|
||||||
key(categoryBlock) {
|
|
||||||
Column {
|
|
||||||
TokensList(
|
|
||||||
title = categoryBlock.name,
|
|
||||||
tokens = categoryBlock.items,
|
|
||||||
onTestTokenClick = { address ->
|
|
||||||
model.onTestTokenClick(address)
|
|
||||||
coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.collapse() }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if (model.categoriesBlocks.lastIndex != index) {
|
|
||||||
Divider()
|
|
||||||
SpacerH8()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun TokensList(title: String, tokens: List<TestTokenItem>, onTestTokenClick: (String) -> Unit) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
horizontal = TangemTheme.dimens.spacing24,
|
|
||||||
vertical = TangemTheme.dimens.spacing8,
|
|
||||||
),
|
|
||||||
maxLines = 1,
|
|
||||||
style = TangemTheme.typography.h3,
|
|
||||||
)
|
|
||||||
|
|
||||||
tokens.forEach { token ->
|
|
||||||
key(token) {
|
|
||||||
PrimaryButton(
|
|
||||||
text = token.name,
|
|
||||||
onClick = { onTestTokenClick(token.address) },
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
|
||||||
.padding(bottom = TangemTheme.dimens.spacing8)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
|
|
||||||
@Composable
|
|
||||||
private fun TestBlock(
|
|
||||||
model: AddCustomTokenTestBlock,
|
|
||||||
coroutineScope: CoroutineScope,
|
|
||||||
bottomSheetScaffoldState: BottomSheetScaffoldState,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
|
||||||
.padding(top = TangemTheme.dimens.spacing16),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
|
||||||
) {
|
|
||||||
val softwareKeyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
PrimaryButton(
|
|
||||||
text = model.chooseTokenButtonText,
|
|
||||||
onClick = {
|
|
||||||
softwareKeyboardController?.hide()
|
|
||||||
coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.expand() }
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
PrimaryButton(
|
|
||||||
text = model.clearButtonText,
|
|
||||||
onClick = model.onClearAddressButtonClick,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
PrimaryButton(
|
|
||||||
text = model.resetButtonText,
|
|
||||||
onClick = model.onResetButtonClick,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun Preview_AddCustomTokenTestContent() {
|
|
||||||
TangemThemePreview {
|
|
||||||
AddCustomTokenTestContent(state = AddCustomTokenPreviewData.createTestContent())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.imePadding
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
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.PrimaryButtonIconStart
|
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom token floating button. Attached above the keyboard.
|
|
||||||
*
|
|
||||||
* @param model button model
|
|
||||||
* @param modifier modifier
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, modifier: Modifier = Modifier) {
|
|
||||||
PrimaryButtonIconStart(
|
|
||||||
modifier = modifier
|
|
||||||
.imePadding()
|
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
text = stringResource(id = R.string.custom_token_add_token),
|
|
||||||
iconResId = R.drawable.ic_plus_24,
|
|
||||||
enabled = model.isEnabled,
|
|
||||||
showProgress = model.showProgress,
|
|
||||||
onClick = model.onClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun Preview_AddCustomTokenFloatingButton(
|
|
||||||
@PreviewParameter(AddCustomTokenFloatingButtonProvider::class) model: AddCustomTokenFloatingButton,
|
|
||||||
) {
|
|
||||||
TangemThemePreview {
|
|
||||||
AddCustomTokenFloatingButton(model)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AddCustomTokenFloatingButtonProvider : CollectionPreviewParameterProvider<AddCustomTokenFloatingButton>(
|
|
||||||
listOf(
|
|
||||||
AddCustomTokenFloatingButton(isEnabled = true, showProgress = false, onClick = {}),
|
|
||||||
AddCustomTokenFloatingButton(isEnabled = false, showProgress = false, onClick = {}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
|
||||||
|
|
||||||
import androidx.compose.animation.*
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
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.res.TangemTheme
|
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
|
||||||
import com.tangem.tap.common.compose.TangemTextFieldsDefault
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData
|
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom token form
|
|
||||||
*
|
|
||||||
* @param model component model
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
internal fun AddCustomTokenForm(model: AddCustomTokenForm) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(TangemTheme.dimens.spacing16),
|
|
||||||
shape = RoundedCornerShape(TangemTheme.dimens.radius8),
|
|
||||||
backgroundColor = TangemTheme.colors.background.primary,
|
|
||||||
elevation = TangemTheme.dimens.elevation4,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(TangemTheme.dimens.spacing16),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
|
||||||
) {
|
|
||||||
if (model.showTokenFields) InputField(model = model.contractAddressInputField)
|
|
||||||
SelectorField(model = model.networkSelectorField)
|
|
||||||
if (model.showTokenFields) InputField(model = model.tokenNameInputField)
|
|
||||||
if (model.showTokenFields) InputField(model = model.tokenSymbolInputField)
|
|
||||||
if (model.showTokenFields) InputField(model = model.decimalsInputField)
|
|
||||||
model.derivationPathSelectorField?.let { SelectorField(model = it) }
|
|
||||||
if (model.derivationPathInputField?.showField == true) InputField(model = model.derivationPathInputField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun InputField(model: AddCustomTokenInputField) {
|
|
||||||
Column {
|
|
||||||
val isError = (model as? AddCustomTokenInputField.ContactAddress)?.isError ?: false
|
|
||||||
|
|
||||||
TextField(model, isError)
|
|
||||||
|
|
||||||
(model as? AddCustomTokenInputField.ContactAddress)?.error?.resolveReference()?.let {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = isError,
|
|
||||||
enter = fadeIn() + slideInVertically(),
|
|
||||||
exit = slideOutVertically() + fadeOut(),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
color = MaterialTheme.colors.error,
|
|
||||||
style = TangemTheme.typography.body2,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun TextField(model: AddCustomTokenInputField, isError: Boolean) {
|
|
||||||
Box {
|
|
||||||
val isEnabled = when (model) {
|
|
||||||
is AddCustomTokenInputField.ContactAddress -> true
|
|
||||||
is AddCustomTokenInputField.Decimals -> model.isEnabled
|
|
||||||
is AddCustomTokenInputField.TokenName -> model.isEnabled
|
|
||||||
is AddCustomTokenInputField.TokenSymbol -> model.isEnabled
|
|
||||||
is AddCustomTokenInputField.DerivationPath -> true
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
value = model.value,
|
|
||||||
onValueChange = model.onValueChange,
|
|
||||||
keyboardOptions = model.keyboardOptions,
|
|
||||||
label = {
|
|
||||||
Text(
|
|
||||||
text = model.label.resolveReference(),
|
|
||||||
style = TangemTheme.typography.caption2,
|
|
||||||
color = TangemTextFieldsDefault.defaultTextFieldColors.labelColor(
|
|
||||||
enabled = isEnabled,
|
|
||||||
error = isError,
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
).value,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
placeholder = {
|
|
||||||
Text(
|
|
||||||
text = model.placeholder.resolveReference(),
|
|
||||||
style = TangemTheme.typography.body1,
|
|
||||||
color = TangemTextFieldsDefault.defaultTextFieldColors
|
|
||||||
.placeholderColor(enabled = isEnabled)
|
|
||||||
.value,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
singleLine = true,
|
|
||||||
enabled = isEnabled,
|
|
||||||
isError = isError,
|
|
||||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
|
||||||
)
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = (model as? AddCustomTokenInputField.ContactAddress)?.isLoading ?: false,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing6)
|
|
||||||
.padding(bottom = TangemTheme.dimens.spacing6),
|
|
||||||
) {
|
|
||||||
LinearProgressIndicator(color = TangemTheme.colors.icon.primary1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Be careful with this function – ExposedDropdownMenuBox can crash the app if it is open and user clicks system back
|
|
||||||
* button. It was fixed in compose-material 1.6.4.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
|
||||||
@Composable
|
|
||||||
private fun SelectorField(model: AddCustomTokenSelectorField) {
|
|
||||||
var isExpanded by remember { mutableStateOf(value = false) }
|
|
||||||
|
|
||||||
ExposedDropdownMenuBox(
|
|
||||||
expanded = isExpanded,
|
|
||||||
onExpandedChange = { isExpanded = !isExpanded },
|
|
||||||
) {
|
|
||||||
val isEnabled = (model as? AddCustomTokenSelectorField.DerivationPath)?.isEnabled ?: true
|
|
||||||
OutlinedTextField(
|
|
||||||
value = when (val item = model.selectedItem) {
|
|
||||||
is AddCustomTokenSelectorField.SelectorItem.Title -> item.title
|
|
||||||
is AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle -> item.subtitle
|
|
||||||
}.resolveReference(),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onValueChange = {},
|
|
||||||
readOnly = true,
|
|
||||||
enabled = isEnabled,
|
|
||||||
label = { Text(text = model.label.resolveReference()) },
|
|
||||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = isExpanded) },
|
|
||||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
|
||||||
)
|
|
||||||
|
|
||||||
ExposedDropdownMenu(
|
|
||||||
expanded = isExpanded && isEnabled,
|
|
||||||
onDismissRequest = { isExpanded = false },
|
|
||||||
) {
|
|
||||||
model.items.forEachIndexed { index, item ->
|
|
||||||
DropdownMenuItem(
|
|
||||||
onClick = {
|
|
||||||
model.onMenuItemClick(index)
|
|
||||||
isExpanded = false
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
Text(text = item.title.resolveReference())
|
|
||||||
|
|
||||||
val subtitle = (item as? AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle)
|
|
||||||
?.subtitle?.resolveReference()
|
|
||||||
|
|
||||||
if (!subtitle.isNullOrBlank()) {
|
|
||||||
Text(
|
|
||||||
text = subtitle,
|
|
||||||
color = TangemTheme.colors.text.secondary,
|
|
||||||
maxLines = 1,
|
|
||||||
style = TangemTheme.typography.caption2,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun Preview_AddCustomTokenForm(@PreviewParameter(AddCustomTokenFormProvider::class) model: AddCustomTokenForm) {
|
|
||||||
TangemThemePreview {
|
|
||||||
AddCustomTokenForm(model)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AddCustomTokenFormProvider : CollectionPreviewParameterProvider<AddCustomTokenForm>(
|
|
||||||
collection = listOf(
|
|
||||||
AddCustomTokenPreviewData.createDefaultForm(),
|
|
||||||
AddCustomTokenPreviewData.createDefaultForm().copy(derivationPathSelectorField = null),
|
|
||||||
AddCustomTokenPreviewData.createDefaultForm().let { form ->
|
|
||||||
form.copy(contractAddressInputField = form.contractAddressInputField.copy(isLoading = true))
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.material.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import com.tangem.core.ui.R
|
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom token toolbar
|
|
||||||
*
|
|
||||||
* @param title title
|
|
||||||
* @param onBackButtonClick lambda be invoked when BackButton is been pressed
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
internal fun AddCustomTokenToolbar(title: TextReference, onBackButtonClick: () -> Unit) {
|
|
||||||
val toolbarElevation = if (isSystemInDarkTheme()) {
|
|
||||||
TangemTheme.dimens.elevation0
|
|
||||||
} else {
|
|
||||||
AppBarDefaults.TopAppBarElevation
|
|
||||||
}
|
|
||||||
TopAppBar(
|
|
||||||
backgroundColor = TangemTheme.colors.background.primary,
|
|
||||||
elevation = toolbarElevation,
|
|
||||||
) {
|
|
||||||
IconButton(onClick = onBackButtonClick) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_back_24),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = TangemTheme.colors.icon.secondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing26))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = title.resolveReference(),
|
|
||||||
color = TangemTheme.colors.text.primary1,
|
|
||||||
maxLines = 1,
|
|
||||||
style = TangemTheme.typography.h3,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
internal fun Preview_AddCustomTokenToolbar() {
|
|
||||||
TangemThemePreview {
|
|
||||||
AddCustomTokenToolbar(title = TextReference.Res(R.string.add_custom_token_title), onBackButtonClick = {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.Card
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.key
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
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.customtoken.impl.presentation.models.AddCustomTokenWarning
|
|
||||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData
|
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add custom token warnings
|
|
||||||
*
|
|
||||||
* @param warnings warnings descriptions set
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
internal fun AddCustomTokenWarnings(warnings: Set<AddCustomTokenWarning>) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
|
||||||
) {
|
|
||||||
warnings.forEach { warning ->
|
|
||||||
key(warning) { AddCustomTokenWarning(warning) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AddCustomTokenWarning(warning: AddCustomTokenWarning) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
shape = TangemTheme.shapes.roundedCornersSmall2,
|
|
||||||
backgroundColor = TangemColorPalette.Tangerine,
|
|
||||||
contentColor = TangemColorPalette.White,
|
|
||||||
elevation = TangemTheme.dimens.elevation4,
|
|
||||||
) {
|
|
||||||
// FIXME("Incorrect typography. Replace with typography from design system")
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.common_warning),
|
|
||||||
maxLines = 1,
|
|
||||||
style = TangemTheme.typography.body2.copy(fontWeight = FontWeight.Bold),
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = warning.description.resolveReference(),
|
|
||||||
fontSize = 13.sp,
|
|
||||||
lineHeight = 18.sp,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Composable
|
|
||||||
private fun Preview_AddCustomTokenWarnings() {
|
|
||||||
TangemThemePreview {
|
|
||||||
AddCustomTokenWarnings(warnings = AddCustomTokenPreviewData.createWarnings())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.validators
|
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.blockchain.common.address.AddressService
|
|
||||||
import com.tangem.common.card.EllipticCurve
|
|
||||||
import com.tangem.domain.tokens.error.AddCustomTokenError
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validator of contract address
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
object ContractAddressValidator {
|
|
||||||
|
|
||||||
/** Validate a [address] using [blockchain] */
|
|
||||||
fun validate(address: String, blockchain: Blockchain): ContractAddressValidatorResult {
|
|
||||||
return when {
|
|
||||||
address.isEmpty() -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.FIELD_IS_EMPTY)
|
|
||||||
validateAddress(blockchain, address) -> ContractAddressValidatorResult.Success
|
|
||||||
else -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.INVALID_CONTRACT_ADDRESS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateAddress(blockchain: Blockchain, address: String): Boolean {
|
|
||||||
return when (blockchain) {
|
|
||||||
Blockchain.Unknown,
|
|
||||||
Blockchain.Binance,
|
|
||||||
Blockchain.BinanceTestnet,
|
|
||||||
-> SuccessAddressValidator.validate(address)
|
|
||||||
Blockchain.Cardano -> blockchain.validateContractAddress(address)
|
|
||||||
else -> blockchain.validateAddress(address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private object SuccessAddressValidator : AddressService() {
|
|
||||||
override fun makeAddress(walletPublicKey: ByteArray, curve: EllipticCurve?): String {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun validate(address: String): Boolean = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.validators
|
|
||||||
|
|
||||||
import com.tangem.domain.tokens.error.AddCustomTokenError
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Result of validation contract address
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
sealed interface ContractAddressValidatorResult {
|
|
||||||
|
|
||||||
/** Success */
|
|
||||||
object Success : ContractAddressValidatorResult
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error
|
|
||||||
*
|
|
||||||
* @property type type of error
|
|
||||||
*/
|
|
||||||
data class Error(val type: AddCustomTokenError) : ContractAddressValidatorResult
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package com.tangem.tap.features.customtoken.impl.presentation.viewmodels
|
|
||||||
|
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|
||||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
|
||||||
import com.tangem.tap.common.analytics.events.ManageTokens
|
|
||||||
|
|
||||||
/** Analytics sender for tokens list screen */
|
|
||||||
class AddCustomTokenAnalyticsSender(private val analyticsEventHandler: AnalyticsEventHandler) {
|
|
||||||
|
|
||||||
fun sendWhenScreenOpened() {
|
|
||||||
analyticsEventHandler.send(ManageTokens.CustomToken.ScreenOpened)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendWhenAddTokenButtonClicked(customCurrency: CustomCurrency) {
|
|
||||||
analyticsEventHandler.send(ManageTokens.CustomToken.TokenWasAdded(customCurrency))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,6 @@
|
||||||
package com.tangem.tap.features.details.redux
|
package com.tangem.tap.features.details.redux
|
||||||
|
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
|
@ -14,15 +13,6 @@ sealed class DetailsAction : Action {
|
||||||
val shouldSaveUserWallets: Boolean,
|
val shouldSaveUserWallets: Boolean,
|
||||||
) : DetailsAction()
|
) : DetailsAction()
|
||||||
|
|
||||||
data object ScanAndSaveUserWallet : DetailsAction() {
|
|
||||||
|
|
||||||
data object Success : DetailsAction()
|
|
||||||
|
|
||||||
data class Error(val error: TextReference?) : DetailsAction()
|
|
||||||
}
|
|
||||||
|
|
||||||
data object DismissError : DetailsAction()
|
|
||||||
|
|
||||||
sealed class AppSettings : DetailsAction() {
|
sealed class AppSettings : DetailsAction() {
|
||||||
data class SwitchPrivacySetting(
|
data class SwitchPrivacySetting(
|
||||||
val enable: Boolean,
|
val enable: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,24 @@ package com.tangem.tap.features.details.redux
|
||||||
|
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import com.tangem.common.CompletionResult
|
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.doOnFailure
|
||||||
import com.tangem.common.doOnSuccess
|
import com.tangem.common.doOnSuccess
|
||||||
import com.tangem.common.routing.AppRoute
|
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.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.apptheme.model.AppThemeMode
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
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.*
|
||||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||||
import com.tangem.tap.common.analytics.events.Settings
|
import com.tangem.tap.common.analytics.events.Settings
|
||||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||||
import com.tangem.tap.common.extensions.inject
|
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.AppState
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
import com.tangem.tap.features.demo.DemoHelper
|
import com.tangem.tap.features.demo.DemoHelper
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.utils.coroutines.JobHolder
|
import com.tangem.utils.coroutines.JobHolder
|
||||||
import com.tangem.utils.coroutines.saveIn
|
import com.tangem.utils.coroutines.saveIn
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
@ -41,7 +29,6 @@ import kotlinx.coroutines.launch
|
||||||
import org.rekotlin.Action
|
import org.rekotlin.Action
|
||||||
import org.rekotlin.Middleware
|
import org.rekotlin.Middleware
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
|
|
||||||
|
|
||||||
class DetailsMiddleware {
|
class DetailsMiddleware {
|
||||||
private val appSettingsMiddleware = AppSettingsMiddleware()
|
private val appSettingsMiddleware = AppSettingsMiddleware()
|
||||||
|
|
@ -62,7 +49,6 @@ class DetailsMiddleware {
|
||||||
private fun handleAction(state: DetailsState, action: Action) {
|
private fun handleAction(state: DetailsState, action: Action) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, 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
|
package com.tangem.tap.features.details.redux
|
||||||
|
|
||||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
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.extensions.inject
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
|
|
@ -21,7 +20,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
||||||
val detailsState = state.detailsState
|
val detailsState = state.detailsState
|
||||||
return when (action) {
|
return when (action) {
|
||||||
is DetailsAction.PrepareScreen -> {
|
is DetailsAction.PrepareScreen -> {
|
||||||
handlePrepareScreen(action, state)
|
handlePrepareScreen(action)
|
||||||
}
|
}
|
||||||
is DetailsAction.AppSettings -> {
|
is DetailsAction.AppSettings -> {
|
||||||
handlePrivacyAction(action, detailsState)
|
handlePrivacyAction(action, detailsState)
|
||||||
|
|
@ -31,26 +30,12 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
||||||
selectedAppCurrency = action.currency,
|
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(
|
return DetailsState(
|
||||||
scanResponse = action.scanResponse,
|
scanResponse = action.scanResponse,
|
||||||
createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
|
||||||
appSettingsState = AppSettingsState(
|
appSettingsState = AppSettingsState(
|
||||||
isBiometricsAvailable = runBlocking {
|
isBiometricsAvailable = runBlocking {
|
||||||
tangemSdkManager.checkCanUseBiometry()
|
tangemSdkManager.checkCanUseBiometry()
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,16 @@
|
||||||
package com.tangem.tap.features.details.redux
|
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.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import org.rekotlin.StateType
|
import org.rekotlin.StateType
|
||||||
|
|
||||||
data class DetailsState(
|
data class DetailsState(
|
||||||
|
@Deprecated("Delete after onboarding refactoring")
|
||||||
val scanResponse: ScanResponse? = null,
|
val scanResponse: ScanResponse? = null,
|
||||||
val privacyPolicyUrl: String? = null,
|
|
||||||
val createBackupAllowed: Boolean = false,
|
|
||||||
val isScanningInProgress: Boolean = false,
|
|
||||||
val error: TextReference? = null,
|
|
||||||
val appSettingsState: AppSettingsState = AppSettingsState(),
|
val appSettingsState: AppSettingsState = AppSettingsState(),
|
||||||
) : StateType
|
) : StateType
|
||||||
|
|
||||||
sealed class ResetCardDialog {
|
|
||||||
data object StartResetDialog : ResetCardDialog()
|
|
||||||
data object ContinueResetDialog : ResetCardDialog()
|
|
||||||
data object InterruptedResetDialog : ResetCardDialog()
|
|
||||||
data object CompletedResetDialog : ResetCardDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
data class AppSettingsState(
|
data class AppSettingsState(
|
||||||
val saveWallets: Boolean = false,
|
val saveWallets: Boolean = false,
|
||||||
val saveAccessCodes: 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.CanUseBiometryUseCase
|
||||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
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.AnalyticsParam
|
||||||
import com.tangem.tap.common.analytics.events.Settings
|
import com.tangem.tap.common.analytics.events.Settings
|
||||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||||
|
|
@ -45,7 +44,6 @@ internal class AppSettingsViewModel @Inject constructor(
|
||||||
private val appThemeModeRepository: AppThemeModeRepository,
|
private val appThemeModeRepository: AppThemeModeRepository,
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
|
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
|
||||||
private val detailsFeatureToggles: DetailsFeatureToggles,
|
|
||||||
) : ViewModel(),
|
) : ViewModel(),
|
||||||
StoreSubscriber<DetailsState>,
|
StoreSubscriber<DetailsState>,
|
||||||
DefaultLifecycleObserver {
|
DefaultLifecycleObserver {
|
||||||
|
|
@ -62,9 +60,7 @@ internal class AppSettingsViewModel @Inject constructor(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
bootstrapAppCurrencyUpdates()
|
bootstrapAppCurrencyUpdates()
|
||||||
if (detailsFeatureToggles.isRedesignEnabled) {
|
bootstrapBiometricsUpdates()
|
||||||
bootstrapBiometricsUpdates()
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribeToStoreChanges()
|
subscribeToStoreChanges()
|
||||||
sendItemsAnalytics()
|
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.analytics.events.Settings
|
||||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
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.cardsettings.domain.CardSettingsInteractor
|
||||||
import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription
|
import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,6 @@ abstract class BaseDisclaimer(
|
||||||
override suspend fun isAccepted(): Boolean = dataProvider.isAccepted()
|
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) {
|
class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) {
|
||||||
override fun getUri(): Uri = Uri.parse("$baseUrl/tangem_tos.html")
|
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(
|
StoriesScreenContent(
|
||||||
modifier = Modifier.fillMaxSize().testTag(TestTags.STORIES_SCREEN),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.testTag(TestTags.STORIES_SCREEN),
|
||||||
config = StoriesScreenContentConfig(
|
config = StoriesScreenContentConfig(
|
||||||
storiesSize = state.stories.lastIndex,
|
storiesSize = state.stories.lastIndex,
|
||||||
currentStoryIndex = currentStoryIndex,
|
currentStoryIndex = currentStoryIndex,
|
||||||
|
|
@ -157,7 +159,7 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M
|
||||||
duration = currentStoryDuration,
|
duration = currentStoryDuration,
|
||||||
)
|
)
|
||||||
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
|
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
|
||||||
is Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
|
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
|
||||||
isPaused = isPaused,
|
isPaused = isPaused,
|
||||||
stepDuration = currentStoryDuration,
|
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 ReadCard(val scope: CoroutineScope) : HomeAction()
|
||||||
|
|
||||||
data class ScanInProgress(val scanInProgress: Boolean) : 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 {
|
private fun internalReduce(action: Action, appState: AppState): HomeState {
|
||||||
if (action !is HomeAction) return appState.homeState
|
if (action !is HomeAction) return appState.homeState
|
||||||
|
|
||||||
var state = appState.homeState
|
return when (action) {
|
||||||
when (action) {
|
|
||||||
is HomeAction.ScanInProgress -> {
|
is HomeAction.ScanInProgress -> {
|
||||||
state = state.copy(scanInProgress = action.scanInProgress)
|
appState.homeState.copy(scanInProgress = action.scanInProgress)
|
||||||
}
|
}
|
||||||
is HomeAction.UpdateCountryCode -> {
|
else -> appState.homeState
|
||||||
state.onCountryCodeUpdate(state, action.userCountryCode)
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,56 +1,24 @@
|
||||||
package com.tangem.tap.features.home.redux
|
package com.tangem.tap.features.home.redux
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import androidx.compose.runtime.MutableState
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import org.rekotlin.StateType
|
import org.rekotlin.StateType
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
data class HomeState(
|
data class HomeState(
|
||||||
val scanInProgress: Boolean = false,
|
val scanInProgress: Boolean = false,
|
||||||
val stories: List<Stories> = initDefaultStories(),
|
val stories: ImmutableList<Stories> = Stories.entries.toImmutableList(),
|
||||||
) : StateType {
|
) : StateType {
|
||||||
|
|
||||||
val firstStory: Stories
|
val firstStory: Stories get() = stories[0]
|
||||||
get() = stories[0]
|
|
||||||
|
|
||||||
fun stepOf(story: Stories): Int = stories.indexOf(story)
|
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(
|
enum class Stories(val duration: Int = 6000) {
|
||||||
val duration: Int,
|
TangemIntro,
|
||||||
val isNewWalletAvailable: MutableState<Boolean> = mutableStateOf(HomeState.isNewWalletAvailableInit()),
|
RevolutionaryWallet,
|
||||||
) {
|
UltraSecureBackup,
|
||||||
object TangemIntro : Stories(duration = 6000)
|
Currencies,
|
||||||
object RevolutionaryWallet : Stories(duration = 6000)
|
Web3,
|
||||||
object UltraSecureBackup : Stories(duration = 6000)
|
WalletForEveryone,
|
||||||
object Currencies : Stories(duration = 6000)
|
|
||||||
object Web3 : Stories(duration = 6000)
|
|
||||||
object WalletForEveryone : Stories(duration = 6000)
|
|
||||||
}
|
}
|
||||||
|
|
@ -13,16 +13,15 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||||
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||||
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
||||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
|
||||||
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
|
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
|
||||||
import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
|
import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
|
||||||
|
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
||||||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||||
import com.tangem.tap.common.extensions.setContext
|
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.features.main.model.MainScreenState
|
||||||
import com.tangem.tap.store
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
@ -41,12 +40,12 @@ internal class MainViewModel @Inject constructor(
|
||||||
private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase,
|
private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase,
|
||||||
private val blockchainSDKFactory: BlockchainSDKFactory,
|
private val blockchainSDKFactory: BlockchainSDKFactory,
|
||||||
private val userWalletsListManager: UserWalletsListManager,
|
private val userWalletsListManager: UserWalletsListManager,
|
||||||
private val walletManagersFacade: WalletManagersFacade,
|
|
||||||
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
|
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
stakingFeatureToggles: StakingFeatureToggles,
|
stakingFeatureToggles: StakingFeatureToggles,
|
||||||
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
|
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
|
||||||
private val apiConfigsManager: ApiConfigsManager,
|
private val apiConfigsManager: ApiConfigsManager,
|
||||||
|
homeFeatureToggles: HomeFeatureToggles,
|
||||||
|
private val fetchUserCountryUseCase: FetchUserCountryUseCase,
|
||||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||||
) : ViewModel(), MainIntents {
|
) : ViewModel(), MainIntents {
|
||||||
|
|
||||||
|
|
@ -67,6 +66,14 @@ internal class MainViewModel @Inject constructor(
|
||||||
|
|
||||||
viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() }
|
viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() }
|
||||||
|
|
||||||
|
if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
fetchUserCountryUseCase().onLeft {
|
||||||
|
Timber.e("Unable to fetch the user country code $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateAppCurrencies()
|
updateAppCurrencies()
|
||||||
observeFlips()
|
observeFlips()
|
||||||
displayBalancesHidingStatusToast()
|
displayBalancesHidingStatusToast()
|
||||||
|
|
@ -97,19 +104,6 @@ internal class MainViewModel @Inject constructor(
|
||||||
.onEach { userWallet ->
|
.onEach { userWallet ->
|
||||||
Analytics.setContext(userWallet.scanResponse)
|
Analytics.setContext(userWallet.scanResponse)
|
||||||
Analytics.send(Basic.WalletOpened())
|
Analytics.send(Basic.WalletOpened())
|
||||||
|
|
||||||
if (!feedbackManagerFeatureToggles.isLocalLogsEnabled) {
|
|
||||||
store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder ->
|
|
||||||
infoHolder.setCardInfo(userWallet.scanResponse)
|
|
||||||
|
|
||||||
walletManagersFacade
|
|
||||||
.getAll(userWallet.walletId)
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.onEach(infoHolder::setWalletsInfo)
|
|
||||||
.catch { Timber.e(it) }
|
|
||||||
.launchIn(viewModelScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.flowOn(dispatchers.io)
|
.flowOn(dispatchers.io)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,31 @@
|
||||||
package com.tangem.tap.features.onboarding
|
package com.tangem.tap.features.onboarding
|
||||||
|
|
||||||
|
import com.tangem.blockchain.common.WalletManager
|
||||||
import com.tangem.common.doOnFailure
|
import com.tangem.common.doOnFailure
|
||||||
import com.tangem.common.doOnSuccess
|
import com.tangem.common.doOnSuccess
|
||||||
import com.tangem.common.extensions.guard
|
import com.tangem.common.extensions.guard
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.analytics.models.Basic
|
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.cardTypesResolver
|
||||||
|
import com.tangem.domain.common.util.derivationStyleProvider
|
||||||
import com.tangem.domain.common.util.twinsIsTwinned
|
import com.tangem.domain.common.util.twinsIsTwinned
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ProductType
|
import com.tangem.domain.models.scan.ProductType
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
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.UserWalletBuilder
|
||||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
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.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.demo.DemoHelper
|
||||||
|
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
|
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
|
||||||
import com.tangem.tap.mainScope
|
import com.tangem.tap.mainScope
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
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(
|
private suspend fun proceedWithScanResponse(
|
||||||
scanResponse: ScanResponse,
|
scanResponse: ScanResponse,
|
||||||
backupCardsIds: List<String>?,
|
backupCardsIds: List<String>?,
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue