diff --git a/app/src/main/assets/features_dev.json b/app/src/main/assets/features_dev.json deleted file mode 100644 index b24d083053..0000000000 --- a/app/src/main/assets/features_dev.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "isTopUpEnabled": true, - "isCreatingTwinCardsAllowed": true -} \ No newline at end of file diff --git a/app/src/main/assets/features_prod.json b/app/src/main/assets/features_prod.json deleted file mode 100644 index b24d083053..0000000000 --- a/app/src/main/assets/features_prod.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "isTopUpEnabled": true, - "isCreatingTwinCardsAllowed": true -} \ No newline at end of file diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 328c13826f..5b70b3652d 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 328c13826f7e52ba783db1ba39de5b17b1b79bc5 +Subproject commit 5b70b3652d06811e3f1292e41e735e00fe75783f diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index cb960e3694..e6805136f2 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -718,6 +718,26 @@ "networkId": "sei-network/test" } ] + }, + { + "id": "energy-web-chain", + "name": "Energy Web Chain", + "symbol": "EWT", + "networks": [ + { + "networkId": "energy-web-chain/test" + } + ] + }, + { + "id": "energy-web-x", + "name": "Energy Web X", + "symbol": "EWT", + "networks": [ + { + "networkId": "energy-web-x/test" + } + ] } ] } diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index 21ab576e7f..e14a41915f 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -17,24 +17,23 @@ import com.tangem.domain.apptheme.repository.AppThemeModeRepository import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.repository.CardRepository -import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase -import com.tangem.features.details.DetailsFeatureToggles -import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles import com.tangem.tap.common.log.TangemAppLoggerInitializer +import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository -import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles +import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles import com.tangem.tap.proxy.AppStateHolder import dagger.hilt.EntryPoint import dagger.hilt.InstallIn @@ -56,7 +55,7 @@ interface ApplicationEntryPoint { fun getNetworkConnectionManager(): NetworkConnectionManager - fun getCustomTokenFeatureToggles(): CustomTokenFeatureToggles + fun getCardScanningFeatureToggles(): CardScanningFeatureToggles fun getWalletConnect2Repository(): WalletConnect2Repository @@ -94,20 +93,16 @@ interface ApplicationEntryPoint { fun getCardRepository(): CardRepository - fun getFeedbackManagerFeatureToggles(): FeedbackManagerFeatureToggles - fun getTangemSdkLogger(): TangemSdkLogger fun getSettingsRepository(): SettingsRepository fun getBlockchainSDKFactory(): BlockchainSDKFactory - fun getGetFeedbackEmailUseCase(): GetFeedbackEmailUseCase + fun getSendFeedbackEmailUseCase(): SendFeedbackEmailUseCase fun getSaveBlockchainErrorUseCase(): SaveBlockchainErrorUseCase - fun getDetailsFeatureToggles(): DetailsFeatureToggles - fun getGetCardInfoUseCase(): GetCardInfoUseCase fun getUrlOpener(): UrlOpener @@ -116,7 +111,9 @@ interface ApplicationEntryPoint { fun getAppRouter(): AppRouter - fun getPushNotificationsFeatureToggles(): PushNotificationsFeatureToggles - fun getTangemAppLogger(): TangemAppLoggerInitializer + + fun getHomeFeatureToggles(): HomeFeatureToggles + + fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 10d69f7704..169f30bc6c 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -67,7 +67,6 @@ import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.domain.sdk.TangemSdkManager @@ -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.proxy.AppStateHolder import com.tangem.tap.proxy.redux.DaggerGraphAction -import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.routing.RoutingComponent import com.tangem.tap.routing.configurator.AppRouterConfig import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler @@ -550,11 +548,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } } else { lifecycleScope.launch { - val toggles = store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles) - val isPushPermissionEnabled = toggles.isPushNotificationsEnabled - val shouldShowTos = !cardRepository.isTangemTOSAccepted() && isPushPermissionEnabled - val wasPushInitiallyAsked = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false } - val shouldShowInitialPush = wasPushInitiallyAsked && isPushPermissionEnabled + val shouldShowTos = !cardRepository.isTangemTOSAccepted() + val shouldShowInitialPush = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false } val route = when { shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false) diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index ef8490029a..7468d27129 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -1,13 +1,10 @@ package com.tangem.tap import android.app.Application -import android.content.Context -import android.content.pm.PackageManager import coil.ImageLoader import coil.ImageLoaderFactory import com.chuckerteam.chucker.api.ChuckerInterceptor import com.tangem.Log -import com.tangem.LogFormat import com.tangem.TangemSdkLogger import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder 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.asset.loader.AssetLoader import com.tangem.datasource.config.ConfigManager -import com.tangem.datasource.config.FeaturesLocalLoader import com.tangem.datasource.config.models.Config import com.tangem.datasource.connection.NetworkConnectionManager 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.repository.CardRepository import com.tangem.domain.common.LogConfig -import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase -import com.tangem.features.details.DetailsFeatureToggles -import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler 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.log.TangemAppLoggerInitializer -import com.tangem.tap.common.log.TangemLogCollector import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.appReducer 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.walletconnect2.domain.WalletConnectSessionsRepository -import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles +import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider @@ -96,8 +88,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val networkConnectionManager: NetworkConnectionManager get() = entryPoint.getNetworkConnectionManager() - private val customTokenFeatureToggles: CustomTokenFeatureToggles - get() = entryPoint.getCustomTokenFeatureToggles() + private val cardScanningFeatureToggles: CardScanningFeatureToggles + get() = entryPoint.getCardScanningFeatureToggles() private val walletConnect2Repository: WalletConnect2Repository get() = entryPoint.getWalletConnect2Repository() @@ -153,9 +145,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val cardRepository: CardRepository get() = entryPoint.getCardRepository() - private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles - get() = entryPoint.getFeedbackManagerFeatureToggles() - private val tangemSdkLogger: TangemSdkLogger get() = entryPoint.getTangemSdkLogger() @@ -165,8 +154,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val blockchainSDKFactory: BlockchainSDKFactory get() = entryPoint.getBlockchainSDKFactory() - private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase - get() = entryPoint.getGetFeedbackEmailUseCase() + private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase + get() = entryPoint.getSendFeedbackEmailUseCase() private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase get() = entryPoint.getSaveBlockchainErrorUseCase() @@ -174,9 +163,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val getCardInfoUseCase: GetCardInfoUseCase get() = entryPoint.getGetCardInfoUseCase() - private val detailsFeatureToggles: DetailsFeatureToggles - get() = entryPoint.getDetailsFeatureToggles() - private val urlOpener get() = entryPoint.getUrlOpener() @@ -186,11 +172,14 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val appRouter: AppRouter get() = entryPoint.getAppRouter() - private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles - get() = entryPoint.getPushNotificationsFeatureToggles() - private val tangemAppLoggerInitializer: TangemAppLoggerInitializer get() = entryPoint.getTangemAppLogger() + + private val homeFeatureToggles: HomeFeatureToggles + get() = entryPoint.getHomeFeatureToggles() + + private val getUserCountryUseCase: GetUserCountryUseCase + get() = entryPoint.getGetUserCountryCodeUseCase() // endregion override fun onCreate() { @@ -212,10 +201,11 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { // [REDACTED_JIRA] runBlocking { featureTogglesManager.init() - initConfigManager( - loader = FeaturesLocalLoader(assetLoader, BuildConfig.ENVIRONMENT), - onComplete = ::initWithConfigDependency, - ) + + val config = configManager.initialize() + store.dispatch(GlobalAction.SetConfigManager(configManager)) + + initWithConfigDependency(config = config) } loadNativeLibraries() @@ -233,7 +223,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { ) appStateHolder.mainStore = store - walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId) + walletConnect2Repository.init(projectId = configManager.getConfigSync().walletConnectProjectId) } private fun createReduxStore(): Store { @@ -243,7 +233,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { state = AppState( daggerGraphState = DaggerGraphState( networkConnectionManager = networkConnectionManager, - customTokenFeatureToggles = customTokenFeatureToggles, + cardScanningFeatureToggles = cardScanningFeatureToggles, walletConnectRepository = walletConnect2Repository, walletConnectSessionsRepository = walletConnectSessionsRepository, scanCardProcessor = scanCardProcessor, @@ -260,19 +250,18 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase, generateWalletNameUseCase = generateWalletNameUseCase, cardRepository = cardRepository, - feedbackManagerFeatureToggles = feedbackManagerFeatureToggles, tangemSdkLogger = tangemSdkLogger, settingsRepository = settingsRepository, blockchainSDKFactory = blockchainSDKFactory, saveBlockchainErrorUseCase = saveBlockchainErrorUseCase, - getFeedbackEmailUseCase = getFeedbackEmailUseCase, + sendFeedbackEmailUseCase = sendFeedbackEmailUseCase, getCardInfoUseCase = getCardInfoUseCase, assetLoader = assetLoader, - detailsFeatureToggles = detailsFeatureToggles, urlOpener = urlOpener, shareManager = shareManager, appRouter = appRouter, - pushNotificationsFeatureToggles = pushNotificationsFeatureToggles, + homeFeatureToggles = homeFeatureToggles, + getUserCountryUseCase = getUserCountryUseCase, ), ), ) @@ -289,16 +278,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { 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) { initAnalytics(this, config) - initFeedbackManager(this, store) + Log.addLogger(logger = tangemSdkLogger) } private fun initAnalytics(application: Application, config: Config) { @@ -318,51 +300,4 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { factory.build(Analytics, buildData) // ExceptionHandler.append(blockchainExceptionHandler) TODO: [REDACTED_JIRA] } - - private fun initFeedbackManager(context: Context, store: Store) { - 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)) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt deleted file mode 100644 index 9bf97f2250..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt +++ /dev/null @@ -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 = 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 = 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 = 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() - } - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index 8ba8e26539..98b0289289 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -1,7 +1,6 @@ package com.tangem.tap.common.analytics.events import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.tap.features.details.ui.details.SocialNetwork /** [REDACTED_AUTHOR] @@ -14,20 +13,8 @@ sealed class Settings( ) : AnalyticsEvent(category, event, params, error) { class ScreenOpened : Settings(event = "Settings Screen Opened") - class ButtonChat : Settings(event = "Button - Chat") - class ButtonSendFeedback : Settings(event = "Button - Send Feedback") class ButtonStartWalletConnectSession : Settings(event = "Button - Start Wallet Connect Session") class ButtonStopWalletConnectSession : Settings(event = "Button - Stop Wallet Connect Session") - class ButtonCardSettings : Settings(event = "Button - Card Settings") - class ButtonAppSettings : Settings(event = "Button - App Settings") - class ButtonCreateBackup : Settings(event = "Button - Create Backup") - class ButtonWalletConnect : Settings(event = "Button - Wallet Connect") - object ScanNewCard : Settings(event = "Button - Scan New Card") - - class ButtonSocialNetwork(network: SocialNetwork) : Settings( - event = "Button - Social Network", - params = mapOf("Network" to network.id), - ) sealed class CardSettings( event: String, @@ -63,8 +50,6 @@ sealed class Settings( error = error, ) - class AccessCodeRecoveryButton : CardSettings("Button - Access Code Recovery") - class AccessCodeRecoveryChanged(status: AnalyticsParam.AccessCodeRecoveryStatus) : CardSettings( event = "Access Code Recovery Changed", params = mapOf(status.key to status.value), diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt index 773bca1bd5..0dfce4b8c7 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt @@ -20,8 +20,4 @@ fun BigDecimal.toFormattedString( df.isGroupingUsed = true df.roundingMode = roundingMode return df.format(this) -} - -fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString() - -fun BigDecimal.isPositive(): Boolean = this.signum() == 1 \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt deleted file mode 100644 index 21fc321bbe..0000000000 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ /dev/null @@ -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 = 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() - 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) { - 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): 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") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/DefaultFeedbackManagerFeatureToggles.kt b/app/src/main/java/com/tangem/tap/common/feedback/DefaultFeedbackManagerFeatureToggles.kt deleted file mode 100644 index 85acfd9af6..0000000000 --- a/app/src/main/java/com/tangem/tap/common/feedback/DefaultFeedbackManagerFeatureToggles.kt +++ /dev/null @@ -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") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt deleted file mode 100644 index 7e878f6e69..0000000000 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt deleted file mode 100644 index e9432205b9..0000000000 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt +++ /dev/null @@ -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") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt deleted file mode 100644 index e7713995ac..0000000000 --- a/app/src/main/java/com/tangem/tap/common/feedback/LegacyFeedbackManager.kt +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/ProxyFeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/ProxyFeedbackManager.kt deleted file mode 100644 index d013937813..0000000000 --- a/app/src/main/java/com/tangem/tap/common/feedback/ProxyFeedbackManager.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/log/TangemLogCollector.kt b/app/src/main/java/com/tangem/tap/common/log/TangemLogCollector.kt deleted file mode 100644 index 54a56e17c3..0000000000 --- a/app/src/main/java/com/tangem/tap/common/log/TangemLogCollector.kt +++ /dev/null @@ -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, - private val messageFormatter: LogFormat, -) : TangemSdkLogger { - - private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS") - private val logs = mutableListOf() - 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 = synchronized(mutex) { logs.toList() } - - fun clearLogs() = synchronized(mutex) { logs.clear() } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index cdc2bed475..38732f69ce 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -3,14 +3,12 @@ package com.tangem.tap.common.redux import com.tangem.tap.common.redux.global.globalReducer import com.tangem.tap.features.details.redux.DetailsReducer import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer -import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer import com.tangem.tap.features.home.redux.HomeReducer import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer 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.saveWallet.redux.SaveWalletReducer -import com.tangem.tap.features.tokens.legacy.redux.TokensReducer import com.tangem.tap.features.welcome.redux.WelcomeReducer import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.redux.DaggerGraphReducer @@ -28,8 +26,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder) onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state), twinCardsState = TwinCardsReducer.reduce(action, state), detailsState = DetailsReducer.reduce(action, state), - disclaimerState = DisclaimerReducer.reduce(action, state), - tokensState = TokensReducer.reduce(action, state), walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState), welcomeState = WelcomeReducer.reduce(action, state), saveWalletState = SaveWalletReducer.reduce(action, state), diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index bdcb0bd1c1..4fed318b36 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -10,8 +10,6 @@ import com.tangem.tap.features.details.redux.DetailsMiddleware import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState -import com.tangem.tap.features.disclaimer.redux.DisclaimerMiddleware -import com.tangem.tap.features.disclaimer.redux.DisclaimerState import com.tangem.tap.features.home.redux.HomeMiddleware import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware @@ -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.saveWallet.redux.SaveWalletMiddleware 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.welcome.redux.WelcomeMiddleware import com.tangem.tap.features.welcome.redux.WelcomeState @@ -43,8 +40,6 @@ data class AppState( val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(), val twinCardsState: TwinCardsState = TwinCardsState(), val detailsState: DetailsState = DetailsState(), - val disclaimerState: DisclaimerState = DisclaimerState(), - val tokensState: TokensState = TokensState(), val walletConnectState: WalletConnectState = WalletConnectState(), val welcomeState: WelcomeState = WelcomeState(), val saveWalletState: SaveWalletState = SaveWalletState(), @@ -75,7 +70,6 @@ data class AppState( OnboardingOtherCardsMiddleware.handler, TwinCardsMiddleware.handler, DetailsMiddleware().detailsMiddleware, - DisclaimerMiddleware().disclaimerMiddleware, WalletConnectMiddleware().walletConnectMiddleware, BackupMiddleware().backupMiddleware, WelcomeMiddleware().middleware, diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 2b9e953b46..ae7d06f836 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -6,8 +6,6 @@ import com.tangem.datasource.config.ConfigManager import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.scan.ScanResponse 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.ErrorAction import com.tangem.tap.common.redux.NotificationAction @@ -70,9 +68,6 @@ sealed class GlobalAction : Action { ) : 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 Init : GlobalAction() { diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 43d85d7fc5..2d3356afae 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -55,15 +55,9 @@ private fun handleAction(action: Action, appState: () -> AppState?) { is GlobalAction.RestoreAppCurrency -> { restoreAppCurrency() } - is GlobalAction.SendEmail -> { - store.state.globalState.feedbackManager?.sendEmail( - feedbackData = action.feedbackData, - scanResponse = action.scanResponse, - ) - } is GlobalAction.ExchangeManager.Init -> { val appStateSafe = appState() ?: return - val config = appStateSafe.globalState.configManager?.config ?: return + val config = appStateSafe.globalState.configManager?.getConfigSync() ?: return scope.launch { val scanResponseProvider: () -> ScanResponse? = { @@ -92,21 +86,24 @@ private fun handleAction(action: Action, appState: () -> AppState?) { scope.launch { exchangeManager.update() } } is GlobalAction.FetchUserCountry -> { - scope.launch { - // TODO("After adding DI") get dependencies by DI - runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() } - .onSuccess { - store.dispatchOnMain( - GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()), - ) - } - .onFailure { - store.dispatchOnMain( - GlobalAction.FetchUserCountry.Success( - countryCode = Locale.getDefault().country.lowercase(), - ), - ) - } + val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles) + + if (!homeFeatureToggles.isMigrateUserCountryCodeEnabled) { + scope.launch { + runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() } + .onSuccess { + store.dispatchOnMain( + GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()), + ) + } + .onFailure { + store.dispatchOnMain( + GlobalAction.FetchUserCountry.Success( + countryCode = Locale.getDefault().country.lowercase(), + ), + ) + } + } } } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 6b011082d2..cec7015f94 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -2,12 +2,9 @@ package com.tangem.tap.common.redux.global import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.global.DomainGlobalAction -import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.proxy.AppStateHolder -import com.tangem.tap.store import com.tangem.utils.extensions.replaceBy import org.rekotlin.Action @@ -71,9 +68,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde ) globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance)) } - is GlobalAction.SetFeedbackManager -> { - globalState.copy(feedbackManager = action.feedbackManager) - } is GlobalAction.ShowDialog -> { globalState.copy(dialog = action.stateDialog) } @@ -86,10 +80,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde is GlobalAction.SetIfCardVerifiedOnline -> globalState.copy(cardVerifiedOnline = action.verified) is GlobalAction.FetchUserCountry.Success -> { - store.dispatchOnMain(HomeAction.UpdateCountryCode(action.countryCode)) - globalState.copy( - userCountryCode = action.countryCode, - ) + globalState.copy(userCountryCode = action.countryCode) } else -> globalState } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 420951087b..199835d5ef 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -5,7 +5,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.StateDialog -import com.tangem.tap.common.feedback.LegacyFeedbackManager import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager @@ -18,11 +17,11 @@ data class GlobalState( val cardVerifiedOnline: Boolean = false, val tapWalletManager: TapWalletManager = TapWalletManager(), val configManager: ConfigManager? = null, - val feedbackManager: LegacyFeedbackManager? = null, val appCurrency: AppCurrency = AppCurrency.Default, val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(), + @Deprecated("Use GetUserCountryCodeUseCase instead") val userCountryCode: String? = null, val appThemeMode: AppThemeMode = AppThemeMode.DEFAULT, ) : StateType diff --git a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt index 2712e8375d..1dc41c90bd 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt @@ -1,15 +1,8 @@ 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.utils.convertToSdkAmount import com.tangem.tap.common.extensions.dispatchWithMain 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.global.GlobalAction 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.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import org.rekotlin.Middleware internal object LegacyMiddleware { @@ -33,68 +25,13 @@ internal object LegacyMiddleware { { next -> { 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 -> { store.dispatch( - GlobalAction.Onboarding.Start(action.scanResponse, canSkipBackup = action.canSkipBackup), - ) - } - 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), + GlobalAction.Onboarding.Start( scanResponse = action.scanResponse, - ) - } 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, - ) - } - } + canSkipBackup = action.canSkipBackup, + ), + ) } is LegacyAction.PrepareDetailsScreen -> { val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) diff --git a/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt index 410f81e1c6..6096730a8e 100644 --- a/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt @@ -8,16 +8,19 @@ import androidx.core.view.isVisible import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic +import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.redux.StateDialog import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchOpenUrl -import com.tangem.tap.common.feedback.ScanFailsEmail -import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.extensions.inject import com.tangem.tap.features.home.LocaleRegionProvider 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.wallet.R +import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] @@ -65,7 +68,11 @@ internal object ScanFailsDialog { } customView.findViewById(R.id.request_support_button)?.setOnClickListener { 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(R.id.cancel_button)?.setOnClickListener { store.dispatchDialogHide() diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/email/AndroidEmailSender.kt b/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt similarity index 61% rename from core/navigation/src/main/java/com/tangem/core/navigation/email/AndroidEmailSender.kt rename to app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt index d6fe4b6d35..e38ae3919c 100644 --- a/core/navigation/src/main/java/com/tangem/core/navigation/email/AndroidEmailSender.kt +++ b/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt @@ -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.net.Uri +import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ShareCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import com.tangem.core.navigation.email.EmailSender +import com.tangem.tap.foregroundActivityObserver import timber.log.Timber /** * Implementation of email sender for Android * - * @property context context - * [REDACTED_AUTHOR] */ -internal class AndroidEmailSender(private val context: Context) : EmailSender { +internal class AndroidEmailSender : EmailSender { 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 packageManager = context.packageManager + val packageManager = activity.packageManager val originalIntentResults = packageManager.queryIntentActivities(originalIntent, 0) val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0) @@ -32,7 +39,7 @@ internal class AndroidEmailSender(private val context: Context) : EmailSender { } } .map { - createEmailShareIntent(email).apply { setPackage(it.activityInfo.packageName) } + createEmailShareIntent(activity, email).apply { setPackage(it.activityInfo.packageName) } } .toMutableList() @@ -41,14 +48,14 @@ internal class AndroidEmailSender(private val context: Context) : EmailSender { putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray()) } - ContextCompat.startActivity(context, chooserIntent, null) + ContextCompat.startActivity(activity, chooserIntent, null) } catch (ex: Exception) { Timber.e("Failed to send email: $ex") } } - private fun createEmailShareIntent(email: EmailSender.Email): Intent { - val builder = ShareCompat.IntentBuilder(context) + private fun createEmailShareIntent(activity: AppCompatActivity, email: EmailSender.Email): Intent { + val builder = ShareCompat.IntentBuilder(activity) .setType("message/rfc822") .setEmailTo(arrayOf(email.address)) .setSubject(email.subject) @@ -56,7 +63,7 @@ internal class AndroidEmailSender(private val context: Context) : EmailSender { email.attachment?.let { builder.setStream( - FileProvider.getUriForFile(context, "${context.packageName}.provider", it), + FileProvider.getUriForFile(activity, "${activity.packageName}.provider", it), ) } diff --git a/app/src/main/java/com/tangem/tap/di/FeedbackManagerFeatureTogglesModule.kt b/app/src/main/java/com/tangem/tap/di/FeedbackManagerFeatureTogglesModule.kt deleted file mode 100644 index 4202ba1af8..0000000000 --- a/app/src/main/java/com/tangem/tap/di/FeedbackManagerFeatureTogglesModule.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/UtilsModule.kt b/app/src/main/java/com/tangem/tap/di/UtilsModule.kt index 9c29c8598b..7fdf0263cc 100644 --- a/app/src/main/java/com/tangem/tap/di/UtilsModule.kt +++ b/app/src/main/java/com/tangem/tap/di/UtilsModule.kt @@ -5,8 +5,6 @@ import com.tangem.core.navigation.finisher.AppFinisher import com.tangem.core.navigation.settings.SettingsManager import com.tangem.core.navigation.share.ShareManager 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.settings.IntentSettingsManager import com.tangem.tap.common.share.IntentShareManager @@ -30,10 +28,6 @@ internal object UtilsModule { @Singleton fun provideUrlOpener(): UrlOpener = CustomTabsUrlOpener() - @Provides - @Singleton - fun provideFeedbackManager(): FeedbackManager = ProxyFeedbackManager() - @Provides @Singleton fun provideAppFinisher(@ApplicationContext context: Context): AppFinisher = AndroidAppFinisher(context) diff --git a/app/src/main/java/com/tangem/tap/di/core/navigation/email/EmailSenderModule.kt b/app/src/main/java/com/tangem/tap/di/core/navigation/email/EmailSenderModule.kt new file mode 100644 index 0000000000..b986cfe46d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/core/navigation/email/EmailSenderModule.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index ce9ed6e593..24c41445ab 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -22,14 +22,6 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal object CardDomainModule { - @Provides - @Singleton - fun provideGetBiometricsStatusUseCase( - cardSdkConfigRepository: CardSdkConfigRepository, - ): GetBiometricsStatusUseCase { - return GetBiometricsStatusUseCase(cardSdkConfigRepository = cardSdkConfigRepository) - } - @Provides @Singleton fun provideSetAccessCodeRequestPolicyUseCase( @@ -38,12 +30,6 @@ internal object CardDomainModule { return SetAccessCodeRequestPolicyUseCase(cardSdkConfigRepository = cardSdkConfigRepository) } - @Provides - @Singleton - fun provideWasWalletAlreadySignedHashesConfirmedUseCase(cardRepository: CardRepository): WasCardScannedUseCase { - return WasCardScannedUseCase(cardRepository = cardRepository) - } - @Provides @Singleton fun provideSetCardWasScannedUseCase(cardRepository: CardRepository): SetCardWasScannedUseCase { diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt index 9654238c85..12b071ad16 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt @@ -1,8 +1,10 @@ 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.usecase.GenerateWalletNameUseCase +import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor import dagger.Module import dagger.Provides @@ -14,6 +16,12 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal object CardLegacyDomainModule { + @Provides + @Singleton + fun provideCardScanningFeatureToggles(featureTogglesManager: FeatureTogglesManager): CardScanningFeatureToggles { + return CardScanningFeatureToggles(featureTogglesManager) + } + @Provides @Singleton fun provideScanCardProcessor(): ScanCardProcessor = DefaultScanCardProcessor() diff --git a/app/src/main/java/com/tangem/tap/di/domain/FeedbackDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/FeedbackDomainModule.kt index 04f4f03fb8..b905aa7edc 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/FeedbackDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/FeedbackDomainModule.kt @@ -2,8 +2,8 @@ package com.tangem.tap.di.domain import android.content.Context import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.repository.FeedbackRepository import dagger.Module import dagger.Provides @@ -27,8 +27,8 @@ internal object FeedbackDomainModule { fun provideGetFeedbackEmailUseCase( feedbackRepository: FeedbackRepository, @ApplicationContext context: Context, - ): GetFeedbackEmailUseCase { - return GetFeedbackEmailUseCase(feedbackRepository = feedbackRepository, resources = context.resources) + ): SendFeedbackEmailUseCase { + return SendFeedbackEmailUseCase(feedbackRepository = feedbackRepository, resources = context.resources) } @Provides diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 1b5860c2c0..e175d51e25 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -10,6 +10,8 @@ import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.PermissionRepository import com.tangem.domain.settings.repositories.PromoSettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase +import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository import dagger.Module @@ -212,4 +214,16 @@ internal object SettingsDomainModule { return ShouldSaveAccessCodesUseCase(settingsRepository = settingsRepository) } // endregion + + @Provides + @Singleton + fun provideFetchUserCountryCodeUseCase(settingsRepository: SettingsRepository): FetchUserCountryUseCase { + return FetchUserCountryUseCase(settingsRepository) + } + + @Provides + @Singleton + fun provideGetUserCountryCodeUseCase(settingsRepository: SettingsRepository): GetUserCountryUseCase { + return GetUserCountryUseCase(settingsRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 4c74d58706..5245e5a7ec 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -287,12 +287,6 @@ internal object TokensDomainModule { ) } - @Provides - @Singleton - fun provideGetCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase { - return GetCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository) - } - @Provides @Singleton 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 @Singleton fun provideGetBalanceNotEnoughForFeeWarningUseCase( diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 256c8f1bf9..faaa017b70 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -4,19 +4,12 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.core.analytics.Analytics 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.util.cardTypesResolver -import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.models.UserWallet import com.tangem.operations.attestation.Attestation -import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.disclaimer.createDisclaimer -import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction -import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider @@ -54,30 +47,13 @@ class TapWalletManager( tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) - val featureToggles = store.inject(DaggerGraphState::feedbackManagerFeatureToggles) - if (!featureToggles.isLocalLogsEnabled) { - store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) - } - - updateConfigManager(scanResponse) withMainContext { // Order is important - store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer())) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) } } - - 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) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/CardScanningFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/CardScanningFeatureToggles.kt new file mode 100644 index 0000000000..904cfe1673 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/CardScanningFeatureToggles.kt @@ -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") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt index d4b5ac160c..b8515ee5b9 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt @@ -12,7 +12,7 @@ import com.tangem.tap.store // TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead internal class DefaultScanCardProcessor : ScanCardProcessor { private val isNewCardScanningEnabled: Boolean - get() = store.inject(DaggerGraphState::customTokenFeatureToggles).isNewCardScanningEnabled + get() = store.inject(DaggerGraphState::cardScanningFeatureToggles).isNewCardScanningEnabled override suspend fun scan( cardId: String?, diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index ea7c4ac652..3475f1559f 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain.scanCard import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.routing.AppRoute @@ -18,9 +17,6 @@ import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.disclaimer.createDisclaimer -import com.tangem.tap.features.disclaimer.redux.DisclaimerAction -import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback -import com.tangem.tap.features.disclaimer.redux.DisclaimerSource import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep @@ -102,6 +98,8 @@ internal object LegacyScanProcessor { } } + // TODO: [REDACTED_JIRA] + @Suppress("UnusedPrivateMember") private suspend inline fun showDisclaimerIfNeed( scanResponse: ScanResponse, crossinline disclaimerWillShow: () -> Unit = {}, @@ -109,7 +107,6 @@ internal object LegacyScanProcessor { crossinline onFailure: suspend (error: TangemError) -> Unit, ) { val disclaimer = scanResponse.card.createDisclaimer() - store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) if (disclaimer.isAccepted()) { nextHandler(scanResponse) @@ -120,23 +117,9 @@ internal object LegacyScanProcessor { withContext(Dispatchers.Main.immediate) { disclaimerWillShow() - store.dispatch( - DisclaimerAction.Show( - from = DisclaimerSource.Home, - callback = DisclaimerCallback( - onAccept = { - scope.launch(Dispatchers.Main.immediate) { - nextHandler(scanResponse) - } - }, - onDismiss = { - scope.launch(Dispatchers.Main.immediate) { - onFailure(TangemSdkError.UserCancelled()) - } - }, - ), - ), - ) + store.dispatchNavigationAction { + push(AppRoute.Disclaimer(isTosAccepted = false)) + } } } } @@ -149,10 +132,6 @@ internal object LegacyScanProcessor { crossinline onWalletNotCreated: suspend () -> Unit, crossinline onSuccess: suspend (ScanResponse) -> Unit, ) { - val globalState = store.state.globalState - val tapWalletManager = globalState.tapWalletManager - tapWalletManager.updateConfigManager(scanResponse) - store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) if (OnboardingHelper.isOnboardingCase(scanResponse)) { diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt index 381d348980..09fe732b6d 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -59,12 +59,8 @@ internal object UseCaseScanProcessor { ), ) add(AnalyticsChain(Basic.CardWasScanned(analyticsSource))) - val pushNotificationsToggles = - store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles) - if (pushNotificationsToggles.isPushNotificationsEnabled) { - add(DisclaimerChain(store, disclaimerWillShow)) - } - add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager)) + add(DisclaimerChain(store, disclaimerWillShow)) + add(CheckForOnboardingChain(store)) } scanCardUseCase(cardId, afterScanChains = chains).fold( diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt index 72d0b6ee59..f5606560c0 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt @@ -16,7 +16,6 @@ import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.AppState 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.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep @@ -32,17 +31,14 @@ import org.rekotlin.Store * - [ScanChainException.OnboardingNeeded] if onboarding required. * * @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. */ class CheckForOnboardingChain( private val store: Store, - private val tapWalletManager: TapWalletManager, ) : ResultChain() { override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult { - tapWalletManager.updateConfigManager(previousChainResult) store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult)) return when { diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/DisclaimerChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/DisclaimerChain.kt index 8aa003b952..7bd4be874c 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/DisclaimerChain.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/DisclaimerChain.kt @@ -1,21 +1,15 @@ package com.tangem.tap.domain.scanCard.chains -import arrow.core.left import arrow.core.right +import com.tangem.common.routing.AppRoute import com.tangem.domain.card.ScanCardException import com.tangem.domain.core.chain.Chain import com.tangem.domain.core.chain.ResultChain import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.disclaimer.Disclaimer import com.tangem.tap.features.disclaimer.createDisclaimer -import com.tangem.tap.features.disclaimer.redux.DisclaimerAction -import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback -import com.tangem.tap.features.disclaimer.redux.DisclaimerSource -import kotlinx.coroutines.suspendCancellableCoroutine import org.rekotlin.Store -import kotlin.coroutines.resume /** * Handles disclaimer display after the card scanning operation. @@ -40,31 +34,13 @@ internal class DisclaimerChain( previousChainResult.right() } else { disclaimerWillShow() - showDisclaimer(disclaimer, previousChainResult) - } - } - private suspend fun showDisclaimer(disclaimer: Disclaimer, response: ScanResponse): ScanChainResult { - store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) + // TODO: [REDACTED_JIRA] + store.dispatchNavigationAction { + push(route = AppRoute.Disclaimer(isTosAccepted = false)) + } - return suspendCancellableCoroutine { continuation -> - store.dispatchOnMain( - DisclaimerAction.Show( - from = DisclaimerSource.Home, - callback = DisclaimerCallback( - onAccept = { - if (continuation.isActive) { - continuation.resume(response.right()) - } - }, - onDismiss = { - if (continuation.isActive) { - continuation.resume(ScanChainException.DisclaimerWasCanceled.left()) - } - }, - ), - ), - ) + previousChainResult.right() } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt index 2b3094d9f6..0e6ccce36b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt @@ -4,12 +4,12 @@ import com.github.salomonbrys.kotson.registerTypeAdapter import com.google.gson.GsonBuilder import com.tangem.blockchain.common.Blockchain 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.WcBinanceTransferOrder 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.TradeData +import com.tangem.utils.extensions.stripZeroPlainString import timber.log.Timber internal object BnbHelper { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index 8c78ffa3a5..deb7e332b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -239,9 +239,8 @@ class WalletConnectSdkHelper { private suspend fun signTransaction(data: WcTransactionData, cardId: String?): String? { val dataToSign = EthereumUtils.buildTransactionToSign( transactionData = data.transaction, - nonce = null, blockchain = data.walletManager.wallet.blockchain, - ) ?: return null + ) val command = SignHashCommand( hash = dataToSign.hash, diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt deleted file mode 100644 index a6894e04b1..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.tap.features.customtoken.api.featuretoggles - -/** - * Add custom token feature toggles - * -[REDACTED_AUTHOR] - */ -interface CustomTokenFeatureToggles { - - val isNewCardScanningEnabled: Boolean -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/DefaultCustomTokenRepository.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/DefaultCustomTokenRepository.kt deleted file mode 100644 index e9b45a2623..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/DefaultCustomTokenRepository.kt +++ /dev/null @@ -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") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/converters/FoundTokenConverter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/converters/FoundTokenConverter.kt deleted file mode 100644 index 33271e9fb6..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/data/converters/FoundTokenConverter.kt +++ /dev/null @@ -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 { - - 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"), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenFeatureTogglesModule.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenFeatureTogglesModule.kt deleted file mode 100644 index 5648aa592d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenFeatureTogglesModule.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt deleted file mode 100644 index 61dc90c2dc..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt +++ /dev/null @@ -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, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenRouterModule.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenRouterModule.kt deleted file mode 100644 index 0c8dec0948..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenRouterModule.kt +++ /dev/null @@ -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() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt deleted file mode 100644 index 22bea5bf11..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenRepository.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenRepository.kt deleted file mode 100644 index cb1959d157..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenRepository.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt deleted file mode 100644 index bd9cc89301..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt +++ /dev/null @@ -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 { - 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, - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt deleted file mode 100644 index 5b39f2dd52..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt deleted file mode 100644 index cd06665b00..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt +++ /dev/null @@ -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") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt deleted file mode 100644 index 443a8af290..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt +++ /dev/null @@ -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().apply { - LocalLifecycleOwner.current.lifecycle.addObserver(this) - } - val state by viewModel.uiState.collectAsStateWithLifecycle() - - AddCustomTokenScreen( - modifier = Modifier - .background(TangemTheme.colors.background.primary) - .systemBarsPadding(), - stateHolder = state, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt deleted file mode 100644 index 6ba979beab..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt +++ /dev/null @@ -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, - val onTestTokenClick: (String) -> Unit, -) { - - /** - * Tokens category model - * - * @property name category name - * @property items category items - */ - data class TokensCategoryBlock(val name: String, val items: List) - - /** - * 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 - - /** 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, - 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, - 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, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/CustomTokenType.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/CustomTokenType.kt deleted file mode 100644 index b36fe58ce3..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/CustomTokenType.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.tangem.tap.features.customtoken.impl.presentation.models - -/** Custom token type */ -enum class CustomTokenType { TOKEN, BLOCKCHAIN } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/SupportBlockchainType.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/SupportBlockchainType.kt deleted file mode 100644 index 27215046bd..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/SupportBlockchainType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.tap.features.customtoken.impl.presentation.models - -internal enum class SupportBlockchainType { - - SUPPORTED, UNSUPPORTED, UNABLE_TO_DETERMINE -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt deleted file mode 100644 index 1c016c4993..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt +++ /dev/null @@ -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() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt deleted file mode 100644 index 348c3492ce..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt +++ /dev/null @@ -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() } - } - - 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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt deleted file mode 100644 index 32eba291ef..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt +++ /dev/null @@ -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 - - /** 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 = 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, - 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, - override val floatingButton: AddCustomTokenFloatingButton, - val testBlock: AddCustomTokenTestBlock, - val bottomSheet: AddCustomTokenChooseTokenBottomSheet, - ) : AddCustomTokenStateHolder -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt deleted file mode 100644 index afff4bf8e5..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt +++ /dev/null @@ -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()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt deleted file mode 100644 index ba7d22727c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt +++ /dev/null @@ -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 { - 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 = {}, - ), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt deleted file mode 100644 index d2e3729321..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt +++ /dev/null @@ -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( - collection = listOf( - AddCustomTokenPreviewData.createContent(), - AddCustomTokenPreviewData.createTestContent(), - ), -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt deleted file mode 100644 index f5b7759a82..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt +++ /dev/null @@ -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, 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()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt deleted file mode 100644 index 3cc1d4847a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt +++ /dev/null @@ -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( - listOf( - AddCustomTokenFloatingButton(isEnabled = true, showProgress = false, onClick = {}), - AddCustomTokenFloatingButton(isEnabled = false, showProgress = false, onClick = {}), - ), -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt deleted file mode 100644 index 58da365d81..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt +++ /dev/null @@ -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( - collection = listOf( - AddCustomTokenPreviewData.createDefaultForm(), - AddCustomTokenPreviewData.createDefaultForm().copy(derivationPathSelectorField = null), - AddCustomTokenPreviewData.createDefaultForm().let { form -> - form.copy(contractAddressInputField = form.contractAddressInputField.copy(isLoading = true)) - }, - ), -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenToolbar.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenToolbar.kt deleted file mode 100644 index 030f130a1a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenToolbar.kt +++ /dev/null @@ -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 = {}) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt deleted file mode 100644 index a323a8a982..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt +++ /dev/null @@ -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) { - 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()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt deleted file mode 100644 index 966af2bd6a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidator.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidatorResult.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidatorResult.kt deleted file mode 100644 index daf1b4d159..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/validators/ContractAddressValidatorResult.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt deleted file mode 100644 index 55886b316a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt +++ /dev/null @@ -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)) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt deleted file mode 100644 index 0fadd8ea5b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ /dev/null @@ -1,1048 +0,0 @@ -package com.tangem.tap.features.customtoken.impl.presentation.viewmodels - -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.tangem.blockchain.blockchains.cardano.CardanoTokenAddressConverter -import com.tangem.blockchain.blockchains.hedera.HederaTokenAddressConverter -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.derivation.DerivationStyle -import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.blockchainsdk.utils.isSupportedInApp -import com.tangem.blockchainsdk.utils.toNetworkId -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.extensions.canHandleBlockchain -import com.tangem.domain.common.extensions.canHandleToken -import com.tangem.domain.common.extensions.supportedBlockchains -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.features.addCustomToken.CustomCurrency -import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase -import com.tangem.domain.tokens.error.AddCustomTokenError -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor -import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken -import com.tangem.tap.features.customtoken.impl.presentation.models.* -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TokensCategoryBlock -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField.SelectorItem -import com.tangem.tap.features.customtoken.impl.presentation.routers.CustomTokenRouter -import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder -import com.tangem.tap.features.customtoken.impl.presentation.validators.ContractAddressValidator -import com.tangem.tap.features.customtoken.impl.presentation.validators.ContractAddressValidatorResult -import com.tangem.tap.features.details.ui.cardsettings.TextReference -import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider -import com.tangem.utils.coroutines.runCatching -import com.tangem.wallet.BuildConfig -import com.tangem.wallet.R -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.flow.updateAndGet -import kotlinx.coroutines.launch -import timber.log.Timber -import javax.inject.Inject - -/** - * ViewModel for add custom token screen - * - * @param analyticsEventHandler analytics event handler - * @param featureRouter feature router - * @property featureInteractor feature interactor - * @property getSelectedWalletSyncUseCase use case that returns selected wallet - * @property dispatchers coroutine dispatchers provider - * -[REDACTED_AUTHOR] - */ -@Suppress("LargeClass", "LongParameterList") -@HiltViewModel -internal class AddCustomTokenViewModel @Inject constructor( - analyticsEventHandler: AnalyticsEventHandler, - featureRouter: CustomTokenRouter, - getCurrenciesUseCase: GetCryptoCurrenciesUseCase, - private val featureInteractor: CustomTokenInteractor, - private val dispatchers: AppCoroutineDispatcherProvider, - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, -) : ViewModel(), DefaultLifecycleObserver { - - private val analyticsSender = AddCustomTokenAnalyticsSender(analyticsEventHandler) - private val actionsHandler = ActionsHandler(featureRouter) - private val testActionsHandler = TestActionsHandler() - private val formStateBuilder = FormStateBuilder() - private val hederaAddressConverter = HederaTokenAddressConverter() - private val cardanoTokenAddressConverter = CardanoTokenAddressConverter() - - private var currentCryptoCurrencies: List = emptyList() - - /** Screen state */ - val uiState: MutableStateFlow = MutableStateFlow(value = getInitialUiState()) - - private var foundToken: FoundToken? = null - - init { - viewModelScope.launch(dispatchers.main) { - currentCryptoCurrencies = getSelectedWalletSyncUseCase().fold( - ifLeft = { emptyList() }, - ifRight = { selectedWallet -> - getCurrenciesUseCase.getSync(selectedWallet.walletId).fold( - ifLeft = { emptyList() }, - ifRight = { it }, - ) - }, - ) - } - } - - override fun onCreate(owner: LifecycleOwner) { - analyticsSender.sendWhenScreenOpened() - } - - private fun getInitialUiState(): AddCustomTokenStateHolder { - return if (BuildConfig.TEST_ACTION_ENABLED) { - AddCustomTokenStateHolder.TestContent( - onBackButtonClick = actionsHandler::onBackButtonClick, - toolbar = createToolbar(), - form = formStateBuilder.createForm(), - warnings = emptySet(), - floatingButton = createFloatingButton(), - testBlock = AddCustomTokenTestBlock( - chooseTokenButtonText = "Choose token", - clearButtonText = "Clear address", - resetButtonText = "Reset", - onClearAddressButtonClick = testActionsHandler::onClearAddressButtonClick, - onResetButtonClick = testActionsHandler::onResetButtonClick, - ), - bottomSheet = AddCustomTokenChooseTokenBottomSheet( - categoriesBlocks = listOf( - TokensCategoryBlock(name = "Common", items = COMMON_TOKENS), - TokensCategoryBlock(name = "Solana", items = SOLANA_TOKENS), - ), - onTestTokenClick = actionsHandler::onContactAddressValueChange, - ), - ) - } else { - AddCustomTokenStateHolder.Content( - onBackButtonClick = actionsHandler::onBackButtonClick, - toolbar = createToolbar(), - form = formStateBuilder.createForm(), - warnings = emptySet(), - floatingButton = createFloatingButton(), - ) - } - } - - private fun createToolbar(): AddCustomTokensToolbar { - return AddCustomTokensToolbar( - title = TextReference.Res(R.string.add_custom_token_title), - onBackButtonClick = actionsHandler::onBackButtonClick, - ) - } - - private fun createFloatingButton(): AddCustomTokenFloatingButton { - return AddCustomTokenFloatingButton( - isEnabled = true, - showProgress = false, - onClick = actionsHandler::onAddCustomTokenClick, - ) - } - - private inner class FormStateBuilder { - - fun createForm(): AddCustomTokenForm { - return AddCustomTokenForm( - contractAddressInputField = createContractAddressInputField(), - networkSelectorField = createNetworkSelectorField(), - tokenNameInputField = createTokenNameInputField(), - tokenSymbolInputField = createTokenSymbolInputField(), - decimalsInputField = createDecimalsInputField(), - derivationPathSelectorField = createDerivationPathsSelectorField(), - derivationPathInputField = createDerivationPathInputField(), - ) - } - - fun createDerivationPathSelectorAdditionalItem( - blockchain: Blockchain, - type: DerivationPathSelectorType = DerivationPathSelectorType.BLOCKCHAIN, - derivationPath: String, - ): SelectorItem.TitleWithSubtitle { - return when (type) { - DerivationPathSelectorType.DEFAULT -> 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, - type = DerivationPathSelectorType.DEFAULT, - ) - DerivationPathSelectorType.CUSTOM -> SelectorItem.TitleWithSubtitle( - title = TextReference.Res(R.string.custom_token_custom_derivation), - subtitle = TextReference.Res(R.string.custom_token_custom_derivation), - blockchain = Blockchain.Unknown, - type = DerivationPathSelectorType.CUSTOM, - ) - DerivationPathSelectorType.BLOCKCHAIN -> SelectorItem.TitleWithSubtitle( - title = TextReference.Str(derivationPath), - subtitle = TextReference.Str(blockchain.getNetworkName()), - blockchain = blockchain, - ) - } - } - - fun createNetworkSelectorItem(blockchain: Blockchain): SelectorItem.Title { - return if (blockchain == Blockchain.Unknown) { - SelectorItem.Title( - title = TextReference.Res(R.string.custom_token_network_input_not_selected), - blockchain = Blockchain.Unknown, - ) - } else { - SelectorItem.Title( - title = TextReference.Str(blockchain.getNetworkName()), - blockchain = blockchain, - ) - } - } - - private fun createContractAddressInputField(): AddCustomTokenInputField.ContactAddress { - return AddCustomTokenInputField.ContactAddress( - value = "", - onValueChange = actionsHandler::onContactAddressValueChange, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), - label = TextReference.Res(R.string.custom_token_contract_address_input_title), - placeholder = TextReference.Str(value = CONTRACT_ADDRESS_PLACEHOLDER), - isLoading = false, - isError = false, - error = null, - ) - } - - private fun createNetworkSelectorField(): AddCustomTokenSelectorField.Network { - val selectorItems = getNetworkSelectorItems() - return AddCustomTokenSelectorField.Network( - label = TextReference.Res(R.string.custom_token_network_input_title), - selectedItem = requireNotNull(selectorItems.firstOrNull()), - items = selectorItems, - onMenuItemClick = actionsHandler::onNetworkSelectorItemClick, - ) - } - - private fun getNetworkSelectorItems(): List { - val defaultNetwork = createNetworkSelectorItem(blockchain = Blockchain.Unknown) - val scanResponse = getSelectedWalletSyncUseCase().fold( - ifLeft = { null }, - ifRight = { it.scanResponse }, - ) - val derivationStyle = scanResponse?.derivationStyleProvider?.getDerivationStyle() - return listOf(defaultNetwork) + Blockchain.entries - .filter { blockchain -> - scanResponse?.card?.supportedBlockchains(scanResponse.cardTypesResolver) - ?.contains(blockchain) == true && isDerivationPathNotEmpty(derivationStyle, blockchain) - } - .sortedBy(Blockchain::fullName) - .map(::createNetworkSelectorItem) - } - - private fun isDerivationPathNotEmpty(derivationStyle: DerivationStyle?, blockchain: Blockchain): Boolean { - // derivationStyle is null for cards without HD wallets, always return true - return if (derivationStyle != null) { - blockchain.derivationPath(derivationStyle)?.rawPath?.isNotEmpty() == true - } else { - true - } - } - - private fun createTokenNameInputField(): AddCustomTokenInputField.TokenName { - return AddCustomTokenInputField.TokenName( - value = "", - onValueChange = actionsHandler::onTokenNameValueChange, - 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, - ) - } - - private fun createTokenSymbolInputField(): AddCustomTokenInputField.TokenSymbol { - return AddCustomTokenInputField.TokenSymbol( - value = "", - onValueChange = actionsHandler::onTokenSymbolValueChange, - 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, - ) - } - - private fun createDecimalsInputField(): AddCustomTokenInputField.Decimals { - return AddCustomTokenInputField.Decimals( - value = "", - onValueChange = actionsHandler::onDecimalsValueChange, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next), - label = TextReference.Res(R.string.custom_token_decimals_input_title), - placeholder = TextReference.Str(value = DECIMALS_PLACEHOLDER), - isEnabled = false, - ) - } - - private fun createDerivationPathsSelectorField(): AddCustomTokenSelectorField.DerivationPath? { - return getSelectedWalletSyncUseCase().fold( - ifLeft = { null }, - ifRight = { - if (!it.scanResponse.card.settings.isHDWalletAllowed) return null - - val selectorItems = getDerivationPathsSelectorItems(it.scanResponse.derivationStyleProvider) - AddCustomTokenSelectorField.DerivationPath( - label = TextReference.Res(R.string.custom_token_derivation_path_input_title), - selectedItem = requireNotNull(selectorItems.firstOrNull()), - items = selectorItems, - onMenuItemClick = actionsHandler::onDerivationPathSelectorItemClick, - isEnabled = true, - ) - }, - ) - } - - private fun getDerivationPathsSelectorItems( - derivationStyleProvider: DerivationStyleProvider?, - ): List { - val derivationStyle = derivationStyleProvider?.getDerivationStyle() - return listOf( - createDerivationPathSelectorAdditionalItem( - blockchain = Blockchain.Unknown, - type = DerivationPathSelectorType.DEFAULT, - derivationPath = "", - ), - createDerivationPathSelectorAdditionalItem( - blockchain = Blockchain.Unknown, - type = DerivationPathSelectorType.CUSTOM, - derivationPath = "", - ), - ) + Blockchain.entries - .filter { blockchain -> - blockchain.isSupportedInApp() && !blockchain.isTestnet() - } - .sortedBy(Blockchain::fullName) - .mapNotNull { - val derivationPath = if (derivationStyle != null) { - it.derivationPath(derivationStyle)?.rawPath - } else { - // derivationStyle is null for cards without HDWallet, use DerivationStyle.V1 - it.derivationPath(DerivationStyle.V1)?.rawPath - } - if (derivationPath?.isNotEmpty() == true) { - createDerivationPathSelectorAdditionalItem( - blockchain = it, - derivationPath = derivationPath, - ) - } else { - null - } - } - } - - private fun createDerivationPathInputField(): AddCustomTokenInputField.DerivationPath? { - return getSelectedWalletSyncUseCase().fold( - ifLeft = { null }, - ifRight = { - if (!it.scanResponse.card.settings.isHDWalletAllowed) return null - - AddCustomTokenInputField.DerivationPath( - value = "", - onValueChange = actionsHandler::onDerivationPathValueChange, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - label = TextReference.Res(R.string.custom_token_custom_derivation), - placeholder = TextReference.Str(value = DERIVATION_PATH_PLACEHOLDER), - ) - }, - ) - } - } - - private fun updateForm(address: String, selectedNetwork: Blockchain) { - viewModelScope.launch(dispatchers.main) { - runCatching(dispatchers.io) { - val tokenAddress = convertTokenAddress(selectedNetwork, address) - ?: error("TokenAddress is invalid") - - featureInteractor.findToken(address = tokenAddress, blockchain = selectedNetwork) - } - .onSuccess { token -> - foundToken = token - uiState.update { state -> - state.copySealed( - form = state.form.copy( - contractAddressInputField = state.form.contractAddressInputField.copy( - isLoading = false, - ), - networkSelectorField = state.form.networkSelectorField.copy( - selectedItem = formStateBuilder.createNetworkSelectorItem( - blockchain = Blockchain.fromNetworkId(token.network.id) - ?: Blockchain.Unknown, - ), - ), - tokenNameInputField = state.form.tokenNameInputField.copy( - value = token.name, - isEnabled = false, - ), - tokenSymbolInputField = state.form.tokenSymbolInputField.copy( - value = token.symbol, - isEnabled = false, - ), - decimalsInputField = state.form.decimalsInputField.copy( - value = token.network.decimalCount, - isEnabled = false, - ), - ), - ) - } - } - .onFailure { - foundToken = null - uiState.update { state -> - state.copySealed( - form = state.form.copy( - contractAddressInputField = state.form.contractAddressInputField.copy( - isLoading = false, - ), - tokenNameInputField = state.form.tokenNameInputField.copy(isEnabled = true), - tokenSymbolInputField = state.form.tokenSymbolInputField.copy(isEnabled = true), - decimalsInputField = state.form.decimalsInputField.copy(isEnabled = true), - ), - ) - } - Timber.e(it) - } - - updateWarnings() - updateFloatingButton() - } - } - - private fun isDerivationPathSelected(): Boolean { - val blockchain = uiState.value.form.derivationPathSelectorField?.selectedItem?.blockchain - val selectorType = uiState.value.form.derivationPathSelectorField?.selectedItem?.type - - return blockchain != null && blockchain != Blockchain.Unknown || - selectorType == DerivationPathSelectorType.CUSTOM && - !uiState.value.warnings.contains(AddCustomTokenWarning.WrongDerivationPath) - } - - private fun updateWarnings() { - uiState.update { state -> - state.copySealed( - warnings = buildSet { - when (getCustomTokenType()) { - CustomTokenType.TOKEN -> { - addAll(getTokenWarningSet()) - } - - CustomTokenType.BLOCKCHAIN -> { - if (isCustomTokenAlreadyAdded()) add(AddCustomTokenWarning.TokenAlreadyAdded) - if (isDerivationPathSelected()) add(AddCustomTokenWarning.PotentialScamToken) - } - } - }, - ) - } - } - - private fun getCustomTokenType(): CustomTokenType { - return if (isAnyTokenFieldsFilled() || isAllTokenFieldsFilled()) { - CustomTokenType.TOKEN - } else { - CustomTokenType.BLOCKCHAIN - } - } - - private fun isAnyTokenFieldsFilled(): Boolean { - return with(uiState.value.form) { - contractAddressInputField.value.isNotEmpty() || tokenNameInputField.value.isNotEmpty() || - tokenSymbolInputField.value.isNotEmpty() || decimalsInputField.value.isNotEmpty() - } - } - - private fun isAllTokenFieldsFilled(): Boolean { - return with(uiState.value.form) { - contractAddressInputField.value.isNotEmpty() && tokenNameInputField.value.isNotEmpty() && - tokenSymbolInputField.value.isNotEmpty() && decimalsInputField.value.isNotEmpty() - } - } - - private fun getTokenWarningSet(): Set { - val networkSelectorValue = uiState.value.form.networkSelectorField.selectedItem.blockchain - - val isContractAddressFieldEmpty = ContractAddressValidator.validate( - address = uiState.value.form.contractAddressInputField.value, - blockchain = networkSelectorValue, - ).let { - it is ContractAddressValidatorResult.Error && it.type == AddCustomTokenError.FIELD_IS_EMPTY - } - - val isSupportedToken = if (!isNetworkSelected()) { - true - } else { - getSelectedWalletSyncUseCase().fold( - ifLeft = { false }, - ifRight = { - it.scanResponse.card.canHandleToken( - blockchain = networkSelectorValue, - cardTypesResolver = it.scanResponse.cardTypesResolver, - ) - }, - ) - } - - return buildSet { - if (!isSupportedToken && !isContractAddressFieldEmpty) { - add(AddCustomTokenWarning.UnsupportedToken(networkSelectorValue.getNetworkName())) - } - if (isCustomTokenAlreadyAdded()) { - add(AddCustomTokenWarning.TokenAlreadyAdded) - } - if (foundToken == null && isAnyTokenFieldsFilled() || foundToken?.isActive == false) { - add(AddCustomTokenWarning.PotentialScamToken) - } - } - } - - private fun isNetworkSelected(): Boolean { - return uiState.value.form.networkSelectorField.selectedItem.blockchain != Blockchain.Unknown - } - - private fun updateFloatingButton() { - uiState.update { state -> - updateStateWithDerivationError(state) - } - if (isCustomTokenAlreadyAdded()) { - uiState.update { state -> - state.copySealed( - warnings = state.warnings + AddCustomTokenWarning.TokenAlreadyAdded, - floatingButton = state.floatingButton.copy(isEnabled = false), - ) - } - return - } - - uiState.update { state -> - val isCorrectDerivationInput = !state.warnings.contains(AddCustomTokenWarning.WrongDerivationPath) - val updatedState = when { - isAllTokenFieldsFilled() && isNetworkSelected() -> { - val networkSelectorValue = state.form.networkSelectorField.selectedItem.blockchain - val error = ContractAddressValidator.validate( - address = state.form.contractAddressInputField.value, - blockchain = networkSelectorValue, - ) - - val isSupportedToken = getSelectedWalletSyncUseCase().fold( - ifLeft = { false }, - ifRight = { - it.scanResponse.card.canHandleToken( - blockchain = networkSelectorValue, - cardTypesResolver = it.scanResponse.cardTypesResolver, - ) - }, - ) - - state.copySealed( - floatingButton = state.floatingButton.copy( - isEnabled = error is ContractAddressValidatorResult.Success && - isSupportedToken && isCorrectDerivationInput, - ), - ) - } - isAnyTokenFieldsFilled() -> { - state.copySealed(floatingButton = state.floatingButton.copy(isEnabled = false)) - } - else -> { - state.copySealed( - floatingButton = state.floatingButton.copy( - isEnabled = if (isNetworkSelected()) { - !isBlockchainAlreadyAdded() && isCorrectDerivationInput - } else { - false - }, - ), - ) - } - } - - updatedState.copySealed( - warnings = updatedState.warnings - AddCustomTokenWarning.TokenAlreadyAdded, - ) - } - } - - private fun updateStateWithDerivationError(uiState: AddCustomTokenStateHolder): AddCustomTokenStateHolder { - val updatedWarnings = if (isWrongDerivationPathEntered( - derivationPathSelectorType = uiState.form.derivationPathSelectorField?.selectedItem?.type, - derivationPath = getDerivationPath(), - ) - ) { - uiState.warnings + AddCustomTokenWarning.WrongDerivationPath - } else { - uiState.warnings - AddCustomTokenWarning.WrongDerivationPath - } - return uiState.copySealed( - warnings = updatedWarnings, - ) - } - - private fun isWrongDerivationPathEntered( - derivationPathSelectorType: DerivationPathSelectorType?, - derivationPath: DerivationPath?, - ): Boolean { - return derivationPathSelectorType == DerivationPathSelectorType.CUSTOM && derivationPath == null - } - - private fun isCustomTokenAlreadyAdded(): Boolean { - return when (getCustomTokenType()) { - CustomTokenType.TOKEN -> isTokenAlreadyAdded() - CustomTokenType.BLOCKCHAIN -> isBlockchainAlreadyAdded() - } - } - - private fun isTokenAlreadyAdded(): Boolean { - val networkSelectorValue = uiState.value.form.networkSelectorField.selectedItem.blockchain - val networkId = Blockchain.fromNetworkId(networkSelectorValue.toNetworkId())?.id - val contractAddress = convertTokenAddress( - blockchain = networkSelectorValue, - address = uiState.value.form.contractAddressInputField.value, - ) ?: return false // invalid address can't be "already added" - - return currentCryptoCurrencies - .filterIsInstance() - .any { token -> - val sameId = if (!token.isCustom) { - // todo after move foundToken to CryptoCurrency model, use only id - foundToken?.id == token.id.rawCurrencyId - } else { - true - } - - val sameAddress = contractAddress.equals(token.contractAddress, ignoreCase = true) - val sameBlockchain = networkId == token.network.id.value - val isSameDerivationPath = getDerivationPath()?.rawPath == token.network.derivationPath.value - - sameId && sameAddress && sameBlockchain && isSameDerivationPath - } - } - - private fun isBlockchainAlreadyAdded(): Boolean { - return currentCryptoCurrencies - .filterIsInstance() - .any { coin -> - coin.network.id.value == uiState.value.form.networkSelectorField.selectedItem.blockchain.id && - coin.network.derivationPath.value == getDerivationPath()?.rawPath - } - } - - private fun handleContractAddressErrorValidation(type: AddCustomTokenError) { - when { - isNetworkSelected() && type == AddCustomTokenError.INVALID_CONTRACT_ADDRESS -> { - val isAnotherTokenFieldsFilled = isAnyTokenFieldsFilled() - uiState.update { state -> - state.copySealed( - form = state.form.copy( - contractAddressInputField = state.form.contractAddressInputField.copy( - isError = true, - error = TextReference.Res( - id = R.string.custom_token_creation_error_invalid_contract_address, - ), - ), - tokenNameInputField = state.form.tokenNameInputField.copy( - isEnabled = isAnotherTokenFieldsFilled, - ), - tokenSymbolInputField = state.form.tokenSymbolInputField.copy( - isEnabled = isAnotherTokenFieldsFilled, - ), - decimalsInputField = state.form.decimalsInputField.copy( - isEnabled = isAnotherTokenFieldsFilled, - ), - ), - ) - } - } - - !isNetworkSelected() || type == AddCustomTokenError.FIELD_IS_EMPTY -> { - uiState.update { state -> - state.copySealed( - form = state.form.copy( - contractAddressInputField = state.form.contractAddressInputField.copy(isError = false), - tokenNameInputField = state.form.tokenNameInputField.copy(value = "", isEnabled = false), - tokenSymbolInputField = state.form.tokenSymbolInputField.copy( - value = "", - isEnabled = false, - ), - decimalsInputField = state.form.decimalsInputField.copy(value = "", isEnabled = false), - ), - ) - } - } - - else -> Unit - } - } - - private fun getDerivationPath(): DerivationPath? { - return when (uiState.value.form.derivationPathSelectorField?.selectedItem?.type) { - DerivationPathSelectorType.CUSTOM -> - createDerivationPathOrNull(uiState.value.form.derivationPathInputField?.value ?: "") - else -> - getDerivationPathForBlockchain(uiState.value.form.derivationPathSelectorField?.selectedItem?.blockchain) - } - } - - private fun createDerivationPathOrNull(rawPath: String): DerivationPath? { - return try { - DerivationPath(rawPath) - } catch (error: Throwable) { - null - } - } - - private fun getDerivationPathForBlockchain(blockchain: Blockchain?): DerivationPath? { - if (blockchain == null) return null - - val derivationStyle = getSelectedWalletSyncUseCase().fold( - ifLeft = { null }, - ifRight = { - it.scanResponse.derivationStyleProvider.getDerivationStyle() - }, - ) - - val derivationNetwork = if (blockchain == Blockchain.Unknown) { - uiState.value.form.networkSelectorField.selectedItem.blockchain - } else { - blockchain - } - return derivationNetwork.derivationPath(derivationStyle) - } - - private fun getSupportBlockchainType(blockchain: Blockchain): SupportBlockchainType { - return getSelectedWalletSyncUseCase().fold( - ifLeft = { SupportBlockchainType.UNABLE_TO_DETERMINE }, - ifRight = { - val canHandleBlockchain = it.scanResponse.card.canHandleBlockchain( - blockchain = blockchain, - cardTypesResolver = it.scanResponse.cardTypesResolver, - ) - if (canHandleBlockchain) { - SupportBlockchainType.SUPPORTED - } else { - SupportBlockchainType.UNSUPPORTED - } - }, - ) - } - - private inner class ActionsHandler(private val featureRouter: CustomTokenRouter) { - - fun onBackButtonClick() { - viewModelScope.launch(dispatchers.main) { - // need delay before close, cause crashed in compose PopUpMenu as - delay(timeMillis = 100) - featureRouter.popBackStack() - } - } - - fun onContactAddressValueChange(enteredValue: String) { - uiState.update { state -> - state.copySealed( - form = state.form.copy( - contractAddressInputField = state.form.contractAddressInputField.copy(value = enteredValue), - ), - ) - } - - val selectedNetwork = uiState.value.form.networkSelectorField.selectedItem.blockchain - val validatorResult = ContractAddressValidator.validate( - address = enteredValue, - blockchain = selectedNetwork, - ) - - when (validatorResult) { - is ContractAddressValidatorResult.Success -> { - uiState.update { state -> - state.copySealed( - form = state.form.copy( - contractAddressInputField = state.form.contractAddressInputField.copy( - isError = false, - isLoading = true, - ), - ), - ) - } - updateForm(address = enteredValue, selectedNetwork = selectedNetwork) - } - - is ContractAddressValidatorResult.Error -> { - handleContractAddressErrorValidation(type = validatorResult.type) - updateWarnings() - updateFloatingButton() - } - } - } - - fun onNetworkSelectorItemClick(index: Int) { - val state = uiState.updateAndGet { state -> - val selectedItem = requireNotNull(state.form.networkSelectorField.items.getOrNull(index)) - state.copySealed( - form = state.form.copy( - networkSelectorField = state.form.networkSelectorField.copy( - selectedItem = selectedItem, - ), - showTokenFields = selectedItem.blockchain.canHandleTokens() && - // workaround cause in Terra we support only 1 token - selectedItem.blockchain != Blockchain.TerraV1, - ), - ) - } - - onContactAddressValueChange(state.form.contractAddressInputField.value) - } - - fun onTokenNameValueChange(enteredValue: String) { - uiState.update { state -> - state.copySealed( - form = state.form.copy( - tokenNameInputField = state.form.tokenNameInputField.copy(value = enteredValue), - ), - ) - } - - updateFloatingButton() - } - - fun onTokenSymbolValueChange(enteredValue: String) { - uiState.update { state -> - state.copySealed( - form = state.form.copy( - tokenSymbolInputField = state.form.tokenSymbolInputField.copy(value = enteredValue), - ), - ) - } - updateFloatingButton() - } - - fun onDecimalsValueChange(enteredValue: String) { - uiState.update { state -> - state.copySealed( - form = state.form.copy( - decimalsInputField = state.form.decimalsInputField.copy(value = enteredValue), - ), - ) - } - updateFloatingButton() - } - - fun onDerivationPathSelectorItemClick(index: Int) { - uiState.update { state -> - val derivationSelector = requireNotNull(state.form.derivationPathSelectorField) - val selected = requireNotNull(derivationSelector.items.getOrNull(index)) - val derivationInputField = requireNotNull(state.form.derivationPathInputField) - - state.copySealed( - form = state.form.copy( - derivationPathSelectorField = derivationSelector.copy( - selectedItem = selected, - ), - derivationPathInputField = derivationInputField.copy( - showField = selected.type == DerivationPathSelectorType.CUSTOM, - ), - ), - ) - } - - updateFloatingButton() - } - - fun onDerivationPathValueChange(enteredValue: String) { - uiState.update { state -> - state.copySealed( - form = state.form.copy( - derivationPathInputField = state.form.derivationPathInputField?.copy(value = enteredValue), - ), - ) - } - updateFloatingButton() - } - - fun onAddCustomTokenClick() { - if (!isNetworkSelected()) return - val blockchain = uiState.value.form.networkSelectorField.selectedItem.blockchain - when (getSupportBlockchainType(blockchain)) { - SupportBlockchainType.SUPPORTED -> { - /* no-op */ - } - SupportBlockchainType.UNSUPPORTED -> { - featureRouter.openUnsupportedNetworkAlert(blockchain) - return - } - SupportBlockchainType.UNABLE_TO_DETERMINE -> { - featureRouter.showGenericErrorAlertAndPopBack() - return - } - } - - val currency = when (getCustomTokenType()) { - CustomTokenType.TOKEN -> { - val contractAddress = convertTokenAddress( - blockchain = blockchain, - address = foundToken?.network?.contractAddress ?: uiState.value.form.contractAddressInputField - .value, - ) ?: error("Contract address is invalid") // impossible to add a token with invalid address - - CustomCurrency.CustomToken( - token = Token( - name = uiState.value.form.tokenNameInputField.value, - symbol = uiState.value.form.tokenSymbolInputField.value, - contractAddress = contractAddress, - decimals = requireNotNull(uiState.value.form.decimalsInputField.value.toIntOrNull()), - id = foundToken?.id, - ), - network = blockchain, - derivationPath = getDerivationPath(), - ) - } - CustomTokenType.BLOCKCHAIN -> { - CustomCurrency.CustomBlockchain( - network = blockchain, - derivationPath = getDerivationPath(), - ) - } - } - - analyticsSender.sendWhenAddTokenButtonClicked(currency) - - viewModelScope.launch { - uiState.update { state -> - state.copySealed( - floatingButton = state.floatingButton.copy( - isEnabled = false, - showProgress = true, - ), - ) - } - - val result = featureInteractor.saveToken(currency) - - uiState.update { state -> - state.copySealed( - floatingButton = state.floatingButton.copy( - isEnabled = true, - showProgress = false, - ), - ) - } - - result - .onSuccess { featureRouter.openWalletScreen() } - .onFailure { Timber.e(it, "Unable to save custom token") } - } - } - } - - /** Convert [address] to single address for specific [blockchain] or return null if invalid */ - private fun convertTokenAddress(blockchain: Blockchain, address: String): String? { - return runCatching { - when (blockchain) { - Blockchain.Hedera, Blockchain.HederaTestnet -> hederaAddressConverter.convertToTokenId(address) - Blockchain.Cardano -> { - // TODO: [REDACTED_JIRA] - cardanoTokenAddressConverter.convertToFingerprint( - address = address, - symbol = uiState.value.form.tokenSymbolInputField.value, - ) - } - else -> address - } - } - .getOrNull() - } - - private inner class TestActionsHandler { - - fun onClearAddressButtonClick() { - uiState.update { state -> - state.copySealed( - form = state.form.copy( - contractAddressInputField = state.form.contractAddressInputField.copy( - value = "", - isLoading = false, - isError = false, - error = null, - ), - ), - ) - } - } - - fun onResetButtonClick() { - uiState.update { state -> - with(state.form) { - state.copySealed( - form = state.form.copy( - contractAddressInputField = contractAddressInputField.copy( - value = "", - isLoading = false, - isError = false, - error = null, - ), - networkSelectorField = networkSelectorField.copy( - selectedItem = formStateBuilder.createNetworkSelectorItem( - blockchain = Blockchain.Unknown, - ), - ), - tokenNameInputField = tokenNameInputField.copy(value = "", isEnabled = false), - tokenSymbolInputField = tokenSymbolInputField.copy(value = "", isEnabled = false), - decimalsInputField = decimalsInputField.copy(value = "", isEnabled = false), - derivationPathSelectorField = derivationPathSelectorField?.copy( - isEnabled = true, - selectedItem = formStateBuilder.createDerivationPathSelectorAdditionalItem( - blockchain = Blockchain.Unknown, - type = DerivationPathSelectorType.DEFAULT, - derivationPath = "", - ), - ), - ), - ) - } - } - } - } - - private companion object { - val COMMON_TOKENS = persistentListOf( - TestTokenItem(name = "USDC on ETH", address = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), - TestTokenItem(name = "BUSD on ETH", address = "0x4fabb145d64652a948d72533023f6e7a623c7c53"), - TestTokenItem(name = "ETH on AVALANCHE", address = "0xf20d962a6c8f70c731bd838a3a388d7d48fa6e15"), - TestTokenItem(name = "USDC on ETH (invalid - cut address)", address = "0xa0b86991c6218b36c1d1"), - TestTokenItem(name = "Custom EVM", address = "0x1111111111111111112111111111111111111113"), - TestTokenItem( - name = "Supported by several networks", - address = "0xa1faa113cbe53436df28ff0aee54275c13b40975", - ), - TestTokenItem(name = "Invalid", address = "!@#_ _-%%^&&*((){P P2iOWsdfFQLA"), - ) - - val SOLANA_TOKENS = persistentListOf( - TestTokenItem(name = "USDT (full)", address = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"), - TestTokenItem( - name = "USDT (valid - 2/3 of address)", - address = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8Ben", - ), - TestTokenItem(name = "USDT (invalid - 1/3 of address)", address = "Es9vMFrzaCERmJ"), - TestTokenItem(name = "ETH (full)", address = "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk"), - ) - const val DERIVATION_PATH_PLACEHOLDER = "m/44'/0'/0'/0/0" - const val DECIMALS_PLACEHOLDER = "8" - const val CONTRACT_ADDRESS_PLACEHOLDER = "0x0000000000000000000000000000000000000000" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 10bb1b383d..53844799aa 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.details.redux import androidx.lifecycle.LifecycleCoroutineScope -import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse @@ -14,15 +13,6 @@ sealed class DetailsAction : Action { val shouldSaveUserWallets: Boolean, ) : DetailsAction() - data object ScanAndSaveUserWallet : DetailsAction() { - - data object Success : DetailsAction() - - data class Error(val error: TextReference?) : DetailsAction() - } - - data object DismissError : DetailsAction() - sealed class AppSettings : DetailsAction() { data class SwitchPrivacySetting( val enable: Boolean, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 40f3d03a11..713ffe5c9a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -2,36 +2,24 @@ package com.tangem.tap.features.details.redux import androidx.lifecycle.LifecycleCoroutineScope import com.tangem.common.CompletionResult -import com.tangem.common.core.TangemError -import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.AppRouter -import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.Analytics -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.wallets.builder.UserWalletBuilder -import com.tangem.domain.wallets.legacy.UserWalletsListError -import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.inject -import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow @@ -41,7 +29,6 @@ import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware import timber.log.Timber -import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam class DetailsMiddleware { private val appSettingsMiddleware = AppSettingsMiddleware() @@ -62,7 +49,6 @@ class DetailsMiddleware { private fun handleAction(state: DetailsState, action: Action) { when (action) { is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, action) - is DetailsAction.ScanAndSaveUserWallet -> scanAndSaveUserWallet() } } @@ -278,95 +264,4 @@ class DetailsMiddleware { } } } - - private fun scanAndSaveUserWallet() = scope.launch(Dispatchers.IO) { - val cardSdkConfigRepository = store.inject(DaggerGraphState::cardSdkConfigRepository) - - val prevUseBiometricsForAccessCode = cardSdkConfigRepository.isBiometricsRequestPolicy() - - // Update access code policy for access code saving when a card was scanned - val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() - cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = shouldSaveAccessCodes) - - store.inject(DaggerGraphState::scanCardProcessor).scan( - analyticsSource = CoreAnalyticsParam.ScreensSources.Settings, - onWalletNotCreated = { - // No need to rollback policy, continue with the policy set before the card scan - store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success) - }, - disclaimerWillShow = { - store.dispatchNavigationAction(AppRouter::pop) - }, - onSuccess = { scanResponse -> - createUserWallet(scanResponse) - .doOnSuccess { - saveUserWalletAndPopBackToWalletScreen( - userWallet = it, - prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode, - ) - } - .doOnFailure { error -> - Timber.e(error, "Unable to create user wallet") - handleError(error = error, prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode) - } - }, - onFailure = { error -> - Timber.e(error, "Unable to scan card") - handleError(error = error, prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode) - }, - ) - } - - private suspend fun createUserWallet(scanResponse: ScanResponse): CompletionResult { - 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() } - } - .doOnFailure { error -> - if (error is UserWalletsListError.WalletAlreadySaved) { - userWalletsListManager.select(userWallet.walletId) - store.onUserWalletSelected(userWallet) - - store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success) - store.dispatchNavigationAction { popTo() } - } 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) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index dbb450b426..bfe8750b2a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.details.redux import com.tangem.domain.apptheme.model.AppThemeMode -import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.AppState import com.tangem.tap.proxy.redux.DaggerGraphState @@ -21,7 +20,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { val detailsState = state.detailsState return when (action) { is DetailsAction.PrepareScreen -> { - handlePrepareScreen(action, state) + handlePrepareScreen(action) } is DetailsAction.AppSettings -> { handlePrivacyAction(action, detailsState) @@ -31,26 +30,12 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { selectedAppCurrency = action.currency, ), ) - is DetailsAction.ScanAndSaveUserWallet -> detailsState.copy( - isScanningInProgress = true, - ) - is DetailsAction.ScanAndSaveUserWallet.Error -> detailsState.copy( - isScanningInProgress = false, - error = action.error, - ) - is DetailsAction.ScanAndSaveUserWallet.Success -> detailsState.copy( - isScanningInProgress = false, - ) - is DetailsAction.DismissError -> detailsState.copy( - error = null, - ) } } -private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppState): DetailsState { +private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsState { return DetailsState( scanResponse = action.scanResponse, - createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appSettingsState = AppSettingsState( isBiometricsAvailable = runBlocking { tangemSdkManager.checkCanUseBiometry() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index c35d0c8d83..bc7f17a7b6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -1,27 +1,16 @@ package com.tangem.tap.features.details.redux -import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse import org.rekotlin.StateType data class DetailsState( + @Deprecated("Delete after onboarding refactoring") val scanResponse: ScanResponse? = null, - val privacyPolicyUrl: String? = null, - val createBackupAllowed: Boolean = false, - val isScanningInProgress: Boolean = false, - val error: TextReference? = null, val appSettingsState: AppSettingsState = AppSettingsState(), ) : StateType -sealed class ResetCardDialog { - data object StartResetDialog : ResetCardDialog() - data object ContinueResetDialog : ResetCardDialog() - data object InterruptedResetDialog : ResetCardDialog() - data object CompletedResetDialog : ResetCardDialog() -} - data class AppSettingsState( val saveWallets: Boolean = false, val saveAccessCodes: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt index 34a693dbb4..55d16d7d80 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt @@ -11,7 +11,6 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.settings.CanUseBiometryUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.repository.WalletsRepository -import com.tangem.features.details.DetailsFeatureToggles import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchNavigationAction @@ -45,7 +44,6 @@ internal class AppSettingsViewModel @Inject constructor( private val appThemeModeRepository: AppThemeModeRepository, private val settingsRepository: SettingsRepository, private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender, - private val detailsFeatureToggles: DetailsFeatureToggles, ) : ViewModel(), StoreSubscriber, DefaultLifecycleObserver { @@ -62,9 +60,7 @@ internal class AppSettingsViewModel @Inject constructor( init { bootstrapAppCurrencyUpdates() - if (detailsFeatureToggles.isRedesignEnabled) { - bootstrapBiometricsUpdates() - } + bootstrapBiometricsUpdates() subscribeToStoreChanges() sendItemsAnalytics() diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt deleted file mode 100644 index 5b41307402..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt +++ /dev/null @@ -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 { - - @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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt deleted file mode 100644 index f31718287c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt +++ /dev/null @@ -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) { - 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, 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) { - 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( - 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 \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt deleted file mode 100644 index acafefe8e7..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ /dev/null @@ -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, - val tangemLinks: ImmutableList, - val tangemVersion: String, - val showSnackbar: StateEvent, - 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 = 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 = 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"), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt deleted file mode 100644 index d2c84a43db..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ /dev/null @@ -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, - private val walletsRepository: WalletsRepository, - private val userWalletsListManager: UserWalletsListManager, -) { - - var detailsScreenState: MutableState = 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 { - 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 { - 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 { - 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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardDialog.kt new file mode 100644 index 0000000000..c6efa99d33 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardDialog.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index fefbdf122e..ebcdf702a9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -22,7 +22,6 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.onUserWalletSelected -import com.tangem.tap.features.details.redux.ResetCardDialog import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription import com.tangem.tap.store diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt index 9cda07b754..3524b38f40 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt @@ -24,12 +24,6 @@ abstract class BaseDisclaimer( override suspend fun isAccepted(): Boolean = dataProvider.isAccepted() } -class DummyDisclaimer : Disclaimer { - override fun getUri(): Uri = Uri.parse("https://tangem.com/tangem_tos.html") - override suspend fun accept() {} - override suspend fun isAccepted(): Boolean = false -} - class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) { override fun getUri(): Uri = Uri.parse("$baseUrl/tangem_tos.html") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt deleted file mode 100644 index 9611dc80b5..0000000000 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt +++ /dev/null @@ -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() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt deleted file mode 100644 index 60ffa51781..0000000000 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt +++ /dev/null @@ -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 = { 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() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt deleted file mode 100644 index 9b655697ba..0000000000 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt +++ /dev/null @@ -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, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerSource.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerSource.kt deleted file mode 100644 index cdfcbbdb90..0000000000 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerSource.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.tap.features.disclaimer.redux - -enum class DisclaimerSource { - Home, Details -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt deleted file mode 100644 index c5ccfc7cfc..0000000000 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt +++ /dev/null @@ -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, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt deleted file mode 100644 index 9d217e6111..0000000000 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ /dev/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 { - - 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") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt deleted file mode 100644 index c64d0b7543..0000000000 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerWebViewClient.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/LocalTermOfServices.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/LocalTermOfServices.kt deleted file mode 100644 index 6651d0fa75..0000000000 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/LocalTermOfServices.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.tangem.tap.features.disclaimer.ui - -internal val localTermsOfServices = """ - - - - - - - Legal Disclaimer - - -

Legal Disclaimer

-

    1. Tangem application (Software)

-

    The Software is intended for usage only with Tangem hardware wallets (Cards) via NFC interface. The - Software DOES NOT:

-

    a) Generate, store, transmit, or have access to private (secret) cryptographic keys to blockchain - wallets holding digital assets, including crypto-currency.

-

    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.

-

    c) Provide exchange, trading, investment services on behalf of Tangem AG.

-

    2. Risks related to the use of Software

-

    Tangem will not be responsible for any losses, damages or claims arising from events falling within the - scope of the following five categories:

-

    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.

-

    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.

-

    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.

-

    d) Security problems experienced by the user of any cryptocurrency-related software or service, e.g., - unauthorized access to users' wallets and/or accounts.

-

    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.

-

    3. Trading and Investment risks

-

    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.

-

    4. Electronic Trading Risks

-

    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.

-

    5. Compliance with tax obligations        

-

    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.        

-

    6. No warranties        

-

    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.       -  

-

    7. Limitation of liability

-

    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.

-

    8. Last amendment

-

    This disclaimer was amended for the last time on October 1st, 2020.

- -""".trimIndent() \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index 9f4e9fbaa9..f33229cf24 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -58,7 +58,9 @@ internal fun StoriesScreen( } StoriesScreenContent( - modifier = Modifier.fillMaxSize().testTag(TestTags.STORIES_SCREEN), + modifier = Modifier + .fillMaxSize() + .testTag(TestTags.STORIES_SCREEN), config = StoriesScreenContentConfig( storiesSize = state.stories.lastIndex, currentStoryIndex = currentStoryIndex, @@ -157,7 +159,7 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M duration = currentStoryDuration, ) Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet() - is Stories.UltraSecureBackup -> StoriesUltraSecureBackup( + Stories.UltraSecureBackup -> StoriesUltraSecureBackup( isPaused = isPaused, stepDuration = currentStoryDuration, ) diff --git a/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt new file mode 100644 index 0000000000..ce8c0bcec3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.features.home.featuretoggles + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import javax.inject.Inject + +class HomeFeatureToggles @Inject constructor( + private val featureTogglesManager: FeatureTogglesManager, +) { + + val isMigrateUserCountryCodeEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "MIGRATE_USER_COUNTRY_CODE_ENABLED") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index e3f0375cc1..05ba17df63 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -15,6 +15,4 @@ sealed class HomeAction : Action { data class ReadCard(val scope: CoroutineScope) : HomeAction() data class ScanInProgress(val scanInProgress: Boolean) : HomeAction() - - data class UpdateCountryCode(val userCountryCode: String) : HomeAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index f9d977fdec..ab41e3d89f 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -10,16 +10,10 @@ object HomeReducer { private fun internalReduce(action: Action, appState: AppState): HomeState { if (action !is HomeAction) return appState.homeState - var state = appState.homeState - when (action) { + return when (action) { is HomeAction.ScanInProgress -> { - state = state.copy(scanInProgress = action.scanInProgress) + appState.homeState.copy(scanInProgress = action.scanInProgress) } - is HomeAction.UpdateCountryCode -> { - state.onCountryCodeUpdate(state, action.userCountryCode) - } - else -> {} + else -> appState.homeState } - - return state } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt index 2e584d3d35..5341631157 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -1,56 +1,24 @@ package com.tangem.tap.features.home.redux -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import org.rekotlin.StateType -import java.util.Locale -@Immutable data class HomeState( val scanInProgress: Boolean = false, - val stories: List = initDefaultStories(), + val stories: ImmutableList = Stories.entries.toImmutableList(), ) : StateType { - val firstStory: Stories - get() = stories[0] + val firstStory: Stories get() = stories[0] fun stepOf(story: Stories): Int = stories.indexOf(story) - - fun onCountryCodeUpdate(homeState: HomeState, countryCode: String) { - val isNewWalletAvailable = !(countryCode == RUSSIA_COUNTRY_CODE || countryCode == BELARUS_COUNTRY_CODE) - homeState.stories.forEach { - it.isNewWalletAvailable.value = isNewWalletAvailable - } - } - - companion object { - private const val RUSSIA_COUNTRY_CODE = "ru" - private const val BELARUS_COUNTRY_CODE = "by" - fun initDefaultStories(): List = listOf( - Stories.TangemIntro, - Stories.RevolutionaryWallet, - Stories.UltraSecureBackup, - Stories.Currencies, - Stories.Web3, - Stories.WalletForEveryone, - ) - - fun isNewWalletAvailableInit(): Boolean { - val locale = Locale.getDefault().language - return !(locale == RUSSIA_COUNTRY_CODE || locale == BELARUS_COUNTRY_CODE) - } - } } -sealed class Stories( - val duration: Int, - val isNewWalletAvailable: MutableState = mutableStateOf(HomeState.isNewWalletAvailableInit()), -) { - object TangemIntro : Stories(duration = 6000) - object RevolutionaryWallet : Stories(duration = 6000) - object UltraSecureBackup : Stories(duration = 6000) - object Currencies : Stories(duration = 6000) - object Web3 : Stories(duration = 6000) - object WalletForEveryone : Stories(duration = 6000) +enum class Stories(val duration: Int = 6000) { + TangemIntro, + RevolutionaryWallet, + UltraSecureBackup, + Currencies, + Web3, + WalletForEveryone, } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 6ba358cced..e9db3ee67e 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -13,16 +13,15 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase -import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase +import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase import com.tangem.domain.staking.FetchStakingTokensUseCase -import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.tap.common.extensions.setContext +import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles import com.tangem.tap.features.main.model.MainScreenState -import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* @@ -41,12 +40,12 @@ internal class MainViewModel @Inject constructor( private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase, private val blockchainSDKFactory: BlockchainSDKFactory, private val userWalletsListManager: UserWalletsListManager, - private val walletManagersFacade: WalletManagersFacade, - private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, stakingFeatureToggles: StakingFeatureToggles, private val fetchStakingTokensUseCase: FetchStakingTokensUseCase, private val apiConfigsManager: ApiConfigsManager, + homeFeatureToggles: HomeFeatureToggles, + private val fetchUserCountryUseCase: FetchUserCountryUseCase, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel(), MainIntents { @@ -67,6 +66,14 @@ internal class MainViewModel @Inject constructor( viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() } + if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) { + viewModelScope.launch { + fetchUserCountryUseCase().onLeft { + Timber.e("Unable to fetch the user country code $it") + } + } + } + updateAppCurrencies() observeFlips() displayBalancesHidingStatusToast() @@ -97,19 +104,6 @@ internal class MainViewModel @Inject constructor( .onEach { userWallet -> Analytics.setContext(userWallet.scanResponse) 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) .launchIn(viewModelScope) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index bc085c6dca..130eddc672 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -1,22 +1,31 @@ package com.tangem.tap.features.onboarding +import com.tangem.blockchain.common.WalletManager import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.Basic +import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.redux.AppDialog +import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.features.demo.DemoHelper +import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.mainScope import com.tangem.tap.proxy.redux.DaggerGraphState @@ -138,6 +147,39 @@ object OnboardingHelper { } } + fun handleTopUpAction(walletManager: WalletManager, scanResponse: ScanResponse, globalState: GlobalState) { + val blockchain = walletManager.wallet.blockchain + val cryptoCurrency = CryptoCurrencyFactory().createCoin( + blockchain = blockchain, + extraDerivationPath = null, + derivationStyleProvider = scanResponse.derivationStyleProvider, + ) ?: return + + val topUpUrl = walletManager.getTopUpUrl(cryptoCurrency) ?: return + + val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain) + Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType)) + + scope.launch { + val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles) + + val isRussia = if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) { + val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase) + + getUserCountryCodeUseCase().isRight { it is UserCountry.Russia } + } else { + globalState.userCountryCode == RUSSIA_COUNTRY_CODE + } + + if (isRussia) { + val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl) + store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData)) + } else { + store.dispatchOpenUrl(topUpUrl) + } + } + } + private suspend fun proceedWithScanResponse( scanResponse: ScanResponse, backupCardsIds: List?, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt index d37b7153d1..a0db9b19f8 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -11,7 +11,6 @@ import com.tangem.operations.attestation.CardVerifyAndGetInfo import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.common.entities.ProgressState import com.tangem.tap.common.extensions.inject -import com.tangem.tap.common.extensions.isPositive import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl @@ -20,6 +19,7 @@ import com.tangem.tap.domain.model.hasPendingTransactions import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store +import com.tangem.utils.extensions.isPositive import timber.log.Timber import java.math.BigDecimal diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt index 89ffc2161c..c619d4bd17 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt @@ -7,12 +7,15 @@ import androidx.core.view.MenuProvider import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic +import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.common.feedback.SupportInfo -import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.extensions.inject +import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.utils.Provider import com.tangem.wallet.R +import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] @@ -28,13 +31,15 @@ class OnboardingMenuProvider( override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) { R.id.menu_item_chat_support -> { Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) - // changed on email support [REDACTED_TASK_KEY] - store.dispatch( - GlobalAction.SendEmail( - feedbackData = SupportInfo(), - scanResponse = scanResponseProvider(), - ), - ) + + val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponseProvider()).getOrNull() + ?: error("CardInfo must be not null") + + scope.launch { + store.inject(DaggerGraphState::sendFeedbackEmailUseCase) + .invoke(type = FeedbackEmailType.DirectUserRequest(cardInfo)) + } + true } else -> false diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt index 757ec91454..97746d2d70 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -14,7 +14,6 @@ import com.tangem.core.navigation.ShareElement import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.getDrawableCompat -import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget import com.tangem.tap.common.transitions.InternalNoteLayoutTransition import com.tangem.tap.features.addBackPressHandler @@ -24,6 +23,7 @@ import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteActi import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteStep import com.tangem.tap.store +import com.tangem.utils.extensions.stripZeroPlainString import com.tangem.wallet.R /** diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 52af2f7d66..d47862a648 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -4,12 +4,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics - -import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.domain.common.extensions.makePrimaryWalletManager import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.entities.ProgressState import com.tangem.tap.common.extensions.* @@ -20,7 +16,6 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.model.Currency import com.tangem.tap.features.demo.DemoHelper -import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.mainScope @@ -178,23 +173,11 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch return } - val blockchain = walletManager.wallet.blockchain - val cryptoCurrency = CryptoCurrencyFactory().createCoin( - blockchain, - null, - scanResponse.derivationStyleProvider, - ) ?: return - val topUpUrl = walletManager.getTopUpUrl(cryptoCurrency) ?: return - - val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain) - Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType)) - - if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) { - val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl) - store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData)) - } else { - store.dispatchOpenUrl(topUpUrl) - } + OnboardingHelper.handleTopUpAction( + walletManager = walletManager, + scanResponse = scanResponse, + globalState = globalState, + ) } is OnboardingNoteAction.Done -> { store.dispatch(GlobalAction.Onboarding.Stop) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 047a5caaa8..ef29db32d4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -6,16 +6,12 @@ import com.tangem.common.extensions.guard import com.tangem.common.routing.AppRoute import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.Analytics - -import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.domain.common.extensions.makePrimaryWalletManager import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.legacy.asLockable -import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.entities.ProgressState import com.tangem.tap.common.extensions.* @@ -26,7 +22,6 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.model.Currency import com.tangem.tap.domain.twins.TwinCardsManager -import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.mainScope @@ -301,24 +296,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) { return } - val scanResponse = onboardingManager?.scanResponse ?: return - val blockchain = walletManager.wallet.blockchain - val cryptoCurrency = CryptoCurrencyFactory().createCoin( - blockchain, - null, - scanResponse.derivationStyleProvider, - ) ?: return - val topUpUrl = walletManager.getTopUpUrl(cryptoCurrency) ?: return - - val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain) - Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType)) - - if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) { - val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl) - store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData)) - } else { - store.dispatchOpenUrl(topUpUrl) - } + OnboardingHelper.handleTopUpAction( + walletManager = walletManager, + scanResponse = onboardingManager?.scanResponse ?: return, + globalState = globalState, + ) } TwinCardsAction.Done -> { val scanResponse = getScanResponse() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt index 76880fb5e1..f058c94f3b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt @@ -21,7 +21,10 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.models.Artwork import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.analytics.events.Onboarding -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.beginDelayedTransition +import com.tangem.tap.common.extensions.getDrawableCompat +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget import com.tangem.tap.common.transitions.InternalNoteLayoutTransition import com.tangem.tap.domain.twins.TwinsCardWidget @@ -35,6 +38,7 @@ import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep import com.tangem.tap.store import com.tangem.utils.Provider +import com.tangem.utils.extensions.stripZeroPlainString import com.tangem.wallet.R import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding import dagger.hilt.android.AndroidEntryPoint diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index fb9acfefd5..6ffddd4a21 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -28,6 +28,7 @@ import com.tangem.core.analytics.models.Basic import com.tangem.core.ui.extensions.setStatusBarColor import com.tangem.datasource.utils.isNullOrEmpty import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator @@ -37,8 +38,6 @@ import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.* -import com.tangem.tap.common.feedback.SupportInfo -import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.BaseFragment import com.tangem.tap.features.FragmentOnBackPressedHandler import com.tangem.tap.features.addBackPressHandler @@ -46,6 +45,8 @@ import com.tangem.tap.features.onboarding.OnboardingMenuProvider import com.tangem.tap.features.onboarding.products.wallet.redux.* import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog import com.tangem.tap.mainScope +import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.utils.Provider import com.tangem.wallet.R @@ -510,14 +511,16 @@ class OnboardingWalletFragment : onBack = ::legacyOnBackHandler, onOpenChat = { Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) - // changed on email support [REDACTED_TASK_KEY] - store.dispatch( - GlobalAction.SendEmail( - feedbackData = SupportInfo(), - scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse - ?: error("ScanResponse must be not null"), - ), - ) + + val scanResponse = requireNotNull(store.state.globalState.onboardingState.onboardingManager?.scanResponse) + + val getCardInfoUseCase = store.inject(DaggerGraphState::getCardInfoUseCase) + val cardInfo = requireNotNull(getCardInfoUseCase.invoke(scanResponse).getOrNull()) + + scope.launch { + store.inject(DaggerGraphState::sendFeedbackEmailUseCase) + .invoke(type = FeedbackEmailType.DirectUserRequest(cardInfo)) + } }, onOpenUriClick = { uri -> store.dispatchOpenUrl(uri.toString()) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt index 45c8213362..aa0bf35450 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt @@ -6,12 +6,15 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic +import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.tap.common.extensions.dispatchDialogHide -import com.tangem.tap.common.feedback.SupportInfo -import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.extensions.inject import com.tangem.tap.features.onboarding.OnboardingDialog +import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.wallet.R +import kotlinx.coroutines.launch object WalletActivationErrorDialog { @@ -23,13 +26,17 @@ object WalletActivationErrorDialog { setNegativeButton(R.string.common_support) { _, _ -> // changed on email support [REDACTED_TASK_KEY] Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) - store.dispatch( - GlobalAction.SendEmail( - feedbackData = SupportInfo(), - scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse - ?: error("ScanResponse must be not null"), - ), - ) + + val scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse + ?: error("ScanResponse must be not null") + + val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull() + ?: error("CardInfo must be not null") + + scope.launch { + store.inject(DaggerGraphState::sendFeedbackEmailUseCase) + .invoke(type = FeedbackEmailType.DirectUserRequest(cardInfo)) + } } setOnDismissListener { store.dispatchDialogHide() } setCancelable(false) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/DefaultTokensListRepository.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/DefaultTokensListRepository.kt deleted file mode 100644 index ca1cf6190f..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/DefaultTokensListRepository.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.tangem.tap.features.tokens.impl.data - -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingData -import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.local.testnet.TestnetTokensStorage -import com.tangem.domain.common.TapWorkarounds.isTestCard -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.features.tokens.impl.domain.TokensListRepository -import com.tangem.tap.features.tokens.impl.domain.models.Token -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.Flow - -/** - * Default repository implementation of tokens list feature - * - * @property tangemTechApi Tangem Tech API - * @property dispatchers coroutine dispatchers provider - * @property getSelectedWalletSyncUseCase use case that returns selected wallet - * @property testnetTokensStorage storage for getting testnet tokens data - * -[REDACTED_AUTHOR] - */ -internal class DefaultTokensListRepository( - private val tangemTechApi: TangemTechApi, - private val dispatchers: CoroutineDispatcherProvider, - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - private val testnetTokensStorage: TestnetTokensStorage, -) : TokensListRepository { - - override fun getAvailableTokens(searchText: String?, needFilterExcluded: Boolean): Flow> { - return Pager( - config = PagingConfig( - pageSize = 100, - prefetchDistance = 70, - enablePlaceholders = false, - ), - pagingSourceFactory = { - val defaultSource = TangemApiTokensPagingSource( - api = tangemTechApi, - dispatchers = dispatchers, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - searchText = searchText, - needFilterExcluded = needFilterExcluded, - ) - - getSelectedWalletSyncUseCase().fold( - ifLeft = { defaultSource }, - ifRight = { - if (it.scanResponse.card.isTestCard) { - TestnetTokensPagingSource(testnetTokensStorage, searchText) - } else { - defaultSource - } - }, - ) - }, - ).flow - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt deleted file mode 100644 index dcacf9fef2..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TangemApiTokensPagingSource.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.tangem.tap.features.tokens.impl.data - -import androidx.paging.PagingSource -import androidx.paging.PagingState -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.isSupportedInApp -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.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.features.tokens.impl.data.converters.CoinsResponseConverter -import com.tangem.tap.features.tokens.impl.domain.models.Token -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.runCatching - -/** - * Paging source that get tokens by Tangem Tech API - * - * @property api Tangem Tech API - * @property dispatchers coroutine dispatchers provider - * @property getSelectedWalletSyncUseCase use case that returns selected wallet - * @property searchText search text - * @property needFilterExcluded filter networks that are not supported in the app - */ -internal class TangemApiTokensPagingSource( - private val api: TangemTechApi, - private val dispatchers: CoroutineDispatcherProvider, - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - private val searchText: String?, - private val needFilterExcluded: Boolean = false, -) : PagingSource() { - - private val coinsResponseConverter = CoinsResponseConverter(needFilterExcluded) - - private val allAvailableBlockchains by lazy { - Blockchain.entries - .filter { - it.isTestnet().not() && (needFilterExcluded.not() || it.isSupportedInApp()) - } - } - - override fun getRefreshKey(state: PagingState): Int? { - return state.anchorPosition?.let { anchorPosition -> - state.closestPageToPosition(anchorPosition)?.prevKey?.plus(other = 1) - ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(other = 1) - } - } - - override suspend fun load(params: LoadParams): LoadResult { - val page = params.key ?: 0 - - return runCatching(dispatchers.io) { - val supportedBlockchains = getSelectedWalletSyncUseCase().fold( - ifLeft = { allAvailableBlockchains }, - ifRight = { it.scanResponse.card.supportedBlockchains(it.scanResponse.cardTypesResolver) }, - ) - - api.getCoins( - networkIds = supportedBlockchains.joinToString(separator = ",", transform = Blockchain::toNetworkId), - active = true, - searchText = searchText, - offset = page * params.loadSize, - limit = params.loadSize, - ).getOrThrow() - }.fold( - onSuccess = { response -> - LoadResult.Page( - data = coinsResponseConverter.convert(response), - prevKey = if (page == 0) null else page.minus(other = 1), - nextKey = if (response.coins.isEmpty()) null else page.plus(other = 1), - ) - }, - onFailure = { LoadResult.Error(it) }, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TestnetTokensPagingSource.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TestnetTokensPagingSource.kt deleted file mode 100644 index 9525c329ba..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/TestnetTokensPagingSource.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.tangem.tap.features.tokens.impl.data - -import androidx.paging.PagingSource -import androidx.paging.PagingState -import com.tangem.datasource.local.testnet.TestnetTokensStorage -import com.tangem.datasource.local.testnet.models.TestnetTokensConfig -import com.tangem.tap.features.tokens.impl.data.converters.TestnetTokensConfigConverter -import com.tangem.tap.features.tokens.impl.domain.models.Token - -/** - * Paging source that get tokens by testnet tokens config - * - * @property testnetTokensStorage testnet tokens config storage - * @property searchText search text - */ -internal class TestnetTokensPagingSource( - private val testnetTokensStorage: TestnetTokensStorage, - private val searchText: String?, -) : PagingSource() { - - override fun getRefreshKey(state: PagingState): Int? = null - - override suspend fun load(params: LoadParams): LoadResult { - return LoadResult.Page( - data = TestnetTokensConfigConverter.convert( - value = testnetTokensStorage.getConfig().searchByText(searchText), - ), - prevKey = null, - nextKey = null, - ) - } - - private fun TestnetTokensConfig.searchByText(searchText: String?): TestnetTokensConfig { - if (searchText.isNullOrBlank()) return this - - return copy( - tokens = tokens.filter { token -> - token.symbol.contains(other = searchText, ignoreCase = true) || - token.name.contains(other = searchText, ignoreCase = true) - }, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/CoinsResponseConverter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/CoinsResponseConverter.kt deleted file mode 100644 index 2d620d45e8..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/CoinsResponseConverter.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.tangem.tap.features.tokens.impl.data.converters - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.blockchainsdk.utils.isSupportedInApp -import com.tangem.datasource.api.tangemTech.models.CoinsResponse -import com.tangem.tap.features.tokens.impl.domain.models.Token -import com.tangem.utils.converter.Converter - -/** - * Converter from data model [CoinsResponse] to list of domain models [Token] - * -[REDACTED_AUTHOR] - */ -internal class CoinsResponseConverter(val needFilterExcluded: Boolean) : Converter> { - - override fun convert(value: CoinsResponse): List { - return value.coins.map { token -> - Token( - id = token.id, - name = token.name, - symbol = token.symbol, - iconUrl = getIconUrl(token.id, value.imageHost), - networks = token.networks.mapNotNull { network -> - val blockchain = Blockchain.fromNetworkId(network.networkId) ?: return@mapNotNull null - - if (needFilterExcluded && !blockchain.isSupportedInApp()) { - return@mapNotNull null - } - - // filter tokens, if contractAddress != null, assume that it is a token - if (network.contractAddress != null && - !blockchain.canHandleTokens() - ) { - return@mapNotNull null - } - - Token.Network( - id = network.networkId, - blockchain = blockchain, - address = network.contractAddress, - iconUrl = getIconUrl(network.networkId, value.imageHost), - decimalCount = network.decimalCount?.toInt(), - ) - }, - ) - }.filter { it.networks.isNotEmpty() } - } - - fun getIconUrl(id: String, imageHost: String? = null): String { - return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png" - } - - private companion object { - const val DEFAULT_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/TestnetTokensConfigConverter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/TestnetTokensConfigConverter.kt deleted file mode 100644 index 561c8c123a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/data/converters/TestnetTokensConfigConverter.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.tangem.tap.features.tokens.impl.data.converters - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.datasource.local.testnet.models.TestnetTokensConfig -import com.tangem.tap.features.tokens.impl.domain.models.Token -import com.tangem.utils.converter.Converter - -/** - * Converter from data model [TestnetTokensConfig] to list of domain models [Token] - * -[REDACTED_AUTHOR] - */ -internal object TestnetTokensConfigConverter : Converter> { - - private val coinsResponseConverter = CoinsResponseConverter(needFilterExcluded = false) - override fun convert(value: TestnetTokensConfig): List { - return value.tokens.map { token -> - Token( - id = token.id, - name = token.name, - symbol = token.symbol, - iconUrl = coinsResponseConverter.getIconUrl(token.id), - networks = token.networks?.mapNotNull { network -> - val blockchain = Blockchain.fromNetworkId(network.id) ?: return@mapNotNull null - - Token.Network( - id = network.id, - blockchain = blockchain, - address = network.address, - iconUrl = coinsResponseConverter.getIconUrl(network.id), - decimalCount = network.decimalCount, - ) - }.orEmpty(), - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListInteractorModule.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListInteractorModule.kt deleted file mode 100644 index 464420346b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListInteractorModule.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.tap.features.tokens.impl.di - -import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.local.testnet.TestnetTokensStorage -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.features.tokens.impl.data.DefaultTokensListRepository -import com.tangem.tap.features.tokens.impl.domain.DefaultTokensListInteractor -import com.tangem.tap.features.tokens.impl.domain.TokensListInteractor -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -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 TokensListInteractorModule { - - @Provides - @ViewModelScoped - fun provideTokensListInteractor( - tangemTechApi: TangemTechApi, - dispatchers: CoroutineDispatcherProvider, - getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - testnetTokensStorage: TestnetTokensStorage, - ): TokensListInteractor { - return DefaultTokensListInteractor( - repository = DefaultTokensListRepository( - tangemTechApi = tangemTechApi, - dispatchers = dispatchers, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - testnetTokensStorage = testnetTokensStorage, - ), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRepositoryModule.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRepositoryModule.kt deleted file mode 100644 index 186545138b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRepositoryModule.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.tap.features.tokens.impl.di - -import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.local.testnet.TestnetTokensStorage -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.features.tokens.impl.data.DefaultTokensListRepository -import com.tangem.tap.features.tokens.impl.domain.TokensListRepository -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -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 TokensListRepositoryModule { - - @Provides - @Singleton - fun providesTokensListRepository( - tangemTechApi: TangemTechApi, - dispatchers: CoroutineDispatcherProvider, - getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - testnetTokensStorage: TestnetTokensStorage, - ): TokensListRepository { - return DefaultTokensListRepository( - tangemTechApi = tangemTechApi, - dispatchers = dispatchers, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - testnetTokensStorage = testnetTokensStorage, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt deleted file mode 100644 index 2a7da5cca4..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.tap.features.tokens.impl.di - -import com.tangem.tap.features.tokens.impl.presentation.router.DefaultTokensListRouter -import com.tangem.tap.features.tokens.impl.presentation.router.TokensListRouter -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 TokensListRouterModule { - - @Provides - @ViewModelScoped - fun provideTokensListRouter(): TokensListRouter = DefaultTokensListRouter() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/DefaultTokensListInteractor.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/DefaultTokensListInteractor.kt deleted file mode 100644 index 0cb9da8c42..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/DefaultTokensListInteractor.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.tap.features.tokens.impl.domain - -import androidx.paging.PagingData -import com.tangem.tap.features.tokens.impl.domain.models.Token -import kotlinx.coroutines.flow.Flow - -/** - * Default implementation of tokens list interactor - * - * @property repository repository of tokens list feature - */ -internal class DefaultTokensListInteractor(private val repository: TokensListRepository) : TokensListInteractor { - - override fun getTokensList(searchText: String, needFilterExcluded: Boolean): Flow> { - return repository.getAvailableTokens( - searchText = searchText.ifBlank(defaultValue = { null }), - needFilterExcluded = needFilterExcluded, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/TokensListInteractor.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/TokensListInteractor.kt deleted file mode 100644 index 4b8edb914d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/TokensListInteractor.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.tap.features.tokens.impl.domain - -import androidx.paging.PagingData -import com.tangem.tap.features.tokens.impl.domain.models.Token -import kotlinx.coroutines.flow.Flow - -/** - * Interactor of tokens list feature - * -[REDACTED_AUTHOR] - */ -internal interface TokensListInteractor { - - /** Get tokens list using filter by text [searchText] */ - fun getTokensList(searchText: String, needFilterExcluded: Boolean): Flow> -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/TokensListRepository.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/TokensListRepository.kt deleted file mode 100644 index 67a9626b6b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/TokensListRepository.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.tap.features.tokens.impl.domain - -import androidx.paging.PagingData -import com.tangem.tap.features.tokens.impl.domain.models.Token -import kotlinx.coroutines.flow.Flow - -/** - * Repository of tokens list feature - * -[REDACTED_AUTHOR] - */ -internal interface TokensListRepository { - - /** - * Get available tokens list - * - * @param searchText search text - * @param needFilterExcluded flag that determines if excluded tokens should be filtered - * - * @throws com.tangem.datasource.api.common.response.ApiResponseError - */ - fun getAvailableTokens(searchText: String?, needFilterExcluded: Boolean): Flow> -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/models/Token.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/models/Token.kt deleted file mode 100644 index 72c6ecfdae..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/domain/models/Token.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tangem.tap.features.tokens.impl.domain.models - -import com.tangem.blockchain.common.Blockchain - -/** - * Domain model of token for tokens list screen - * - * @property id token id - * @property name token name - * @property symbol token brief name. Example, "BTC" - * @property iconUrl token icon url - * @property networks token networks - * -[REDACTED_AUTHOR] - */ -internal data class Token( - val id: String, - val name: String, - val symbol: String, - val iconUrl: String, - val networks: List, -) { - - /** - * Domain model of network for tokens list screen - * - * @property id network id - * @property blockchain blockchain - * @property address address. If address equals null, it means it is the main network of token - * @property iconUrl network icon url - * @property decimalCount decimal count - * -[REDACTED_AUTHOR] - */ - data class Network( - val id: String, - val blockchain: Blockchain, - val address: String?, - val iconUrl: String, - val decimalCount: Int?, - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt deleted file mode 100644 index ed260691fe..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.hilt.navigation.compose.hiltViewModel -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.tap.features.tokens.impl.presentation.ui.TokensListScreen -import com.tangem.tap.features.tokens.impl.presentation.viewmodels.TokensListViewModel -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -/** - * Fragment with list of tokens - * -[REDACTED_AUTHOR] - */ -@AndroidEntryPoint -internal class TokensListFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - @Composable - override fun ScreenContent(modifier: Modifier) { - val viewModel = hiltViewModel().apply { - LocalLifecycleOwner.current.lifecycle.addObserver(this) - } - TokensListScreen( - stateHolder = viewModel.uiState, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/models/SupportTokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/models/SupportTokensState.kt deleted file mode 100644 index 0917dcb2f9..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/models/SupportTokensState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.models - -/** - * State that shows is token support by given card - */ -sealed class SupportTokensState { - - object NetworkTokensUnsupported : SupportTokensState() - object UnsupportedCurve : SupportTokensState() - object SupportedToken : SupportTokensState() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt deleted file mode 100644 index d9142d235c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.router - -import com.tangem.blockchain.common.Blockchain -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.AppRouter - -import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.common.extensions.dispatchNotification -import com.tangem.tap.common.redux.AppDialog -import com.tangem.tap.store -import com.tangem.wallet.R - -/** - * Default implementation of tokens list router - * FIXME("Necessary to avoid using redux actions") - * -[REDACTED_AUTHOR] - */ -internal class DefaultTokensListRouter : TokensListRouter { - - override fun popBackStack() { - store.dispatchNavigationAction(AppRouter::pop) - } - - override fun openAddCustomTokenScreen() { - store.dispatchNavigationAction { push(AppRoute.AddCustomToken) } - } - - override fun showAddressCopiedNotification() { - store.dispatchNotification(R.string.contract_address_copied_message) - } - - override fun openUnableHideMainTokenAlert(tokenName: String, tokenSymbol: String, networkName: String) { - store.dispatchDialogShow( - dialog = AppDialog.TokensAreLinkedDialog( - currencyTitle = tokenName, - currencySymbol = tokenSymbol, - networkName = networkName, - ), - ) - } - - override fun openRemoveWalletAlert(tokenName: String, onOkClick: () -> Unit) { - store.dispatchDialogShow( - dialog = AppDialog.RemoveWalletDialog(currencyTitle = tokenName, onOk = onOkClick), - ) - } - - 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) - } - - override fun openNetworkTokensNotSupportAlert(networkName: String) { - store.dispatchDialogShow( - AppDialog.SimpleOkDialogRes( - headerId = R.string.common_warning, - messageId = R.string.alert_manage_tokens_unsupported_message, - args = listOf(networkName), - ), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt deleted file mode 100644 index a11e584564..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.router - -import com.tangem.blockchain.common.Blockchain - -/** Tokens list feature router */ -internal interface TokensListRouter { - - /** Return to last screen */ - fun popBackStack() - - /** Open adding custom token screen */ - fun openAddCustomTokenScreen() - - /** Show notification to inform about copied address */ - fun showAddressCopiedNotification() - - /** - * Open alert if unable to hide the main token - * - * @param tokenName token name - * @param tokenSymbol token brief name - * @param networkName blockchain network full name - */ - fun openUnableHideMainTokenAlert(tokenName: String, tokenSymbol: String, networkName: String) - - /** - * Open alert to remove wallet - * - * @param tokenName token name - * @param onOkClick lambda be invoked if OkButton is been clicked - */ - fun openRemoveWalletAlert(tokenName: String, onOkClick: () -> Unit) - - /** Open alert if solana network is unsupported - * - * @param blockchain blockchain to show alert - */ - fun openUnsupportedNetworkAlert(blockchain: Blockchain) - - fun showGenericErrorAlertAndPopBack() - - /** - * Open alert with unsupported networks tokens error - */ - fun openNetworkTokensNotSupportAlert(networkName: String) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/NetworkItemState.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/NetworkItemState.kt deleted file mode 100644 index a78f074e16..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/NetworkItemState.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.states - -import androidx.compose.runtime.MutableState -import com.tangem.blockchain.common.Blockchain -import com.tangem.core.ui.extensions.getActiveIconRes -import com.tangem.core.ui.extensions.getGreyedOutIconRes - -/** - * Network item state - * -[REDACTED_AUTHOR] - */ -sealed interface NetworkItemState { - - /** Network name */ - val name: String - - /** Network protocol name */ - val protocolName: String - - /** Network icon id from resources */ - val iconResId: MutableState - - /** Flag that determines if the network is the main network for the token */ - val isMainNetwork: Boolean - - /** - * Network item state that is available for read - * - * @property name network name - * @property protocolName network protocol name - * @property iconResId network icon id from resources - * @property isMainNetwork flag that determines if the network is the main network for the token - */ - data class ReadContent( - override val name: String, - override val protocolName: String, - override val iconResId: MutableState, - override val isMainNetwork: Boolean, - ) : NetworkItemState - - /** - * Network item state that is available for read and edit - * - * @property name network name - * @property protocolName network protocol name - * @property iconResId network icon id from resources - * @property isMainNetwork flag that determines if the network is the main network for the token - * @property isAdded flag that determines if the user has saved the token - * @property id network id - * @property address contract address - * @property decimalCount decimal count - * @property blockchain blockchain - * @property onToggleClick lambda be invoked when switch is been toggled - * @property onNetworkClick lambda be invoked when network item is been clicked - */ - data class ManageContent( - override val name: String, - override val protocolName: String, - override val iconResId: MutableState, - override val isMainNetwork: Boolean, - val isAdded: MutableState, - val id: String, - val address: String?, - val decimalCount: Int?, - val blockchain: Blockchain, - val onToggleClick: (TokenItemState.ManageContent, ManageContent) -> Unit, - val onNetworkClick: () -> Unit, - ) : NetworkItemState { - - /** - * Change toggle state [isAdded]. - * - * It is a hack that helps us to change element of flow - */ - fun changeToggleState() { - val reverseState = !isAdded.value - isAdded.value = reverseState - iconResId.value = if (reverseState) getActiveIconRes(blockchain.id) else getGreyedOutIconRes(blockchain.id) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt deleted file mode 100644 index a5f9c615fd..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.states - -import androidx.compose.runtime.Immutable -import kotlinx.collections.immutable.ImmutableList - -/** - * Token item state. - * All subclasses is stable, but @Immutable annotation is required to use this sealed class like as - * field of TokensListStateHolder. - * -[REDACTED_AUTHOR] - */ -@Immutable -sealed interface TokenItemState { - - /** Token id */ - val id: String - - /** Token full name (name with symbol) */ - val fullName: String - - /** Token icon url */ - val iconUrl: String - - /** List of networks */ - val networks: ImmutableList - - /** Token composed id that unique for tokens in different networks */ - val composedId: String - - /** - * Token item state that is available for read - * - * @property id token id - * @property fullName token name - * @property iconUrl token icon url - * @property networks list of networks that is available for read - * @property composedId token composed id to use in lists - */ - data class ReadContent( - override val id: String, - override val fullName: String, - override val iconUrl: String, - override val networks: ImmutableList, - override val composedId: String, - ) : TokenItemState - - /** - * Token item state that is available for read and manage - * - * @property id token id - * @property fullName token name - * @property iconUrl token icon url - * @property networks list of networks is available for read and edit - * @property composedId token composed id to use in lists - * @property name token name - * @property symbol token brief name - */ - data class ManageContent( - override val id: String, - override val fullName: String, - override val iconUrl: String, - override val networks: ImmutableList, - override val composedId: String, - val name: String, - val symbol: String, - ) : TokenItemState -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt deleted file mode 100644 index bf48a93269..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.states - -import androidx.compose.runtime.Immutable -import androidx.paging.LoadState -import androidx.paging.PagingData -import kotlinx.coroutines.flow.Flow - -/** - * State holder for screen with list of tokens - * -[REDACTED_AUTHOR] - */ -@Immutable -internal sealed interface TokensListStateHolder { - - /** Toolbar state */ - val toolbarState: TokensListToolbarState - - /** Loading state */ - val isLoading: Boolean - - /** Flag that determines if warning block is visible */ - val isDifferentAddressesBlockVisible: Boolean - - /** Tokens list */ - val tokens: Flow> - - /** Callback to be invoked when [tokens] loading state is been changed */ - val onTokensLoadStateChanged: (LoadState) -> Unit - - /** - * Util function that allow to make a copy - * - * @param toolbarState toolbar state - * @param isLoading loading state - * @param isDifferentAddressesBlockVisible flag that determines if warning block is visible - * @param tokens tokens list - * @param onTokensLoadStateChanged callback to be invoked when tokens loading state is been changed - */ - fun copySealed( - toolbarState: TokensListToolbarState = this.toolbarState, - isLoading: Boolean = this.isLoading, - isDifferentAddressesBlockVisible: Boolean = this.isDifferentAddressesBlockVisible, - tokens: Flow> = this.tokens, - onTokensLoadStateChanged: (LoadState) -> Unit = this.onTokensLoadStateChanged, - ): TokensListStateHolder { - return when (this) { - is ManageContent -> copy( - toolbarState, - isLoading, - isDifferentAddressesBlockVisible, - tokens, - onTokensLoadStateChanged, - ) - is ReadContent -> copy( - toolbarState, - isLoading, - isDifferentAddressesBlockVisible, - tokens, - onTokensLoadStateChanged, - ) - } - } - - /** - * State screen that is available only for read - * - * @property toolbarState toolbar state - * @property isLoading loading state - * @property isDifferentAddressesBlockVisible flag that determines if warning block is visible - * @property tokens tokens list - * @property onTokensLoadStateChanged callback to be invoked when tokens loading state is been changed - */ - data class ReadContent( - override val toolbarState: TokensListToolbarState, - override val isLoading: Boolean, - override val isDifferentAddressesBlockVisible: Boolean, - override val tokens: Flow>, - override val onTokensLoadStateChanged: (LoadState) -> Unit, - ) : TokensListStateHolder - - /** - * State screen that is available for read and manage - * - * @property toolbarState toolbar state - * @property isLoading loading state - * @property isDifferentAddressesBlockVisible flag that determines if warning block is visible - * @property tokens tokens list - * @property onTokensLoadStateChanged callback to be invoked when tokens loading state is been changed - * @property onSaveButtonClick callback to be invoked when SaveButton is being clicked - */ - data class ManageContent( - override val toolbarState: TokensListToolbarState, - override val isLoading: Boolean, - override val isDifferentAddressesBlockVisible: Boolean, - override val tokens: Flow>, - override val onTokensLoadStateChanged: (LoadState) -> Unit, - val onSaveButtonClick: () -> Unit, - val isSavingInProgress: Boolean, - ) : TokensListStateHolder -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt deleted file mode 100644 index a269659991..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.states - -import androidx.compose.runtime.Immutable - -/** - * Toolbar state. - * All subclasses is stable, but @Immutable annotation is required to use this sealed class like as - * field of TokensListStateHolder. - */ -@Immutable -sealed interface TokensListToolbarState { - - /** Callback to be invoked when BackButton is being clicked */ - val onBackButtonClick: () -> Unit - - /** Toolbar state as title */ - sealed interface Title : TokensListToolbarState { - - /** Toolbar title id from resources */ - val titleResId: Int - - /** Callback to be invoked when SearchButton is being clicked */ - val onSearchButtonClick: () -> Unit - - /** - * Title state that is available only for read - * - * @property onBackButtonClick callback to be invoked when BackButton is being clicked - * @property titleResId toolbar title id from resources - * @property onSearchButtonClick callback to be invoked when SearchButton is being clicked - */ - data class Read( - override val onBackButtonClick: () -> Unit, - override val titleResId: Int, - override val onSearchButtonClick: () -> Unit, - ) : Title - - /** - * Title state that is available for read and manage - * - * @property onBackButtonClick callback to be invoked when BackButton is being clicked - * @property titleResId toolbar title id from resources - * @property onSearchButtonClick callback to be invoked when SearchButton is being clicked - * @property onAddCustomTokenClick callback to be invoked when AddCustomTokenButton is being clicked - */ - data class Manage( - override val onBackButtonClick: () -> Unit, - override val titleResId: Int, - override val onSearchButtonClick: () -> Unit, - val onAddCustomTokenClick: () -> Unit, - ) : Title - } - - /** - * Toolbar state as input field - * - * @property onBackButtonClick callback to be invoked when BackButton is being clicked - * @property value input value - * @property onValueChange lambda to be invoked when search value is being changed - * @property onCleanButtonClick callback to be invoked when CleanButton is being clicked - */ - data class InputField( - override val onBackButtonClick: () -> Unit, - val value: String, - val onValueChange: (String) -> Unit, - val onCleanButtonClick: () -> Unit, - ) : TokensListToolbarState -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt deleted file mode 100644 index 4091e53c06..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt +++ /dev/null @@ -1,144 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.ui - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Icon -import androidx.compose.material.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.TextUnitType -import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState -import kotlinx.collections.immutable.ImmutableCollection - -/** This const configures how many items show to user and hide more than*/ -private const val MAX_VISIBLE_BRIEF_ICONS = 9 - -/** -[REDACTED_AUTHOR] - */ -@Composable -internal fun BriefNetworksList( - isCollapsed: Boolean, - networks: ImmutableCollection, - modifier: Modifier = Modifier, -) { - AnimatedVisibility( - visible = isCollapsed, - modifier = modifier, - enter = fadeIn(), - exit = fadeOut(), - ) { - Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { - for ((index, network) in networks.withIndex()) { - if (index < MAX_VISIBLE_BRIEF_ICONS) { - key(network.name + network.protocolName) { - BriefNetworkItem(model = network) - } - } else { - if (networks.size < MAX_VISIBLE_BRIEF_ICONS + 1) { - key(network.name + network.protocolName) { - BriefNetworkItem(model = network) - } - } else { - HasMoreItem(moreCount = networks.size - index) - break - } - } - } - } - } -} - -/** -[REDACTED_AUTHOR] - */ -@Composable -internal fun BriefNetworkItem(model: NetworkItemState, modifier: Modifier = Modifier) { - val isAdded = model is NetworkItemState.ManageContent && model.isAdded.value - Box(modifier = modifier.size(size = TangemTheme.dimens.size20)) { - if (!isAdded) { - Box( - modifier = Modifier - .size(TangemTheme.dimens.size20) - .clip(CircleShape) - .background(TangemTheme.colors.control.unchecked), - ) - } - Icon( - painter = painterResource(id = model.iconResId.value), - contentDescription = null, - modifier = Modifier - .size(size = TangemTheme.dimens.size20) - .clip(CircleShape), - tint = if (isAdded) Color.Unspecified else TangemTheme.colors.text.tertiary, - ) - - if (model.isMainNetwork) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .size(TangemTheme.dimens.size7) - .clip(CircleShape) - .background(TangemTheme.colors.background.primary), - contentAlignment = Alignment.Center, - ) { - Box( - modifier = Modifier - .size(TangemTheme.dimens.size5) - .clip(CircleShape) - .background(TangemTheme.colors.icon.accent), - ) - } - } - } -} - -@Suppress("MagicNumber") -@Composable -internal fun HasMoreItem(moreCount: Int) { - val count = if (moreCount > 99) 99 else moreCount - val themeTextStyle = TangemTheme.typography.overline.copy( - letterSpacing = TextUnit(value = 0f, type = TextUnitType.Sp), - ) - var textStyle by remember(themeTextStyle) { mutableStateOf(themeTextStyle) } - var readyToDraw by remember(themeTextStyle) { mutableStateOf(false) } - - Box( - modifier = Modifier - .size(size = TangemTheme.dimens.size20) - .clip(CircleShape) - .background(TangemTheme.colors.control.unchecked), - ) { - Text( - modifier = Modifier - .align(Alignment.Center) - .drawWithContent { if (readyToDraw) drawContent() }, - text = "+$count", - style = textStyle, - color = TangemTheme.colors.text.tertiary, - overflow = TextOverflow.Clip, - onTextLayout = { textLayoutResult -> - if (textLayoutResult.hasVisualOverflow) { - textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9) - } else { - readyToDraw = true - } - }, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/DetailedNetworksList.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/DetailedNetworksList.kt deleted file mode 100644 index 12620898b6..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/DetailedNetworksList.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.ui - -import android.content.res.Configuration -import androidx.compose.animation.* -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* -import androidx.compose.material.Switch -import androidx.compose.material.SwitchDefaults -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.key -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.sp -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState -import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState -import kotlinx.collections.immutable.ImmutableCollection - -/** -[REDACTED_AUTHOR] - */ -@Composable -internal fun DetailedNetworksList( - isExpanded: Boolean, - token: TokenItemState, - networks: ImmutableCollection, - modifier: Modifier = Modifier, -) { - AnimatedVisibility( - visible = isExpanded, - modifier = modifier, - enter = fadeIn() + expandVertically(), - exit = shrinkVertically() + fadeOut(), - ) { - Column { - networks.forEachIndexed { index, network -> - key(network.name + network.protocolName) { - DetailedNetworkItem(token = token, network = network, isLastItem = networks.size - 1 == index) - } - } - } - } -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -private fun DetailedNetworkItem(token: TokenItemState, network: NetworkItemState, isLastItem: Boolean) { - val clipboardManager = LocalClipboardManager.current - val itemHeight = TangemTheme.dimens.size50 - - Row( - modifier = Modifier - .combinedClickable( - enabled = network is NetworkItemState.ManageContent, - onLongClick = { - if (network is NetworkItemState.ManageContent && network.address != null) { - clipboardManager.setText(AnnotatedString(text = network.address)) - network.onNetworkClick() - } - }, - onClick = {}, - indication = null, - interactionSource = remember { MutableInteractionSource() }, - ) - .fillMaxWidth() - .heightIn(min = itemHeight) - .padding(start = TangemTheme.dimens.spacing38), - verticalAlignment = Alignment.CenterVertically, - ) { - NetworkItemArrow(itemHeight = itemHeight, isLastItem = isLastItem) - Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing16)) - BriefNetworkItem(model = network) - Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing6)) - NetworkTitle(model = network) - - if (network is NetworkItemState.ManageContent) { - Switch( - checked = network.isAdded.value, - onCheckedChange = { - network.onToggleClick( - requireNotNull(token as? TokenItemState.ManageContent), - network, - ) - }, - modifier = Modifier.padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing8), - colors = SwitchDefaults.colors( - checkedThumbColor = TangemTheme.colors.control.key, - checkedTrackColor = TangemTheme.colors.icon.accent, - uncheckedThumbColor = TangemTheme.colors.control.key, - uncheckedTrackColor = TangemTheme.colors.icon.informative, - ), - ) - } - } -} - -@Composable -private fun RowScope.NetworkTitle(model: NetworkItemState) { - Text( - modifier = Modifier.weight(1f), - text = buildAnnotatedString { - append(text = model.name) - append(text = " ") - withStyle( - style = SpanStyle( - fontWeight = FontWeight.Normal, - color = if (model.isMainNetwork) { - TangemTheme.colors.icon.accent - } else { - TangemTheme.colors.icon.secondary - }, - ), - ) { - append(text = model.protocolName) - } - }, - fontWeight = FontWeight.SemiBold, - fontSize = 13.sp, - color = if (model is NetworkItemState.ManageContent && model.isAdded.value) { - TangemTheme.colors.text.primary1 - } else { - TangemTheme.colors.text.secondary - }, - ) -} - -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_DetailedNetworksList_ManageAccess() { - TangemThemePreview { - DetailedNetworksList( - isExpanded = true, - token = TokenListPreviewData.createManageToken(), - networks = TokenListPreviewData.createManageNetworksList(), - ) - } -} - -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_DetailedNetworksList_ReadAccess() { - TangemThemePreview { - DetailedNetworksList( - isExpanded = true, - token = TokenListPreviewData.createReadToken(), - networks = TokenListPreviewData.createReadNetworksList(), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/NetworkItemArrow.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/NetworkItemArrow.kt deleted file mode 100644 index b51b65f1c0..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/NetworkItemArrow.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.ui - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.res.TangemTheme -import com.tangem.wallet.R - -private const val SPECIAL_HEIGHT_3_5 = 3.5 - -/** -[REDACTED_AUTHOR] - */ -@Composable -internal fun NetworkItemArrow(itemHeight: Dp, isLastItem: Boolean) { - Box(modifier = Modifier.height(height = itemHeight)) { - if (!isLastItem) { - Box( - modifier = Modifier - .padding(start = TangemTheme.dimens.spacing0_5) - .background(color = TangemTheme.colors.stroke.primary) - .size(width = TangemTheme.dimens.size1, height = itemHeight), - ) - } - - Icon( - painter = painterResource(id = R.drawable.ic_link), - contentDescription = null, - modifier = Modifier.height(height = itemHeight / 2 + SPECIAL_HEIGHT_3_5.dp), - tint = TangemTheme.colors.stroke.primary, - ) - } -} - -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_NetworkItemArrow_Column() { - TangemThemePreview { - Column( - modifier = Modifier - .fillMaxWidth() - .background(color = TangemTheme.colors.background.primary) - .padding(start = TangemTheme.dimens.size36), - ) { - NetworkItemArrow(itemHeight = TangemTheme.dimens.size62, isLastItem = false) - NetworkItemArrow(itemHeight = TangemTheme.dimens.size62, isLastItem = true) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt deleted file mode 100644 index ce1967b48b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt +++ /dev/null @@ -1,236 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.ui - -import android.content.res.Configuration -import androidx.compose.animation.* -import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Text -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import coil.compose.SubcomposeAsyncImage -import coil.request.ImageRequest -import com.tangem.core.ui.components.CurrencyPlaceholderIcon -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.ImageBackgroundContrastChecker -import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState -import com.tangem.wallet.R -import kotlinx.coroutines.launch - -/** -[REDACTED_AUTHOR] - */ -@Suppress("LongMethod") -@Composable -internal fun TokenItem(model: TokenItemState) { - var isExpanded by rememberSaveable { mutableStateOf(value = false) } - var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) } - - ConstraintLayout( - modifier = Modifier - .fillMaxWidth() - .background(color = TangemTheme.colors.background.primary) - .padding(top = TangemTheme.dimens.spacing16), - ) { - val (icon, title, availableNetworksText) = createRefs() - val (briefNetworksList, detailedNetworksList, changeNetworksViewButton) = createRefs() - - val spacing16 = TangemTheme.dimens.spacing16 - val spacing6 = TangemTheme.dimens.spacing6 - - Box( - modifier = Modifier - .background( - color = iconBackgroundColor, - shape = TangemTheme.shapes.roundedCorners8, - ) - .size(TangemTheme.dimens.size46) - .constrainAs(icon) { - top.linkTo(parent.top) - start.linkTo(anchor = parent.start, margin = spacing16) - }, - ) { - Icon( - name = model.fullName, - iconUrl = model.iconUrl, - onContrastCalculate = { iconBackgroundColor = it }, - ) - } - - Title( - title = model.fullName, - modifier = Modifier.constrainAs(title) { - top.linkTo(parent.top) - start.linkTo(icon.end, margin = spacing16) - end.linkTo(anchor = changeNetworksViewButton.start, margin = spacing16) - width = Dimension.fillToConstraints - }, - ) - - Subtitle( - isExpanded = isExpanded, - modifier = Modifier.constrainAs(availableNetworksText) { - top.linkTo(title.bottom, margin = spacing6) - start.linkTo(icon.end, margin = spacing16) - end.linkTo(anchor = changeNetworksViewButton.start, margin = spacing16) - width = Dimension.fillToConstraints - }, - ) - - BriefNetworksList( - isCollapsed = !isExpanded, - networks = model.networks, - modifier = Modifier.constrainAs(briefNetworksList) { - top.linkTo(title.bottom, margin = spacing6) - start.linkTo(icon.end, margin = spacing16) - end.linkTo(anchor = changeNetworksViewButton.start, margin = spacing16) - width = Dimension.fillToConstraints - }, - ) - - DetailedNetworksList( - isExpanded = isExpanded, - token = model, - networks = model.networks, - modifier = Modifier.constrainAs(detailedNetworksList) { - top.linkTo(anchor = availableNetworksText.bottom, margin = spacing16) - centerHorizontallyTo(parent) - width = Dimension.fillToConstraints - }, - ) - - ChangeNetworksViewButton( - isExpanded = isExpanded, - onClick = { isExpanded = !isExpanded }, - modifier = Modifier.constrainAs(changeNetworksViewButton) { - top.linkTo(parent.top) - end.linkTo(anchor = parent.end, margin = spacing6) - if (!isExpanded) bottom.linkTo(parent.bottom) - }, - ) - } -} - -@Composable -private fun Icon(name: String, iconUrl: String, onContrastCalculate: (Color) -> Unit, modifier: Modifier = Modifier) { - val pixelsSize = with(LocalDensity.current) { TangemTheme.dimens.size46.roundToPx() } - val iconModifier = modifier - .size(size = TangemTheme.dimens.size46) - .clip(TangemTheme.shapes.roundedCorners8) - - var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) } - var isBackgroundColorDefined by remember { mutableStateOf(false) } - val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb() - val isDarkTheme = isSystemInDarkTheme() - val coroutineScope = rememberCoroutineScope() - - SubcomposeAsyncImage( - modifier = iconModifier, - model = ImageRequest.Builder(context = LocalContext.current) - .data(data = iconUrl) - .size(size = pixelsSize) - .memoryCacheKey(key = iconUrl + pixelsSize) - .crossfade(enable = true) - .allowHardware(false) - .listener( - onSuccess = { _, result -> - if (!isBackgroundColorDefined && isDarkTheme) { - coroutineScope.launch { - val color = ImageBackgroundContrastChecker( - drawable = result.drawable, - backgroundColor = itemBackgroundColor, - size = pixelsSize, - ).getContrastColor(isDarkTheme = true) - onContrastCalculate(color) - iconBackgroundColor = color - isBackgroundColorDefined = true - } - } - }, - ) - .build(), - contentDescription = null, - loading = { CurrencyPlaceholderIcon(id = name, modifier = iconModifier) }, - error = { CurrencyPlaceholderIcon(id = name, modifier = iconModifier) }, - ) -} - -@Composable -private fun Title(title: String, modifier: Modifier = Modifier) { - Text( - text = title, - modifier = modifier, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Start, - maxLines = 1, - style = TangemTheme.typography.body1, - ) -} - -@Composable -private fun Subtitle(isExpanded: Boolean, modifier: Modifier = Modifier) { - AnimatedVisibility( - visible = isExpanded, - modifier = modifier, - enter = fadeIn() + expandVertically(), - exit = shrinkVertically() + fadeOut(), - ) { - Text( - text = stringResource(id = R.string.currency_subtitle_expanded), - color = TangemTheme.colors.text.secondary, - maxLines = 1, - style = TangemTheme.typography.body2, - ) - } -} - -@Composable -private fun ChangeNetworksViewButton(isExpanded: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { - IconButton(onClick = onClick, modifier = modifier.size(size = TangemTheme.dimens.size46)) { - AnimatedContent( - targetState = isExpanded, - transitionSpec = { (fadeIn() + scaleIn()).togetherWith(scaleOut() + fadeOut()) }, - ) { isExpanded -> - Icon( - painter = painterResource( - id = if (isExpanded) R.drawable.ic_arrow_extended else R.drawable.ic_arrow_collapsed, - ), - contentDescription = null, - tint = TangemTheme.colors.icon.secondary, - ) - } - } -} - -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_TokenItem_ManageAccess() { - TangemThemePreview { - TokenItem(model = TokenListPreviewData.createManageToken()) - } -} - -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_TokenItem_ReadAccess() { - TangemThemePreview { - TokenItem(model = TokenListPreviewData.createReadToken()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenListPreviewData.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenListPreviewData.kt deleted file mode 100644 index fafeb5d244..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenListPreviewData.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.ui - -import androidx.compose.runtime.mutableStateOf -import com.tangem.blockchain.common.Blockchain -import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState -import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState -import com.tangem.wallet.R -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -/** -[REDACTED_AUTHOR] - */ -object TokenListPreviewData { - - fun createManageToken(): TokenItemState.ManageContent { - return TokenItemState.ManageContent( - fullName = "Tether (USDT)", - iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png", - networks = createManageNetworksList(), - composedId = "11231", - id = "", - name = "Tether", - symbol = "", - ) - } - - fun createReadToken(): TokenItemState.ReadContent { - return TokenItemState.ReadContent( - id = "1", - fullName = "Tether (USDT)", - iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png", - networks = createReadNetworksList(), - composedId = "11231", - ) - } - - fun createManageNetworksList(): ImmutableList { - return persistentListOf( - NetworkItemState.ManageContent( - name = "Ethereum", - protocolName = "MAIN", - iconResId = mutableStateOf(R.drawable.ic_eth_16), - isMainNetwork = true, - isAdded = mutableStateOf(true), - id = "", - address = null, - onToggleClick = { _, _ -> }, - onNetworkClick = {}, - decimalCount = null, - blockchain = Blockchain.Ethereum, - ), - NetworkItemState.ManageContent( - name = "BNB SMART CHAIN", - protocolName = "BEP20", - iconResId = mutableStateOf(R.drawable.ic_bsc_16), - isMainNetwork = false, - isAdded = mutableStateOf(false), - id = "", - address = null, - onToggleClick = { _, _ -> }, - onNetworkClick = {}, - decimalCount = null, - blockchain = Blockchain.BSC, - ), - ) - } - - fun createReadNetworksList(): ImmutableList { - return persistentListOf( - NetworkItemState.ReadContent( - name = "Ethereum", - protocolName = "MAIN", - iconResId = mutableStateOf(R.drawable.ic_eth_16), - isMainNetwork = true, - ), - NetworkItemState.ReadContent( - name = "BNB SMART CHAIN", - protocolName = "BEP20", - iconResId = mutableStateOf(R.drawable.ic_bsc_16), - isMainNetwork = false, - ), - // check icon clipping - NetworkItemState.ReadContent( - name = "SHIBARIUM", - protocolName = "BEP20", - iconResId = mutableStateOf(R.drawable.ic_shibarium_22), - isMainNetwork = false, - ), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt deleted file mode 100644 index d045437bb8..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt +++ /dev/null @@ -1,271 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.ui - -import android.content.res.Configuration -import androidx.activity.compose.BackHandler -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.FabPosition -import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.TextUnitType -import androidx.compose.ui.unit.dp -import androidx.paging.PagingData -import androidx.paging.compose.LazyPagingItems -import androidx.paging.compose.collectAsLazyPagingItems -import androidx.paging.compose.itemContentType -import androidx.paging.compose.itemKey -import com.tangem.core.ui.components.BottomFade -import com.tangem.core.ui.components.NavigationBar3ButtonsScrim -import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState -import com.tangem.tap.features.tokens.impl.presentation.states.TokensListStateHolder -import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState -import com.tangem.wallet.R -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flowOf - -/** - * Tokens list screen - * - * @param stateHolder state holder - * -[REDACTED_AUTHOR] - */ -@Composable -internal fun TokensListScreen(stateHolder: TokensListStateHolder, modifier: Modifier = Modifier) { - BackHandler(onBack = stateHolder.toolbarState.onBackButtonClick) - - var floatingButtonHeight by remember { mutableStateOf(value = 0.dp) } - - Scaffold( - modifier = modifier, - topBar = { - TokensListToolbar(state = stateHolder.toolbarState) - }, - floatingActionButton = { - if (stateHolder is TokensListStateHolder.ManageContent) { - val density = LocalDensity.current - val verticalPadding = TangemTheme.dimens.spacing32 - - SaveChangesButton( - modifier = Modifier - .navigationBarsPadding() - .onSizeChanged { - with(density) { floatingButtonHeight = it.height.toDp() + verticalPadding } - }, - showProgress = stateHolder.isSavingInProgress, - onClick = stateHolder.onSaveButtonClick, - ) - } - }, - floatingActionButtonPosition = FabPosition.Center, - backgroundColor = TangemTheme.colors.background.secondary, - ) { _ -> - val tokens = stateHolder.tokens.collectAsLazyPagingItems() - - if (stateHolder !is TokensListStateHolder.ManageContent) { - NavigationBar3ButtonsScrim() - } - - Box { - TokensListContent( - isDifferentAddressesBlockVisible = stateHolder.isDifferentAddressesBlockVisible, - tokens = tokens, - bottomMarginDp = floatingButtonHeight, - ) - - if (stateHolder is TokensListStateHolder.ManageContent) { - BottomFade(Modifier.align(Alignment.BottomCenter)) - } - } - - Crossfade(targetState = stateHolder.isLoading, label = "Update progress bar visibility") { - if (it) { - LoadingContent() - } - } - - LaunchedEffect(key1 = tokens.loadState.refresh) { - stateHolder.onTokensLoadStateChanged(tokens.loadState.refresh) - } - } -} - -@Composable -private fun LoadingContent() { - Box( - modifier = Modifier - .fillMaxSize() - .background(color = TangemTheme.colors.background.primary), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator(color = TangemTheme.colors.icon.accent) - } -} - -@Composable -private fun TokensListContent( - isDifferentAddressesBlockVisible: Boolean, - tokens: LazyPagingItems, - bottomMarginDp: Dp, -) { - val state = rememberLazyListState() - val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } - - LazyColumn( - modifier = Modifier - .background(color = TangemTheme.colors.background.primary) - .imePadding() - .fillMaxSize(), - state = state, - contentPadding = PaddingValues( - bottom = bottomMarginDp + bottomBarHeight, - ), - ) { - item( - key = "DifferentAddressesWarning$isDifferentAddressesBlockVisible", - contentType = "DifferentAddressesWarning$isDifferentAddressesBlockVisible", - ) { - if (isDifferentAddressesBlockVisible) DifferentAddressesWarning() - } - - tokens.itemKey(TokenItemState::composedId) - tokens.itemContentType(TokenItemState::composedId) - - items(items = tokens.itemSnapshotList.items, key = TokenItemState::composedId) { - TokenItem(model = it) - } - } - - val keyboardController = LocalSoftwareKeyboardController.current - LaunchedEffect(key1 = state) { - snapshotFlow(state::isScrollInProgress) - .distinctUntilChanged() - .collectLatest { if (it) keyboardController?.hide() } - } -} - -@Composable -private fun DifferentAddressesWarning() { - Box( - modifier = Modifier - .padding(TangemTheme.dimens.spacing16) - .background( - color = TangemTheme.colors.button.disabled, - shape = RoundedCornerShape(TangemTheme.dimens.radius10), - ), - contentAlignment = Alignment.Center, - ) { - val text = stringResource(id = R.string.warning_manage_tokens_legacy_derivation_message) - Text( - text = text, - modifier = Modifier.padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing8, - ), - color = TangemTheme.colors.text.tertiary, - textAlign = TextAlign.Start, - style = TangemTheme.typography.body2.copy( - letterSpacing = TextUnit(value = 0.5f, type = TextUnitType.Sp), - lineHeight = TextUnit(value = 25f, type = TextUnitType.Sp), - ), - ) - } -} - -@Composable -private fun SaveChangesButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { - PrimaryButton( - modifier = modifier - .imePadding() - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - text = stringResource(id = R.string.common_save_changes), - showProgress = showProgress, - onClick = onClick, - ) -} - -@Preview(showSystemUi = true) -@Preview(showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_TokensListScreen( - @PreviewParameter(TokensListScreenProvider::class) stateHolder: TokensListStateHolder, -) { - TangemThemePreview { - TokensListScreen(stateHolder) - } -} - -private class TokensListScreenProvider : CollectionPreviewParameterProvider( - collection = listOf( - TokensListStateHolder.ReadContent( - toolbarState = TokensListToolbarState.Title.Manage( - titleResId = R.string.main_manage_tokens, - onBackButtonClick = {}, - onSearchButtonClick = {}, - onAddCustomTokenClick = {}, - ), - isLoading = true, - isDifferentAddressesBlockVisible = false, - tokens = emptyFlow(), - onTokensLoadStateChanged = {}, - ), - TokensListStateHolder.ManageContent( - toolbarState = TokensListToolbarState.Title.Manage( - titleResId = R.string.main_manage_tokens, - onBackButtonClick = {}, - onSearchButtonClick = {}, - onAddCustomTokenClick = {}, - ), - isLoading = false, - isDifferentAddressesBlockVisible = true, - tokens = flowOf( - PagingData.from( - listOf(TokenListPreviewData.createManageToken()), - ), - ), - onSaveButtonClick = {}, - onTokensLoadStateChanged = {}, - isSavingInProgress = false, - ), - TokensListStateHolder.ReadContent( - toolbarState = TokensListToolbarState.Title.Read( - titleResId = R.string.common_search_tokens, - onBackButtonClick = {}, - onSearchButtonClick = {}, - ), - isLoading = false, - isDifferentAddressesBlockVisible = false, - tokens = flowOf( - PagingData.from( - listOf(TokenListPreviewData.createManageToken()), - ), - ), - onTokensLoadStateChanged = {}, - ), - ), -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListToolbar.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListToolbar.kt deleted file mode 100644 index 0a4dd9c309..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListToolbar.kt +++ /dev/null @@ -1,221 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.ui - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -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.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.sp -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState -import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState.InputField -import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState.Title -import com.tangem.wallet.R -import kotlinx.coroutines.delay - -/** -[REDACTED_AUTHOR] - */ -@Composable -internal fun TokensListToolbar(state: TokensListToolbarState) { - val toolbarElevation = if (isSystemInDarkTheme()) { - TangemTheme.dimens.elevation0 - } else { - AppBarDefaults.TopAppBarElevation - } - val statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() } - - TopAppBar( - backgroundColor = TangemTheme.colors.background.secondary, - contentPadding = PaddingValues( - start = TangemTheme.dimens.spacing4, - end = TangemTheme.dimens.spacing4, - top = statusBarHeight, - ), - elevation = toolbarElevation, - ) { - IconButton(onClick = state.onBackButtonClick) { - Icon( - painter = painterResource(id = R.drawable.ic_back_24), - contentDescription = "Go back", - tint = TangemTheme.colors.icon.primary1, - ) - } - - when (state) { - is Title -> TitleContent(state = state, modifier = Modifier.weight(1f)) - is InputField -> InputContent(state = state, modifier = Modifier.weight(1f)) - } - } -} - -@Composable -private fun TitleContent(state: Title, modifier: Modifier = Modifier) { - Text( - text = stringResource(id = state.titleResId), - modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing26), - color = TangemTheme.colors.text.primary1, - maxLines = 1, - style = TangemTheme.typography.h3, - ) - - IconButton(onClick = state.onSearchButtonClick) { - Icon( - painter = painterResource(id = R.drawable.ic_search_24), - contentDescription = "Search", - tint = TangemTheme.colors.icon.primary1, - ) - } - - if (state is Title.Manage) { - IconButton(onClick = state.onAddCustomTokenClick) { - Icon( - painter = painterResource(id = R.drawable.ic_plus_24), - contentDescription = "Add custom token", - tint = TangemTheme.colors.icon.primary1, - ) - } - } -} - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -private fun InputContent(state: InputField, modifier: Modifier = Modifier) { - val focusRequester = remember { FocusRequester() } - val keyboardController = LocalSoftwareKeyboardController.current - - BasicTextField( - value = state.value, - onValueChange = state.onValueChange, - modifier = modifier.focusRequester(focusRequester), - textStyle = TangemTheme.typography.subtitle1.copy( - fontWeight = FontWeight.Normal, - color = TangemTheme.colors.text.primary1, - ), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text, imeAction = ImeAction.Search), - keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }), - singleLine = true, - maxLines = 1, - cursorBrush = SolidColor(value = TangemTheme.colors.stroke.secondary), - ) { innerTextField -> - Row(verticalAlignment = Alignment.CenterVertically) { - Box( - modifier = Modifier - .weight(1f) - .padding( - start = TangemTheme.dimens.spacing28, - top = TangemTheme.dimens.spacing2, - end = TangemTheme.dimens.spacing16, - ), - ) { - Hint(value = state.value) - innerTextField() - } - - IconButton(onClick = state.onCleanButtonClick) { - Icon( - painter = painterResource(id = R.drawable.ic_cross_rounded_24), - contentDescription = "Clean entered value", - tint = TangemTheme.colors.icon.secondary, - ) - } - } - } - - // This is a hack because Compose can't show the keyboard if the input field is not found. - // So first I request focus and make a delay and then show the keyboard. - val keyboardManager = LocalSoftwareKeyboardController.current - LaunchedEffect(keyboardManager) { - focusRequester.requestFocus() - delay(timeMillis = 100) - keyboardManager?.show() - } -} - -@Composable -private fun Hint(value: String) { - if (value.isBlank()) { - Row( - modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), - horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_search_24), - contentDescription = null, - tint = TangemTheme.colors.icon.secondary, - ) - Text( - text = stringResource(id = R.string.common_search), - modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing2), - color = TangemTheme.colors.text.tertiary, - maxLines = 1, - // FIXME("Incorrect typography. Replace with typography from design system") - style = TangemTheme.typography.subtitle1.copy(fontSize = 18.sp, fontWeight = FontWeight.Normal), - ) - } - } -} - -@Preview -@Composable -private fun Preview_AddTokensToolbar_EditAccess() { - TangemThemePreview { - TokensListToolbar( - state = Title.Manage( - titleResId = R.string.main_manage_tokens, - onBackButtonClick = {}, - onSearchButtonClick = {}, - onAddCustomTokenClick = {}, - ), - ) - } -} - -@Preview -@Composable -private fun Preview_AddTokensToolbar_ReadAccess() { - TangemThemePreview { - TokensListToolbar( - state = Title.Read( - titleResId = R.string.common_search_tokens, - onBackButtonClick = {}, - onSearchButtonClick = {}, - ), - ) - } -} - -@Preview -@Composable -private fun Preview_AddTokensToolbar_SearchInputField() { - var value by remember { mutableStateOf("") } - - TangemThemePreview { - TokensListToolbar( - state = InputField( - onBackButtonClick = {}, - value = value, - onValueChange = { value = it }, - onCleanButtonClick = { value = "" }, - ), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListAnalyticsSender.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListAnalyticsSender.kt deleted file mode 100644 index 95d0e9371e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListAnalyticsSender.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.viewmodels - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.common.analytics.events.ManageTokens - -/** Analytics sender for tokens list screen */ -class TokensListAnalyticsSender(private val analyticsEventHandler: AnalyticsEventHandler) { - - fun sendWhenScreenOpened() { - analyticsEventHandler.send(event = ManageTokens.ScreenOpened()) - } - - fun sendWhenTokenAdded(token: Token) { - analyticsEventHandler.send( - event = ManageTokens.TokenSwitcherChanged( - type = AnalyticsParam.CurrencyType.Token(token), - state = AnalyticsParam.OnOffState.On, - ), - ) - } - - fun sendWhenBlockchainAdded(blockchain: Blockchain) { - analyticsEventHandler.send( - event = ManageTokens.TokenSwitcherChanged( - type = AnalyticsParam.CurrencyType.Blockchain(blockchain), - state = AnalyticsParam.OnOffState.On, - ), - ) - } - - fun sendWhenTokenRemoved(token: Token) { - analyticsEventHandler.send( - event = ManageTokens.TokenSwitcherChanged( - type = AnalyticsParam.CurrencyType.Token(token), - state = AnalyticsParam.OnOffState.Off, - ), - ) - } - - fun sendWhenBlockchainRemoved(blockchain: Blockchain) { - analyticsEventHandler.send( - event = ManageTokens.TokenSwitcherChanged( - type = AnalyticsParam.CurrencyType.Blockchain(blockchain), - state = AnalyticsParam.OnOffState.Off, - ), - ) - } - - fun sendWhenSaveButtonClicked() { - analyticsEventHandler.send(ManageTokens.ButtonSaveChanges()) - } - - fun sendWhenTokenSearched() { - analyticsEventHandler.send(ManageTokens.TokenSearched()) - } - - fun sendWhenAddCustomTokenClicked() { - analyticsEventHandler.send(ManageTokens.ButtonCustomToken()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListCryptoCurrencies.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListCryptoCurrencies.kt deleted file mode 100644 index da83face06..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListCryptoCurrencies.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.viewmodels - -import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.tokens.TokenWithBlockchain - -internal data class TokensListCryptoCurrencies( - val coins: List, - val tokens: List, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt deleted file mode 100644 index 0cf70b6b4f..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListMigration.kt +++ /dev/null @@ -1,172 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.viewmodels - -import arrow.core.Either -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token -import com.tangem.common.routing.AppRoute -import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.domain.card.DerivePublicKeysUseCase -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase -import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase -import com.tangem.domain.tokens.TokenWithBlockchain -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.common.extensions.inject -import com.tangem.tap.proxy.redux.DaggerGraphState -import com.tangem.tap.store -import timber.log.Timber -import kotlin.properties.Delegates - -/** - * Class that divide a new and legacy logic when user uses tokens list screen - * - * @property getSelectedWalletSyncUseCase use case that returns selected wallet - * @property getCurrenciesUseCase use case that returns crypto currencies of a specified wallet - */ -internal class TokensListMigration( - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - private val getCurrenciesUseCase: GetCryptoCurrenciesUseCase, - private val derivePublicKeysUseCase: DerivePublicKeysUseCase, - private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, -) { - - private var currentNewCoins: List by Delegates.notNull() - private var currentNewTokens: List by Delegates.notNull() - private var currentUserWallet: UserWallet by Delegates.notNull() - - private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() } - - suspend fun getCurrentCryptoCurrencies(): TokensListCryptoCurrencies { - return when (val selectedWalletEither = getSelectedWalletSyncUseCase()) { - is Either.Left -> { - Timber.e(selectedWalletEither.value.toString()) - TokensListCryptoCurrencies(coins = emptyList(), tokens = emptyList()) - } - is Either.Right -> { - currentUserWallet = selectedWalletEither.value - - when ( - val currenciesEither = getCurrenciesUseCase.getSync( - userWalletId = selectedWalletEither.value.walletId, - ) - ) { - is Either.Left -> { - Timber.e(currenciesEither.value.toString()) - TokensListCryptoCurrencies(coins = emptyList(), tokens = emptyList()) - } - is Either.Right -> { - TokensListCryptoCurrencies( - coins = currenciesEither.value - .filterIsInstance() - .filterNot { it.isCustom } - .also { currentNewCoins = it } - .map { Blockchain.fromId(it.network.id.value) }, - tokens = currenciesEither.value - .filterIsInstance() - .filterNot(CryptoCurrency.Token::isCustom) - .also { currentNewTokens = it } - .map { token -> - TokenWithBlockchain( - token = Token( - name = token.name, - symbol = token.symbol, - contractAddress = token.contractAddress, - decimals = token.decimals, - id = token.id.rawCurrencyId, - ), - blockchain = Blockchain.fromId(token.network.id.value), - ) - }, - ) - } - } - } - } - } - - suspend fun onSaveButtonClick( - changedTokensList: MutableList, - changedBlockchainList: List, - ) { - val changedTokens = changedTokensList.mapNotNull { - cryptoCurrencyFactory.createToken( - sdkToken = it.token, - blockchain = it.blockchain, - extraDerivationPath = null, - derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider, - ) - } - - val changedCoins = changedBlockchainList.mapNotNull { - cryptoCurrencyFactory.createCoin( - blockchain = it, - extraDerivationPath = null, - derivationStyleProvider = currentUserWallet.scanResponse.derivationStyleProvider, - ) - } - - val blockchainsToAdd = changedCoins.filterNot(currentNewCoins::contains) - val blockchainsToRemove = currentNewCoins.filterNot(changedCoins::contains) - - val tokensToAdd = changedTokens.filterNot(currentNewTokens::contains) - val tokensToRemove = currentNewTokens.filterNot { token -> changedTokens.any { it == token } } - - removeCurrenciesIfNeeded( - userWalletId = currentUserWallet.walletId, - currencies = blockchainsToRemove + tokensToRemove, - ) - - val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty() - val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() - if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) { - store.dispatchDebugErrorNotification(message = "Nothing to save") - navigateToWallet() - return - } - - val currencyList = blockchainsToAdd + tokensToAdd - - derivePublicKeysUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList) - .onRight { - addCryptoCurrenciesUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList) - navigateToWallet() - } - .onLeft { Timber.e(it, "Failed to derive public keys") } - } - - private fun navigateToWallet() { - store.dispatchNavigationAction { - val route = AppRoute.Wallet - - if (route in stack) { - popTo(route) - } else { - replaceAll(route) - } - } - } - - private suspend fun removeCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List) { - if (currencies.isEmpty()) return - val currenciesRepository = store.inject(DaggerGraphState::currenciesRepository) - val walletManagersFacade = store.inject(DaggerGraphState::walletManagersFacade) - - currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = currencies) - - walletManagersFacade.remove( - userWalletId = userWalletId, - networks = currencies - .filterIsInstance() - .mapTo(hashSetOf(), CryptoCurrency::network), - ) - walletManagersFacade.removeTokens( - userWalletId = userWalletId, - tokens = currencies.filterIsInstance().toSet(), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt deleted file mode 100644 index 736278526e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt +++ /dev/null @@ -1,496 +0,0 @@ -package com.tangem.tap.features.tokens.impl.presentation.viewmodels - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.paging.* -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.ui.extensions.getActiveIconRes -import com.tangem.core.ui.extensions.getGreyedOutIconRes -import com.tangem.domain.card.DerivePublicKeysUseCase -import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation -import com.tangem.domain.common.extensions.canHandleBlockchain -import com.tangem.domain.common.extensions.canHandleToken -import com.tangem.domain.common.extensions.supportedTokens -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase -import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase -import com.tangem.domain.tokens.TokenWithBlockchain -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.common.extensions.networkNameWithoutTestnet -import com.tangem.tap.common.extensions.getNetworkStandardName -import com.tangem.tap.features.customtoken.impl.presentation.models.SupportBlockchainType -import com.tangem.tap.features.tokens.impl.domain.TokensListInteractor -import com.tangem.tap.features.tokens.impl.domain.models.Token -import com.tangem.tap.features.tokens.impl.domain.models.Token.Network -import com.tangem.tap.features.tokens.impl.presentation.models.SupportTokensState -import com.tangem.tap.features.tokens.impl.presentation.router.TokensListRouter -import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState -import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState -import com.tangem.tap.features.tokens.impl.presentation.states.TokensListStateHolder -import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState -import com.tangem.tap.store -import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider -import com.tangem.utils.coroutines.Debouncer -import com.tangem.wallet.R -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import kotlinx.coroutines.plus -import timber.log.Timber -import javax.inject.Inject -import kotlin.properties.Delegates -import com.tangem.blockchain.common.Token as BlockchainToken - -/** - * ViewModel for tokens list screen - * - * @property interactor feature interactor - * @property router feature router - * @property dispatchers coroutine dispatchers provider - * @property getSelectedWalletSyncUseCase use case that returns selected wallet - * @param analyticsEventHandler analytics event handler - * -[REDACTED_AUTHOR] - */ -@Suppress("LongParameterList") -@HiltViewModel -internal class TokensListViewModel @Inject constructor( - private val interactor: TokensListInteractor, - private val router: TokensListRouter, - private val dispatchers: AppCoroutineDispatcherProvider, - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, - derivePublicKeysUseCase: DerivePublicKeysUseCase, - addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, - analyticsEventHandler: AnalyticsEventHandler, - getCurrenciesUseCase: GetCryptoCurrenciesUseCase, -) : ViewModel(), DefaultLifecycleObserver { - - private val isManageAccess = store.state.tokensState.isManageAccess - private val analyticsSender = TokensListAnalyticsSender(analyticsEventHandler) - private val actionsHandler = ActionsHandler(router = router, debouncer = Debouncer()) - - /** Screen state */ - var uiState by mutableStateOf(value = getInitialUiState()) - private set - - private var currentTokensList: List by Delegates.notNull() - private var currentBlockchainList: List by Delegates.notNull() - - private var changedTokensList: MutableList = mutableListOf() - private var changedBlockchainList: MutableList = mutableListOf() - - private val tokensListMigration = TokensListMigration( - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - getCurrenciesUseCase = getCurrenciesUseCase, - derivePublicKeysUseCase = derivePublicKeysUseCase, - addCryptoCurrenciesUseCase = addCryptoCurrenciesUseCase, - ) - - init { - viewModelScope.launch(dispatchers.main) { - val (currentCoins, currentTokens) = tokensListMigration.getCurrentCryptoCurrencies() - - currentBlockchainList = currentCoins - currentTokensList = currentTokens - - changedBlockchainList = currentCoins.toMutableList() - changedTokensList = currentTokens.toMutableList() - - uiState = uiState.copySealed(tokens = getTokensListBySearchText(text = "")) - } - } - - override fun onCreate(owner: LifecycleOwner) { - if (isManageAccess) analyticsSender.sendWhenScreenOpened() - } - - private fun getInitialUiState(): TokensListStateHolder { - return if (isManageAccess) { - TokensListStateHolder.ManageContent( - toolbarState = getInitialToolbarState(), - isLoading = true, - isDifferentAddressesBlockVisible = isDifferentAddressesBlockVisible(), - tokens = getInitialTokensList(), - onTokensLoadStateChanged = actionsHandler::onTokensLoadStateChanged, - isSavingInProgress = false, - onSaveButtonClick = actionsHandler::onSaveButtonClick, - ) - } else { - TokensListStateHolder.ReadContent( - toolbarState = getInitialToolbarState(), - isLoading = true, - isDifferentAddressesBlockVisible = isDifferentAddressesBlockVisible(), - tokens = getInitialTokensList(), - onTokensLoadStateChanged = actionsHandler::onTokensLoadStateChanged, - ) - } - } - - private fun getInitialToolbarState(): TokensListToolbarState { - return if (isManageAccess) { - TokensListToolbarState.Title.Manage( - titleResId = R.string.add_tokens_title, - onBackButtonClick = actionsHandler::onBackButtonClick, - onSearchButtonClick = actionsHandler::onSearchButtonClick, - onAddCustomTokenClick = actionsHandler::onAddCustomTokenClick, - ) - } else { - TokensListToolbarState.Title.Read( - titleResId = R.string.common_search_tokens, - onBackButtonClick = actionsHandler::onBackButtonClick, - onSearchButtonClick = actionsHandler::onSearchButtonClick, - ) - } - } - - private fun isDifferentAddressesBlockVisible(): Boolean { - return getSelectedWalletSyncUseCase().fold( - ifLeft = { false }, - ifRight = { it.scanResponse.card.useOldStyleDerivation }, - ) - } - - private fun getInitialTokensList(): Flow> { - return flowOf( - value = PagingData.empty( - sourceLoadStates = LoadStates(LoadState.Loading, LoadState.Loading, LoadState.Loading), - mediatorLoadStates = LoadStates(LoadState.Loading, LoadState.Loading, LoadState.Loading), - ), - ) - } - - private fun createManageTokenContent(token: Token): TokenItemState.ManageContent { - val currentTime = System.nanoTime() - return TokenItemState.ManageContent( - fullName = getTokenFullName(token), - iconUrl = token.iconUrl, - networks = token.networks.map(::createManageNetworkContent).toImmutableList(), - composedId = token.id + currentTime, - id = token.id, - name = token.name, - symbol = token.symbol, - ) - } - - private fun createManageNetworkContent(network: Network): NetworkItemState.ManageContent { - return NetworkItemState.ManageContent( - name = network.blockchain.networkNameWithoutTestnet.uppercase(), - protocolName = getNetworkProtocolName(network), - iconResId = mutableIntStateOf(value = getNetworkIconResId(network.address, network.blockchain)), - isMainNetwork = isMainNetwork(network), - isAdded = mutableStateOf( - isAdded(address = network.address, blockchain = network.blockchain), - ), - id = network.id, - address = network.address, - decimalCount = network.decimalCount, - blockchain = network.blockchain, - onToggleClick = actionsHandler::onToggleClick, - onNetworkClick = actionsHandler::onNetworkClick, - ) - } - - private fun createReadTokenContent(token: Token): TokenItemState.ReadContent { - val currentTime = System.nanoTime() - return TokenItemState.ReadContent( - id = token.id, - fullName = getTokenFullName(token), - iconUrl = token.iconUrl, - networks = token.networks.map(::createReadNetworkContent).toImmutableList(), - composedId = token.id + currentTime, - ) - } - - private fun createReadNetworkContent(network: Network): NetworkItemState.ReadContent { - return NetworkItemState.ReadContent( - name = network.blockchain.networkNameWithoutTestnet.uppercase(), - protocolName = getNetworkProtocolName(network), - iconResId = mutableIntStateOf(value = getNetworkIconResId(network.address, network.blockchain)), - isMainNetwork = isMainNetwork(network), - ) - } - - private fun getTokenFullName(token: Token) = "${token.name} (${token.symbol})" - - private fun getNetworkProtocolName(network: Network): String { - return if (network.address == null) { - MAIN_NETWORK_LABEL - } else { - network.blockchain.getNetworkStandardName().uppercase() - } - } - - private fun getNetworkIconResId(address: String?, blockchain: Blockchain): Int { - return if (isAdded(address = address, blockchain = blockchain)) { - getActiveIconRes(blockchain.id) - } else { - getGreyedOutIconRes(blockchain.id) - } - } - - private fun isMainNetwork(network: Network) = network.address == null - - private fun isAdded(address: String?, blockchain: Blockchain?): Boolean { - return if (address != null) { - changedTokensList.any { addedToken -> - address == addedToken.token.contractAddress && blockchain == addedToken.blockchain - } - } else { - changedBlockchainList.contains(blockchain) - } - } - - private fun getTokensListBySearchText(text: String): Flow> { - return interactor.getTokensList(searchText = text, needFilterExcluded = !isManageAccess) - .mapToTokenItemState() - .cachedIn(viewModelScope) - } - - private fun Flow>.mapToTokenItemState(): Flow> { - return map { pagingData -> - pagingData.map { token -> - if (isManageAccess) createManageTokenContent(token) else createReadTokenContent(token) - } - } - } - - private inner class ActionsHandler( - private val router: TokensListRouter, - private val debouncer: Debouncer, - ) { - - fun onBackButtonClick() { - router.popBackStack() - } - - fun onSearchButtonClick() { - uiState = uiState.copySealed( - toolbarState = TokensListToolbarState.InputField( - onBackButtonClick = this::onBackButtonClick, - value = "", - onValueChange = this::onSearchValueChange, - onCleanButtonClick = this::onCleanButtonClick, - ), - ) - } - - fun onAddCustomTokenClick() { - analyticsSender.sendWhenAddCustomTokenClicked() - router.openAddCustomTokenScreen() - } - - fun onToggleClick(toggledToken: TokenItemState.ManageContent, toggledNetwork: NetworkItemState.ManageContent) { - val blockchain = Blockchain.fromNetworkId( - networkId = if (toggledNetwork.address == null) toggledNetwork.id else toggledToken.id, - ) - if (toggledNetwork.address == null && blockchain != null) { - updateBlockchainItem(toggledNetwork, blockchain) - } else { - updateTokenItem(toggledToken, toggledNetwork) - } - } - - fun onNetworkClick() { - router.showAddressCopiedNotification() - } - - fun onTokensLoadStateChanged(state: LoadState) { - uiState = uiState.copySealed(isLoading = state is LoadState.Loading) - } - - fun onSaveButtonClick() { - val state = uiState as? TokensListStateHolder.ManageContent ?: return - - analyticsSender.sendWhenSaveButtonClicked() - - viewModelScope.launch(dispatchers.main) { - uiState = state.copy(isSavingInProgress = true) - - tokensListMigration.onSaveButtonClick( - changedTokensList = changedTokensList, - changedBlockchainList = changedBlockchainList, - ) - - uiState = state.copy(isSavingInProgress = false) - } - } - - private fun onSearchValueChange(newValue: String) { - val state = requireNotNull(uiState.toolbarState as? TokensListToolbarState.InputField) - uiState = uiState.copySealed(toolbarState = state.copy(value = newValue)) - - debouncer.debounce(waitMs = 800L, coroutineScope = viewModelScope + dispatchers.io) { - uiState = uiState.copySealed(tokens = getSearchedTokensList(newValue)) - } - } - - private fun getSearchedTokensList(searchText: String): Flow> { - analyticsSender.sendWhenTokenSearched() - return getTokensListBySearchText(text = searchText) - } - - private fun onCleanButtonClick() { - val state = requireNotNull(uiState.toolbarState as? TokensListToolbarState.InputField) - - if (state.value.isEmpty()) { - uiState = uiState.copySealed(toolbarState = getInitialToolbarState()) - } else { - onSearchValueChange(newValue = "") - } - } - - private fun updateBlockchainItem(toggledNetwork: NetworkItemState.ManageContent, blockchain: Blockchain) { - val isRemoveAction = changedBlockchainList.contains(blockchain) - - if (isRemoveAction) { - val isTokenWithSameBlockchainFound = changedTokensList.any { it.blockchain == blockchain } - val isAddedOnMainScreen = currentBlockchainList.contains(blockchain) - - if (isTokenWithSameBlockchainFound) { - router.openUnableHideMainTokenAlert( - tokenName = blockchain.name, - tokenSymbol = blockchain.currency, - networkName = blockchain.getNetworkName(), - ) - } else if (isAddedOnMainScreen) { - router.openRemoveWalletAlert( - tokenName = blockchain.name, - onOkClick = { - analyticsSender.sendWhenBlockchainAdded(blockchain) - changedBlockchainList.remove(blockchain) - toggledNetwork.changeToggleState() - }, - ) - } else { - analyticsSender.sendWhenBlockchainRemoved(blockchain) - changedBlockchainList.remove(blockchain) - toggledNetwork.changeToggleState() - } - } else { - when (getSupportBlockchainType(blockchain)) { - SupportBlockchainType.SUPPORTED -> { - analyticsSender.sendWhenBlockchainAdded(blockchain) - changedBlockchainList.add(blockchain) - toggledNetwork.changeToggleState() - } - SupportBlockchainType.UNSUPPORTED -> { - router.openUnsupportedNetworkAlert(blockchain) - } - SupportBlockchainType.UNABLE_TO_DETERMINE -> { - router.showGenericErrorAlertAndPopBack() - } - } - } - } - - private fun updateTokenItem( - toggledToken: TokenItemState.ManageContent, - toggledNetwork: NetworkItemState.ManageContent, - ) { - val token = TokenWithBlockchain( - token = BlockchainToken( - id = toggledToken.id, - name = toggledToken.name, - symbol = toggledToken.symbol, - contractAddress = requireNotNull(toggledNetwork.address), - decimals = requireNotNull(toggledNetwork.decimalCount), - ), - blockchain = toggledNetwork.blockchain, - ) - - val isRemoveAction = changedTokensList.contains(token) - - if (isRemoveAction) { - val isAddedOnMainScreen = currentTokensList.contains(token) - - if (isAddedOnMainScreen) { - router.openRemoveWalletAlert( - tokenName = token.token.name, - onOkClick = { - analyticsSender.sendWhenTokenRemoved(token.token) - changedTokensList.remove(token) - toggledNetwork.changeToggleState() - }, - ) - } else { - analyticsSender.sendWhenTokenRemoved(token.token) - changedTokensList.remove(token) - toggledNetwork.changeToggleState() - } - } else { - when (isUnsupportedToken(token.blockchain)) { - SupportTokensState.NetworkTokensUnsupported -> { - router.openNetworkTokensNotSupportAlert(token.blockchain.getNetworkName()) - } - SupportTokensState.SupportedToken -> { - analyticsSender.sendWhenTokenAdded(token.token) - changedTokensList.add(token) - toggledNetwork.changeToggleState() - } - SupportTokensState.UnsupportedCurve -> { - router.openUnsupportedNetworkAlert(token.blockchain) - } - null -> { - Timber.e("Something went wrong in isUnsupportedToken (no scanResponse found)") - } - } - } - } - } - - private fun isUnsupportedToken(blockchain: Blockchain): SupportTokensState? { - return getSelectedWalletSyncUseCase().fold( - ifLeft = { null }, - ifRight = { - val cardTypesResolver = it.scanResponse.cardTypesResolver - val supportedTokens = it.scanResponse.card.supportedTokens(cardTypesResolver) - - // refactor this later by moving all this logic in card config - if (blockchain == Blockchain.Solana && !supportedTokens.contains(Blockchain.Solana)) { - return SupportTokensState.NetworkTokensUnsupported - } - val canHandleToken = it.scanResponse.card.canHandleToken( - supportedTokens = supportedTokens, - blockchain = blockchain, - cardTypesResolver = cardTypesResolver, - ) - if (!canHandleToken) { - return SupportTokensState.UnsupportedCurve - } - return SupportTokensState.SupportedToken - }, - ) - } - - private fun getSupportBlockchainType(blockchain: Blockchain): SupportBlockchainType { - return getSelectedWalletSyncUseCase().fold( - ifLeft = { SupportBlockchainType.UNABLE_TO_DETERMINE }, - ifRight = { - val canHandleBlockchain = it.scanResponse.card.canHandleBlockchain( - blockchain = blockchain, - cardTypesResolver = it.scanResponse.cardTypesResolver, - ) - if (canHandleBlockchain) { - SupportBlockchainType.SUPPORTED - } else { - SupportBlockchainType.UNSUPPORTED - } - }, - ) - } - - private companion object { - const val MAIN_NETWORK_LABEL = "MAIN" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensReducer.kt deleted file mode 100644 index 389a02219a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensReducer.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.tap.features.tokens.legacy.redux - -import com.tangem.domain.tokens.TokensAction -import com.tangem.tap.common.redux.AppState -import org.rekotlin.Action - -object TokensReducer { - fun reduce(action: Action, state: AppState): TokensState = internalReduce(action, state) -} - -private fun internalReduce(action: Action, state: AppState): TokensState { - if (action !is TokensAction) return state.tokensState - - return when (action) { - is TokensAction.SetArgs.ManageAccess -> state.tokensState.copy(isManageAccess = true) - is TokensAction.SetArgs.ReadAccess -> state.tokensState.copy(isManageAccess = false) - else -> state.tokensState - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensState.kt deleted file mode 100644 index 14c5d9d721..0000000000 --- a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.tap.features.tokens.legacy.redux - -import org.rekotlin.StateType - -data class TokensState(val isManageAccess: Boolean = false) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 1abdf861b9..2f26b2a39a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics +import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.NetworkAddress @@ -70,16 +71,26 @@ object TradeCryptoMiddleware { isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive, ) - if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) { - val dialogData = topUrl?.let { - AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl = it) - } - store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(data = dialogData)) - return - } + scope.launch { + val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles) - if (currency is CryptoCurrency.Token && currency.network.isTestnet) { - scope.launch { + val isRussia = if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) { + val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase) + + getUserCountryCodeUseCase().isRight { it is UserCountry.Russia } + } else { + state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE + } + + if (action.checkUserLocation && isRussia) { + val dialogData = topUrl?.let { + AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl = it) + } + store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(data = dialogData)) + return@launch + } + + if (currency is CryptoCurrency.Token && currency.network.isTestnet) { val walletManager = store.inject(DaggerGraphState::walletManagersFacade) .getOrCreateWalletManager( userWalletId = action.userWallet.walletId, @@ -97,13 +108,13 @@ object TradeCryptoMiddleware { walletManager = walletManager, destinationAddress = currency.contractAddress, ) + return@launch } - return - } - topUrl?.let { - store.dispatchOpenUrl(it) - Analytics.send(Token.Topup.ScreenOpened()) + topUrl?.let { + store.dispatchOpenUrl(it) + Analytics.send(Token.Topup.ScreenOpened()) + } } } diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultStakeKitAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultStakeKitAuthProvider.kt index a5ef219c9b..2e8c9cde6d 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultStakeKitAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultStakeKitAuthProvider.kt @@ -8,6 +8,6 @@ internal class DefaultStakeKitAuthProvider( ) : StakeKitAuthProvider { override fun getApiKey(): String { - return configManager.config.stakeKitApiKey ?: error("No StakeKit api key provided") + return configManager.getConfigSync().stakeKitApiKey ?: error("No StakeKit api key provided") } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 45b2035717..674924336e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -15,13 +15,13 @@ import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade @@ -29,25 +29,24 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.feature.qrscanning.QrScanningRouter -import com.tangem.features.details.DetailsFeatureToggles -import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.wallet.navigation.WalletRouter +import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository -import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles +import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles import com.tangem.tap.proxy.AppStateHolder import org.rekotlin.StateType data class DaggerGraphState( val testerRouter: TesterRouter? = null, val networkConnectionManager: NetworkConnectionManager? = null, - val customTokenFeatureToggles: CustomTokenFeatureToggles? = null, + val cardScanningFeatureToggles: CardScanningFeatureToggles? = null, val scanCardUseCase: ScanCardUseCase? = null, val walletRouter: WalletRouter? = null, val walletConnectRepository: LegacyWalletConnectRepository? = null, @@ -71,20 +70,19 @@ data class DaggerGraphState( val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null, val generateWalletNameUseCase: GenerateWalletNameUseCase? = null, val cardRepository: CardRepository? = null, - val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles? = null, val tangemSdkLogger: TangemSdkLogger? = null, val settingsRepository: SettingsRepository? = null, val blockchainSDKFactory: BlockchainSDKFactory? = null, val emailSender: EmailSender? = null, val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase? = null, - val getFeedbackEmailUseCase: GetFeedbackEmailUseCase? = null, + val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase? = null, val getCardInfoUseCase: GetCardInfoUseCase? = null, val assetLoader: AssetLoader? = null, - val detailsFeatureToggles: DetailsFeatureToggles? = null, val stakingRouter: StakingRouter? = null, val urlOpener: UrlOpener? = null, val shareManager: ShareManager? = null, val appRouter: AppRouter? = null, - val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles? = null, val pushNotificationsRouter: PushNotificationsRouter? = null, + val homeFeatureToggles: HomeFeatureToggles? = null, + val getUserCountryUseCase: GetUserCountryUseCase? = null, ) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 0996998748..04e0e5f804 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -6,30 +6,24 @@ import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.feature.referral.ReferralFragment import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.feature.walletsettings.component.WalletSettingsComponent -import com.tangem.features.details.DetailsFeatureToggles import com.tangem.features.details.component.DetailsComponent import com.tangem.features.disclaimer.api.components.DisclaimerComponent -import com.tangem.features.managetokens.ManageTokensToggles import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent -import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.wallet.navigation.WalletRouter -import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorFragment import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryFragment -import com.tangem.tap.features.details.ui.details.DetailsFragment import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment -import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment import com.tangem.tap.features.home.HomeFragment import com.tangem.tap.features.main.ui.ModalNotificationBottomSheetFragment import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment @@ -37,7 +31,6 @@ import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCar import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment -import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment import com.tangem.tap.features.welcome.ui.WelcomeFragment import com.tangem.tap.routing.RoutingComponent.Child import com.tangem.utils.Provider @@ -59,9 +52,6 @@ internal class ChildFactory @Inject constructor( private val qrScanningRouter: QrScanningRouter, private val stakingRouter: StakingRouter, private val testerRouter: TesterRouter, - private val detailsFeatureToggles: DetailsFeatureToggles, - private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles, - private val manageTokensToggles: ManageTokensToggles, private val pushNotificationRouter: PushNotificationsRouter, ) { @@ -76,9 +66,6 @@ internal class ChildFactory @Inject constructor( is AppRoute.AccessCodeRecovery -> { route.asFragmentChild(Provider { AccessCodeRecoveryFragment() }) } - is AppRoute.AddCustomToken -> { - route.asFragmentChild(Provider { AddCustomTokenFragment() }) - } is AppRoute.AppCurrencySelector -> { route.asFragmentChild(Provider { AppCurrencySelectorFragment() }) } @@ -98,49 +85,37 @@ internal class ChildFactory @Inject constructor( route.asFragmentChild(Provider { CardSettingsFragment() }) } is AppRoute.Details -> { - if (detailsFeatureToggles.isRedesignEnabled) { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = DetailsComponent.Params(route.userWalletId), - componentFactory = detailsComponentFactory, - ) - } else { - route.asFragmentChild(Provider { DetailsFragment() }) - } + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = DetailsComponent.Params(route.userWalletId), + componentFactory = detailsComponentFactory, + ) } is AppRoute.DetailsSecurity -> { route.asFragmentChild(Provider { SecurityModeFragment() }) } is AppRoute.Disclaimer -> { - if (pushNotificationsFeatureToggles.isPushNotificationsEnabled) { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = DisclaimerComponent.Params(route.isTosAccepted), - componentFactory = disclaimerComponentFactory, - ) - } else { - route.asFragmentChild(Provider { DisclaimerFragment() }) - } + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = DisclaimerComponent.Params(route.isTosAccepted), + componentFactory = disclaimerComponentFactory, + ) } is AppRoute.Home -> { route.asFragmentChild(Provider { HomeFragment() }) } is AppRoute.ManageTokens -> { - if (manageTokensToggles.isFeatureEnabled) { - val source = when (route.source) { - AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS - AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING - AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES - } - - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = ManageTokensComponent.Params(route.userWalletId, source), - componentFactory = manageTokensComponentFactory, - ) - } else { - route.asFragmentChild(Provider { TokensListFragment() }) + val source = when (route.source) { + AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS + AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING + AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES } + + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = ManageTokensComponent.Params(route.userWalletId, source), + componentFactory = manageTokensComponentFactory, + ) } is AppRoute.OnboardingNote -> { route.asFragmentChild(Provider { OnboardingNoteFragment() }) diff --git a/app/src/main/res/layout/fragment_disclaimer.xml b/app/src/main/res/layout/fragment_disclaimer.xml deleted file mode 100644 index 346cab4f03..0000000000 --- a/app/src/main/res/layout/fragment_disclaimer.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - -