diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 0000000000..2369228816 --- /dev/null +++ b/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_PATH: "vendor/bundle" diff --git a/.editorconfig b/.editorconfig index 25a2f7c928..0ab5ea61ec 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,4 @@ [*.{kt,kts}] -ktlint_ignore_back_ticked_identifier = true indent_size = 4 indent_style = space diff --git a/.gitignore b/.gitignore index dd33a7dab3..dcdbe5fc57 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ local.properties # LayoutInspector files /captures +# bundler +/vendor + # OS-specific files .DS_Store .DS_Store? diff --git a/Gemfile.lock b/Gemfile.lock index 9dd60618bd..b4221a320d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,80 +1,102 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.4) + CFPropertyList (3.0.6) rexml - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) + artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.519.0) - aws-sdk-core (3.121.3) + aws-partitions (1.702.0) + aws-sdk-core (3.170.0) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.5) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.62.0) + aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.50.0) - aws-sdk-core (~> 3, >= 3.121.2) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.104.0) - aws-sdk-core (~> 3, >= 3.121.2) + aws-sdk-s3 (1.119.0) + aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.4.0) + aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - claide (1.0.3) + claide (1.1.0) colored (1.2) colored2 (3.1.2) - commander-fastlane (4.4.6) - highline (~> 1.7.2) + commander (4.6.0) + highline (~> 2.0.0) declarative (0.0.20) - declarative-option (0.1.0) digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.6) - emoji_regex (1.0.1) - excon (0.87.0) - faraday (0.17.4) - multipart-post (>= 1.2, < 3) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.98.0) + faraday (1.10.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) faraday-cookie_jar (0.0.7) faraday (>= 0.8.0) http-cookie (~> 1.0.0) - faraday_middleware (0.13.1) - faraday (>= 0.7.4, < 1.0) - fastimage (2.2.5) - fastlane (2.146.1) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + fastimage (2.2.6) + fastlane (2.211.0) CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.2, < 2.0.0) + babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) colored - commander-fastlane (>= 4.4.6, < 5.0.0) + commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 2.0) + emoji_regex (>= 0.1, < 4.0) excon (>= 0.71.0, < 1.0.0) - faraday (~> 0.17) + faraday (~> 1.0) faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.13.1) + faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.29.2, < 0.37.0) - google-cloud-storage (>= 1.15.0, < 2.0.0) - highline (>= 1.7.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) json (< 3.0.0) - jwt (~> 2.1.0) + jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multi_xml (~> 0.5) multipart-post (~> 2.0.0) + naturally (~> 2.2) + optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) - public_suffix (~> 2.0.0) - rubyzip (>= 1.3.0, < 2.0.0) + rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (>= 1.4.5, < 2.0.0) tty-screen (>= 0.6.3, < 1.0.0) @@ -83,74 +105,87 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.3.1) + fastlane-plugin-firebase_app_distribution (0.4.2) gh_inspector (1.1.3) - google-api-client (0.32.1) + google-apis-androidpublisher_v3 (0.33.0) + google-apis-core (>= 0.9.1, < 2.a) + google-apis-core (0.10.0) addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.5, < 0.10.0) - httpclient (>= 2.8.1, < 3.0) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) mini_mime (~> 1.0) representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.10) - google-cloud-core (1.3.2) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.16.0) + google-apis-core (>= 0.9.1, < 2.a) + google-apis-playcustomapp_v1 (0.12.0) + google-apis-core (>= 0.9.1, < 2.a) + google-apis-storage_v1 (0.19.0) + google-apis-core (>= 0.9.0, < 2.a) + google-cloud-core (1.6.0) google-cloud-env (~> 1.0) - google-cloud-env (1.2.1) - faraday (~> 0.11) - google-cloud-storage (1.21.1) - addressable (~> 2.5) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.3.0) + google-cloud-storage (1.44.0) + addressable (~> 2.8) digest-crc (~> 0.4) - google-api-client (~> 0.26) - google-cloud-core (~> 1.2) - googleauth (>= 0.6.2, < 0.10.0) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.19.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (0.9.0) - faraday (~> 0.12) + googleauth (1.3.0) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.7) - highline (1.7.10) - http-cookie (1.0.4) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - jmespath (1.4.0) - json (2.6.1) - jwt (2.1.0) + jmespath (1.6.2) + json (2.6.3) + jwt (2.6.0) memoist (0.16.2) - mini_magick (4.11.0) + mini_magick (4.12.0) mini_mime (1.1.2) multi_json (1.15.0) - multi_xml (0.6.0) multipart-post (2.0.0) nanaimo (0.3.0) naturally (2.2.1) - os (1.1.1) + optparse (0.1.1) + os (1.1.4) plist (3.6.0) - public_suffix (2.0.5) + public_suffix (5.0.1) rake (13.0.6) - representable (3.0.4) + representable (3.2.0) declarative (< 0.1.0) - declarative-option (< 0.2.0) + trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) rexml (3.2.5) rouge (2.0.7) - rubyzip (1.3.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) security (0.1.3) - signet (0.11.0) - addressable (~> 2.3) - faraday (~> 0.9) + signet (0.17.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.8) + simctl (1.6.10) CFPropertyList naturally - slack-notifier (2.4.0) terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) + trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) tty-spinner (0.9.3) @@ -158,10 +193,11 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8) + unf_ext (0.0.8.2) unicode-display_width (1.8.0) + webrick (1.8.1) word_wrap (1.0.0) - xcodeproj (1.21.0) + xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -174,11 +210,11 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS - universal-darwin-18 + ruby DEPENDENCIES fastlane fastlane-plugin-firebase_app_distribution BUNDLED WITH - 2.2.16 + 1.17.2 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 34d26a35ae..1c44bd0851 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") kotlin("android") kotlin("kapt") + kotlin("plugin.serialization") id("com.google.gms.google-services") id("com.google.firebase.crashlytics") id("com.google.dagger.hilt.android") @@ -102,6 +103,7 @@ android { } repositories { + mavenCentral() maven(url = "https://jitpack.io") } @@ -200,6 +202,7 @@ dependencies { implementation(Library.googlePlayServicesWallet) implementation(Library.composeShimmer) implementation(Library.mviCoreWatcher) + implementation(Library.kotlinSerialization) /** Testing libraries */ testImplementation(Test.junit) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f09883d42e..5fa1e04d9d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,10 +3,6 @@ xmlns:tools="http://schemas.android.com/tools" package="com.tangem.wallet"> - - @@ -19,6 +15,9 @@ + @@ -51,47 +50,49 @@ android:windowSoftInputMode="adjustResize"> - + - - - - - - + + + + + + diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 61762924a3..6b5d0bd175 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -12,6 +12,7 @@ import com.google.android.material.snackbar.Snackbar import com.tangem.TangemSdk import com.tangem.operations.backup.BackupService import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tangem_sdk_new.extensions.initWithBiometrics import com.tangem.tap.common.ActivityResultCallbackHolder import com.tangem.tap.common.DialogManager import com.tangem.tap.common.OnActivityResultCallback @@ -20,8 +21,8 @@ import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.common.shop.GooglePayService -import com.tangem.tap.common.shop.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE +import com.tangem.tap.common.shop.googlepay.GooglePayService +import com.tangem.tap.common.shop.googlepay.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager @@ -77,9 +78,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac systemActions() store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) - tangemSdk = TangemSdk.init(this, TangemSdkManager.config) + tangemSdk = TangemSdk.initWithBiometrics(this, TangemSdkManager.config) tangemSdkManager = TangemSdkManager(tangemSdk, this) appStateHolder.tangemSdkManager = tangemSdkManager + appStateHolder.tangemSdk = tangemSdk backupService = BackupService.init(tangemSdk, this) userWalletsListManager = UserWalletsListManager.provideBiometricImplementation( context = applicationContext, @@ -115,29 +117,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac super.onResume() notificationsHandler = NotificationsHandler(binding.fragmentContainer) - val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 - val isScannedBefore = store.state.globalState.scanResponse != null - val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted - val shopOpened = store.state.shopState.total != null - if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) { - if (userWalletsListManager.hasSavedUserWallets) { - store.dispatchOnMain(WelcomeAction.HandleDeepLink(intent)) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome)) - } else { - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home)) - intentHandler.handleWalletConnectLink(intent) - } - store.dispatch(BackupAction.CheckForUnfinishedBackup) - } - intentHandler.handleBackgroundScan(intent) - intentHandler.handleSellCurrencyCallback(intent) + navigateToInitialScreenIfNeeded(intent) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - intentHandler.handleBackgroundScan(intent) - intentHandler.handleSellCurrencyCallback(intent) - intentHandler.handleWalletConnectLink(intent) + intentHandler.handleIntent(intent, userWalletsListManager.hasSavedUserWallets) } override fun onStart() { @@ -160,7 +145,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac if (snackbar != null) return snackbar = Snackbar.make( - binding.fragmentContainer, getString(text), Snackbar.LENGTH_INDEFINITE, + binding.fragmentContainer, + getString(text), + Snackbar.LENGTH_INDEFINITE, ) if (buttonTitle != null && action != null) { snackbar?.setAction(getString(buttonTitle), action) @@ -200,4 +187,30 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac lockUserWalletsTimer?.restart() } + + private fun navigateToInitialScreenIfNeeded(intent: Intent?) { + val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 + val isNotScannedBefore = store.state.globalState.scanResponse == null + val isOnboardingServiceNotActive = store.state.globalState.onboardingState.onboardingStarted + val isShopNotOpened = store.state.shopState.total != null + when { + !backStackIsEmpty && isNotScannedBefore && isOnboardingServiceNotActive && isShopNotOpened -> { + navigateToInitialScreen(intent) + } + backStackIsEmpty -> { + navigateToInitialScreen(intent) + } + } + } + + private fun navigateToInitialScreen(intent: Intent?) { + if (userWalletsListManager.hasSavedUserWallets) { + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome)) + store.dispatchOnMain(WelcomeAction.HandleIntentIfNeeded(intent)) + } else { + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home)) + intentHandler.handleIntent(intent, hasSavedUserWallets = false) + } + store.dispatch(BackupAction.CheckForUnfinishedBackup) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt b/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt index 2239eacd70..6e620bd0bc 100644 --- a/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt +++ b/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt @@ -36,4 +36,4 @@ class NavBarInsetsFragmentLifecycleCallback : FragmentLifecycleCallbacks() { windowInsets } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index d77a6e9c1b..274048b425 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -10,13 +10,15 @@ import com.tangem.LogFormat import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder -import com.tangem.common.json.MoshiJsonConverter import com.tangem.core.analytics.Analytics import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.config.models.Config +import com.tangem.datasource.config.ConfigManager +import com.tangem.datasource.config.FeaturesLocalLoader +import com.tangem.datasource.utils.AndroidAssetReader +import com.tangem.datasource.utils.AssetReader import com.tangem.domain.DomainLayer import com.tangem.domain.common.LogConfig -import com.tangem.tap.common.AndroidAssetReader -import com.tangem.tap.common.AssetReader import com.tangem.tap.common.IntentHandler import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder @@ -29,14 +31,10 @@ import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.images.createCoilImageLoader import com.tangem.tap.common.log.TangemLogCollector -import com.tangem.tap.common.moshi.BigDecimalAdapter 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.common.shop.TangemShopService -import com.tangem.tap.domain.configurable.config.Config -import com.tangem.tap.domain.configurable.config.ConfigManager -import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator @@ -51,7 +49,6 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation import com.tangem.tap.domain.walletconnect.WalletConnectRepository import com.tangem.tap.network.NetworkConnectivity -import com.tangem.tap.persistence.CardBalanceStateAdapter import com.tangem.tap.persistence.PreferencesStorage import com.tangem.tap.proxy.AppStateHolder import com.tangem.wallet.BuildConfig @@ -117,6 +114,9 @@ class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var appStateHolder: AppStateHolder + @Inject + lateinit var configManager: ConfigManager + override fun onCreate() { super.onCreate() @@ -136,14 +136,13 @@ class TapApplication : Application(), ImageLoaderFactory { activityResultCaller = foregroundActivityObserver registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) - initMoshiConverter() DomainLayer.init() NetworkConnectivity.createInstance(store, this) preferencesStorage = PreferencesStorage(this) walletConnectRepository = WalletConnectRepository(this) assetReader = AndroidAssetReader(this) - val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.INSTANCE.moshi) + val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT) initConfigManager(configLoader, ::initWithConfigDependency) initWarningMessagesManager() @@ -158,18 +157,6 @@ class TapApplication : Application(), ImageLoaderFactory { appStateHolder.walletStoresManager = walletStoresManager } - //todo refactor: move to datasource and provide via DI - private fun initMoshiConverter() { - fun appAdapters(): List = listOf( - BigDecimalAdapter(), - CardBalanceStateAdapter(), - ) - MoshiConverter.reInitInstance( - adapters = appAdapters() + MoshiJsonConverter.getTangemSdkAdapters(), - typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(), - ) - } - override fun newImageLoader(): ImageLoader { return createCoilImageLoader( context = this, @@ -178,7 +165,6 @@ class TapApplication : Application(), ImageLoaderFactory { } private fun initConfigManager(loader: FeaturesLocalLoader, onComplete: (Config) -> Unit) { - val configManager = ConfigManager() configManager.load(loader) { config -> store.dispatch(GlobalAction.SetConfigManager(configManager)) onComplete(config) @@ -188,7 +174,7 @@ class TapApplication : Application(), ImageLoaderFactory { private fun initWithConfigDependency(config: Config) { shopService = TangemShopService(this, config.shopify!!) initAnalytics(this, config) - initFeedbackManager(this, preferencesStorage, config) + initFeedbackManager(this, preferencesStorage) } private fun initAnalytics(application: Application, config: Config) { @@ -205,12 +191,12 @@ class TapApplication : Application(), ImageLoaderFactory { config = config, isDebug = BuildConfig.DEBUG, logConfig = LogConfig.analyticsHandlers, - jsonConverter = MoshiConverter.INSTANCE, + jsonConverter = MoshiConverter.sdkMoshiConverter, ) factory.build(Analytics, buildData) } - private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) { + private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage) { fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply { appVersion = try { val pInfo = context.packageManager.getPackageInfo(context.packageName, 0) diff --git a/app/src/main/java/com/tangem/tap/WaitForMigration.kt b/app/src/main/java/com/tangem/tap/WaitForMigration.kt index 536b9711eb..59c14fb064 100644 --- a/app/src/main/java/com/tangem/tap/WaitForMigration.kt +++ b/app/src/main/java/com/tangem/tap/WaitForMigration.kt @@ -4,4 +4,4 @@ package com.tangem.tap [REDACTED_AUTHOR] */ -val DELAY_SDK_DIALOG_CLOSE = 1400L \ No newline at end of file +const val DELAY_SDK_DIALOG_CLOSE = 1400L \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt index 2ae1553d38..2f3e688532 100644 --- a/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt @@ -9,8 +9,8 @@ import java.math.RoundingMode [REDACTED_AUTHOR] */ class CurrencyConverter( - private val rateValue: BigDecimal, - private val decimals: Int + private val rateValue: BigDecimal, + private val decimals: Int ) { private val roundingMode = RoundingMode.HALF_UP diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index f36d0656e8..93c54eeefa 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -14,10 +14,9 @@ import com.tangem.tap.features.details.ui.walletconnect.dialogs.ClipboardOrScanQ import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialog import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionDialog import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction -import com.tangem.tap.features.onboarding.products.twins.ui.dialog.CreateWalletInterruptDialog +import com.tangem.tap.features.onboarding.OnboardingDialog +import com.tangem.tap.features.onboarding.products.twins.ui.dialog.TwinningProcessNotCompletedDialog import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog -import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.InterruptOnboardingDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.NoFundsForActivationDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.PutVisaCardDialog @@ -57,6 +56,7 @@ class DialogManager : StoreSubscriber { store.unsubscribe(this) } + @Suppress("LongMethod", "ComplexMethod") override fun newState(state: GlobalState) { if (state.dialog == null) { dialog?.dismiss() @@ -74,7 +74,7 @@ class DialogManager : StoreSubscriber { is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context) is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context) - is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context) + is OnboardingDialog.TwinningProcessNotCompleted -> TwinningProcessNotCompletedDialog.create(context) is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog) is WalletConnectDialog.UnsupportedCard -> SimpleAlertDialog.create( @@ -135,13 +135,15 @@ class DialogManager : StoreSubscriber { is SaltPayDialog.Activation.PutVisaCard -> PutVisaCardDialog.create(context) is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog) is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context) - is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context) + is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context, state.dialog) is WalletDialog.SelectAmountToSendDialog -> AmountToSendBottomSheetDialog(context, state.dialog) is WalletDialog.SignedHashesMultiWalletDialog -> SignedHashesWarningDialog.create(context) is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create( title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol), message = context.getString( - state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle, + state.dialog.messageRes, + state.dialog.currencySymbol, + state.dialog.currencyTitle, ), context = context, ) diff --git a/app/src/main/java/com/tangem/tap/common/GlobalLayoutStateHandler.kt b/app/src/main/java/com/tangem/tap/common/GlobalLayoutStateHandler.kt index af4645f141..a716d772fe 100644 --- a/app/src/main/java/com/tangem/tap/common/GlobalLayoutStateHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/GlobalLayoutStateHandler.kt @@ -7,9 +7,9 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ -class GlobalLayoutStateHandler( - private val view: T, - attachImmediately: Boolean = true +class GlobalLayoutStateHandler( + private val view: T, + attachImmediately: Boolean = true ) : ViewTreeObserver.OnGlobalLayoutListener { var onStateChanged: ((T) -> Unit)? = null diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt index 6783e3762e..f1cd3b1027 100644 --- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt @@ -3,18 +3,33 @@ package com.tangem.tap.common import android.content.Intent import android.nfc.NfcAdapter import android.nfc.Tag +import android.os.Build import com.tangem.tap.common.extensions.removePrefixOrNull -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.walletconnect.WalletConnectManager import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.welcome.redux.WelcomeAction +import com.tangem.tap.scope import com.tangem.tap.store +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import timber.log.Timber class IntentHandler { + private val nfcActions = arrayOf( + NfcAdapter.ACTION_NDEF_DISCOVERED, + NfcAdapter.ACTION_TECH_DISCOVERED, + NfcAdapter.ACTION_TAG_DISCOVERED, + ) + + fun handleIntent(intent: Intent?, hasSavedUserWallets: Boolean) { + handleBackgroundScan(intent, hasSavedUserWallets) + handleWalletConnectLink(intent) + handleSellCurrencyCallback(intent) + } + fun handleWalletConnectLink(intent: Intent?) { val wcUri = when (intent?.scheme) { WalletConnectManager.WC_SCHEME -> { @@ -32,19 +47,29 @@ class IntentHandler { } } - fun handleBackgroundScan(intent: Intent?) { - if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || - NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action - ) - ) { - val tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) - if (tag != null) { - intent.action = null - store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - store.dispatch(HomeAction.ReadCard()) - } + fun handleBackgroundScan(intent: Intent?, hasSavedUserWallets: Boolean): Boolean { + if (intent == null || intent.action !in nfcActions) return false + + val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) } + if (tag == null) return false + + intent.action = null + if (hasSavedUserWallets) { + // TODO: Remove delay after [REDACTED_JIRA] + scope.launch { + delay(timeMillis = 200) + store.dispatch(WelcomeAction.ProceedWithCard) + } + } else { + store.dispatch(HomeAction.ReadCard()) + } + + return true } fun handleSellCurrencyCallback(intent: Intent?) { diff --git a/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt b/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt index 69581a7a34..47f579ca1b 100644 --- a/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt +++ b/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt @@ -44,7 +44,7 @@ class KeyboardObserver(activity: Activity) { onKeyboardListener?.invoke(isShow) } - private fun isSoftKeyChanged() = ((lastWindowHeight - getWindowHeight()).absoluteValue) == getSoftKeyButtonHeight() + private fun isSoftKeyChanged() = (lastWindowHeight - getWindowHeight()).absoluteValue == getSoftKeyButtonHeight() private fun getSoftKeyButtonHeight(): Int { val applicationDisplayHeight = DisplayMetrics().apply { @@ -57,5 +57,4 @@ class KeyboardObserver(activity: Activity) { return realDisplayHeight - applicationDisplayHeight } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/TestActions.kt b/app/src/main/java/com/tangem/tap/common/TestActions.kt index 5eb1021886..b787e7a1b3 100644 --- a/app/src/main/java/com/tangem/tap/common/TestActions.kt +++ b/app/src/main/java/com/tangem/tap/common/TestActions.kt @@ -40,9 +40,10 @@ typealias TestAction = Pair Unit> class TestActionsBottomSheetDialog( private val appDialog: AppDialog.TestActionsDialog, - context: Context + context: Context, ) : BottomSheetDialog(context) { + @Suppress("MagicNumber") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt index 6b5a1fc3a2..39291d12f6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt @@ -1,8 +1,8 @@ package com.tangem.tap.common.analytics import com.tangem.common.json.MoshiJsonConverter -import com.tangem.analytics.api.ErrorEventLogger -import com.tangem.analytics.api.EventLogger +import com.tangem.core.analytics.api.ErrorEventLogger +import com.tangem.core.analytics.api.EventLogger import timber.log.Timber class AnalyticsEventsLogger( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkErrorMapper.kt similarity index 96% rename from app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt rename to app/src/main/java/com/tangem/tap/common/analytics/TangemSdkErrorMapper.kt index 491ef8f683..2f9552c1ae 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkErrorMapper.kt @@ -2,10 +2,11 @@ package com.tangem.tap.common.analytics import com.tangem.common.core.TangemSdkError -object TangemSdk { +object TangemSdkErrorMapper { // This mapping is performed to group errors in FirebaseCrashlytics. // At the moment, the errors in Crashlytics can only be grouped by their place of creation (class and line). + @Suppress("LongMethod", "ComplexMethod") fun map(error: TangemSdkError): TangemSdkError { return when (error) { is TangemSdkError.TagLost -> TangemSdkError.TagLost() @@ -13,12 +14,15 @@ object TangemSdk { is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError() is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed() is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch( - error.customMessage) + error.customMessage + ) is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage) is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag( - error.customMessage) + error.customMessage + ) is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch( - error.customMessage) + error.customMessage + ) is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage) is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse() is TangemSdkError.UnknownStatus -> TangemSdkError.UnknownStatus(error.statusWord) @@ -90,7 +94,8 @@ object TangemSdk { is TangemSdkError.BackupFailedEmptyWallets -> TangemSdkError.BackupFailedEmptyWallets() is TangemSdkError.BackupFailedNotEmptyWallets -> TangemSdkError.BackupFailedNotEmptyWallets() is TangemSdkError.NoActiveBackup -> TangemSdkError.NoActiveBackup() - is TangemSdkError.ResetBackupFailedHasBackupedWallets -> TangemSdkError.ResetBackupFailedHasBackupedWallets() + is TangemSdkError.ResetBackupFailedHasBackupedWallets -> + TangemSdkError.ResetBackupFailedHasBackupedWallets() is TangemSdkError.BackupServiceInvalidState -> TangemSdkError.BackupServiceInvalidState() is TangemSdkError.NoBackupCardForIndex -> TangemSdkError.NoBackupCardForIndex() is TangemSdkError.EmptyBackupCards -> TangemSdkError.EmptyBackupCards() @@ -111,5 +116,4 @@ object TangemSdk { is TangemSdkError.BiometricsAuthenticationFailed -> error } } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt index 6d23925143..4727772b38 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt @@ -4,7 +4,7 @@ import android.app.Application import com.tangem.common.json.MoshiJsonConverter import com.tangem.core.analytics.api.AnalyticsHandler import com.tangem.domain.common.AnalyticsHandlersLogConfig -import com.tangem.tap.domain.configurable.config.Config +import com.tangem.datasource.config.models.Config interface AnalyticsHandlerBuilder { fun build(data: Data): AnalyticsHandler? diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 1eeda46c6f..1f227fc391 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -7,7 +7,6 @@ import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.filters.BasicTopUpFilter -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.wallet.redux.ProgressState @@ -28,13 +27,14 @@ class BasicEventsPreChecker { BasicTopUpEventConverter().convert(converterData)?.let { Analytics.send(it) } } + @Suppress("ComplexMethod") private fun isReadyToSend(data: BasicEventsSourceData): Boolean { val (scanResponse, walletState, biometricsWalletDataModels) = data if (walletState.derivationsCheckIsScheduled) { Timber.d("FAILED: derivationsCheckIsScheduled") return false } - if (scanResponse.card.isMultiwalletAllowed && walletState.missingDerivations.isNotEmpty()) { + if (scanResponse.cardTypesResolver.isMultiwalletAllowed() && walletState.missingDerivations.isNotEmpty()) { Timber.d("FAILED: isMultiwalletAllowed || missingDerivations.isNotEmpty") return false } @@ -70,13 +70,14 @@ class BasicEventsPreChecker { return false } - if (biometricsWalletDataModels.any { - it.status is WalletDataModel.Loading || - it.status is WalletDataModel.NoAccount || - it.status is WalletDataModel.Unreachable || - it.status is WalletDataModel.MissedDerivation || - it.status.isErrorStatus - }) { + val isCorrectStatus = biometricsWalletDataModels.any { + it.status is WalletDataModel.Loading || + it.status is WalletDataModel.NoAccount || + it.status is WalletDataModel.Unreachable || + it.status is WalletDataModel.MissedDerivation || + it.status.isErrorStatus + } + if (isCorrectStatus) { Timber.d("FAILED: by status") return false } @@ -106,7 +107,9 @@ data class BasicEventsSourceData( ?.stringValue } - val paramCardCurrency: AnalyticsParam.CardCurrency? by lazy { ParamCardCurrencyConverter().convert(scanResponse) } + val paramCardCurrency: AnalyticsParam.CardCurrency? by lazy { + ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver) + } val paramCardBalanceState: AnalyticsParam.CardBalanceState by lazy { calculateAmount().toCardBalanceState() } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt index e359de8e5a..61d71c5a14 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt @@ -2,24 +2,20 @@ package com.tangem.tap.common.analytics.converters import com.tangem.blockchain.common.Blockchain import com.tangem.common.Converter +import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.SaltPayWorkaround -import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.domain.extensions.isMultiwalletAllowed /** [REDACTED_AUTHOR] */ -class ParamCardCurrencyConverter : Converter { +class ParamCardCurrencyConverter : Converter { - override fun convert(value: ScanResponse): AnalyticsParam.CardCurrency? { - if (value.card.isMultiwalletAllowed) return AnalyticsParam.CardCurrency.MultiCurrency + override fun convert(value: CardTypesResolver): AnalyticsParam.CardCurrency? { + if (value.isMultiwalletAllowed()) return AnalyticsParam.CardCurrency.MultiCurrency val type = when { - value.isTangemNote() -> { - value.card.getTangemNoteBlockchain()?.let { AnalyticsParam.CurrencyType.Blockchain(it) } - } + value.isTangemNote() -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain()) value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin) value.isSaltPay() -> AnalyticsParam.CurrencyType.Token(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay)) value.getBlockchain() != Blockchain.Unknown -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain()) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt index 49f6762db9..5d779e661f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt @@ -20,7 +20,8 @@ class ShopOrderToEventConverter : Converter "3" } val amount = "${checkout.totalPriceV2.amount} ${checkout.totalPriceV2.currencyCode.name}" - val code = (checkout.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code + val code = (checkout.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication) + ?.code return Shop.Purchased(sku, count, amount, code) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index bad36d3f0d..2e5a925560 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -8,7 +8,10 @@ sealed class AnalyticsParam { class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol) class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency) class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol) - class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.symbol) + class FiatCurrency( + fiatCurrency: com.tangem.tap.common.entities.FiatCurrency, + ) : CurrencyType(fiatCurrency.symbol) + class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index ecdedd9761..0d9a529469 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -33,4 +33,4 @@ sealed class Basic( event = "Scan", error = error, ) -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt index 8eb0f6490d..4ec37c4806 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt @@ -75,7 +75,7 @@ sealed class Onboarding( class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened") class ButtonClaim : Onboarding("Onboarding", "Button - Claim") class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully") - class ButtonChat: Onboarding("Onboarding", "Button - Chat") + class ButtonChat : Onboarding("Onboarding", "Button - Chat") class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding( category = "Onboarding / Biometric", diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt index 80f8359025..ddb27d18ff 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt @@ -4,7 +4,7 @@ import android.app.Application import com.amplitude.api.Amplitude import com.amplitude.api.AmplitudeClient import com.tangem.common.Converter -import com.tangem.analytics.api.EventLogger +import com.tangem.core.analytics.api.EventLogger import org.json.JSONObject /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt index b8271cc6af..40cee4e698 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt @@ -2,7 +2,7 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer import android.content.Context import com.appsflyer.AppsFlyerLib -import com.tangem.analytics.api.EventLogger +import com.tangem.core.analytics.api.EventLogger /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt index 3db2ed90bc..33751b5e0a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt @@ -6,8 +6,8 @@ import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.ktx.analytics import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase -import com.tangem.analytics.api.ErrorEventLogger -import com.tangem.analytics.api.EventLogger +import com.tangem.core.analytics.api.ErrorEventLogger +import com.tangem.core.analytics.api.EventLogger /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt index 324fd3877e..63d98b6269 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt @@ -11,12 +11,13 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp +@Suppress("MagicNumber") @Composable fun TextAutoSize( - modifier: Modifier = Modifier, text: String, - textStyle: TextStyle = LocalTextStyle.current, fontSizeRange: FontSizeRange, + modifier: Modifier = Modifier, + textStyle: TextStyle = LocalTextStyle.current, ) { val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) } val readyToDraw = remember { mutableStateOf(false) } @@ -46,7 +47,7 @@ fun TextAutoSize( } else { readyToDraw.value = true } - } + }, ) } diff --git a/app/src/main/java/com/tangem/tap/common/compose/BlockchainSpinner.kt b/app/src/main/java/com/tangem/tap/common/compose/BlockchainSpinner.kt index 3df228a032..215fb7b312 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/BlockchainSpinner.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/BlockchainSpinner.kt @@ -17,7 +17,7 @@ fun BlockchainSpinner( textFieldConverter: (Blockchain) -> String, dropdownItemView: @Composable ((Blockchain) -> Unit)? = null, closePopupTrigger: ClosePopupTrigger = ClosePopupTrigger(), - onItemSelected: (Blockchain) -> Unit, + onItemSelect: (Blockchain) -> Unit, ) { OutlinedSpinner( modifier = Modifier.fillMaxWidth(), @@ -27,7 +27,7 @@ fun BlockchainSpinner( textFieldConverter = textFieldConverter, dropdownItemView = dropdownItemView, isEnabled = isEnabled, - onItemSelected = onItemSelected, + onItemSelected = onItemSelect, closePopupTrigger = closePopupTrigger, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/Button.kt b/app/src/main/java/com/tangem/tap/common/compose/Button.kt index b20f7470b8..c536af17bd 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/Button.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/Button.kt @@ -1,137 +1,8 @@ package com.tangem.tap.common.compose -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Scaffold -import androidx.compose.material.Text import androidx.compose.material.ripple.LocalRippleTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.tangem.core.ui.components.SpacerH8 -import com.tangem.tap.common.compose.extensions.stringResourceDefault -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -@Composable -fun RectangleButton( - modifier: Modifier = Modifier, - text: String = "", - textId: Int? = null, - isEnabled: Boolean = true, - contentPadding: PaddingValues = ButtonDefaults.ContentPadding, - leadingView: @Composable RowScope.() -> Unit = {}, - middleView: @Composable RowScope.() -> Unit = { TextInButton(text = text, textId = textId) }, - trailingView: @Composable RowScope.() -> Unit = {}, - onClick: () -> Unit, -) { - Button( - modifier = modifier, - contentPadding = contentPadding, - enabled = isEnabled, - onClick = onClick, - ) { - leadingView() - middleView() - trailingView() - } -} - -@Composable -private fun TextInButton( - modifier: Modifier = Modifier, - text: String = "", - textId: Int? = null, -) { - Text( - modifier = modifier, - text = stringResourceDefault(textId, text), - maxLines = 1, - style = TextStyle( - fontSize = 16.sp, - lineHeight = 20.sp, - fontWeight = FontWeight.Medium, - ) - ) -} - -@Composable -fun PasteButton( - modifier: Modifier = Modifier, - enabled: Boolean = true, - dpSize: DpSize = DpSize(40.dp, 40.dp), - onClick: () -> Unit, - tint: Color? = null, - content: @Composable (() -> Unit)? = null -) { - IconButton( - modifier = modifier.size(dpSize), - enabled = enabled, - onClick = onClick, - ) { - when (content) { - null -> { - val tintColor = tint - ?: colorResource(id = if (enabled) R.color.accent else R.color.accent_disabled) - Icon( - painterResource(id = R.drawable.ic_paste), - contentDescription = "Paste", - tint = tintColor, - ) - } - else -> content() - } - } -} - -@Composable -fun ClearButton( - modifier: Modifier = Modifier, - enabled: Boolean = true, - dpSize: DpSize = DpSize(40.dp, 40.dp), - onClick: () -> Unit, - tint: Color? = null, - content: @Composable (() -> Unit)? = null -) { - IconButton( - modifier = modifier.size(dpSize), - enabled = enabled, - onClick = onClick, - ) { - when (content) { - null -> { - val tintColor = tint - ?: colorResource(id = if (enabled) R.color.accent else R.color.accent_disabled) - Icon( - painterResource(id = R.drawable.ic_clear), - contentDescription = "Clear", - tint = tintColor, - ) - } - else -> content() - } - } -} /** * Used for disable ripple if button is enable = false @@ -143,34 +14,4 @@ fun ToggledRippleTheme( ) { val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme() CompositionLocalProvider(theme) { content() } -} - -@Preview -@Composable -fun ButtonTest() { - Scaffold { - Column(modifier = Modifier.padding(16.dp)) { - PreviewItem("Button") { - RectangleButton(text = "Some button") {} - } - PreviewItem("PasteButton") { - PasteButton(onClick = {}) - } - } - } -} - -@Composable -fun PreviewItem( - name: String, - content: @Composable RowScope.() -> Unit, -) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - modifier = Modifier.weight(1f), - text = name, - ) - content() - } - SpacerH8() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/ComposableValueDebouncer.kt b/app/src/main/java/com/tangem/tap/common/compose/ComposableValueDebouncer.kt index b3251f3cac..44395f9279 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/ComposableValueDebouncer.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/ComposableValueDebouncer.kt @@ -11,19 +11,19 @@ import com.tangem.domain.common.util.ValueDebouncer @Composable fun valueDebouncerAsState( initialValue: T, + onValueChange: (T) -> Unit, debounce: Long = 600, - onEmitValueReceived: (T) -> Unit = {}, - onValueChanged: (T) -> Unit, + onEmitValueReceive: (T) -> Unit = {}, ): ValueDebouncer { return remember { - ValueDebouncer( + ValueDebouncer( initialValue = initialValue, debounceDuration = debounce, onEmitValueReceived = { emitValue -> - emitValue?.let { onEmitValueReceived(it) } + emitValue?.let { onEmitValueReceive(it) } }, onValueChanged = { changedValue -> - changedValue?.let { onValueChanged(it) } + changedValue?.let { onValueChange(it) } }, ) } @@ -32,16 +32,16 @@ fun valueDebouncerAsState( @Composable fun valueDebouncerNullableAsState( initialValue: T?, + onValueChange: (T?) -> Unit, debounce: Long = 400, - onEmitValueReceived: (T?) -> Unit = {}, - onValueChanged: (T?) -> Unit, + onEmitValueReceive: (T?) -> Unit = {}, ): ValueDebouncer { return remember { ValueDebouncer( initialValue = initialValue, debounceDuration = debounce, - onEmitValueReceived = onEmitValueReceived, - onValueChanged = onValueChanged, + onEmitValueReceived = onEmitValueReceive, + onValueChanged = onValueChange, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt b/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt index 99042c3391..8d0de7df69 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt @@ -80,6 +80,7 @@ private fun ShowTheDialog(dialogState: MutableState) { onDismissRequest ) is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest) + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/common/compose/ErrorViews.kt b/app/src/main/java/com/tangem/tap/common/compose/ErrorViews.kt index 49b6625474..c8d0701b2e 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/ErrorViews.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/ErrorViews.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.material.LocalTextStyle import androidx.compose.material.MaterialTheme -import androidx.compose.material.Scaffold import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -19,23 +18,20 @@ import androidx.compose.ui.unit.dp fun ErrorView( text: String, modifier: Modifier = Modifier, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, ) { Text( text, color = MaterialTheme.colors.error, modifier = modifier, - style = style + style = style, ) } @Preview @Composable -fun ErrorViewTest() { - Scaffold( - ) { - Box(Modifier.padding(16.dp)) { - ErrorView(text = "Some error description") - } +private fun ErrorViewTest() { + Box(Modifier.padding(16.dp)) { + ErrorView(text = "Some error description") } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/Theme.kt b/app/src/main/java/com/tangem/tap/common/compose/NoRippleTheme.kt similarity index 100% rename from app/src/main/java/com/tangem/tap/common/compose/Theme.kt rename to app/src/main/java/com/tangem/tap/common/compose/NoRippleTheme.kt diff --git a/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt b/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt index 3792119da9..981e5e00e6 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt @@ -17,14 +17,15 @@ import com.tangem.tap.common.extensions.ValueCallback /** [REDACTED_AUTHOR] */ +@Suppress("MagicNumber") @OptIn(ExperimentalMaterialApi::class) @Composable fun OutlinedSpinner( - modifier: Modifier = Modifier, label: String, itemList: List, selectedItem: Field.Data, onItemSelected: ValueCallback, + modifier: Modifier = Modifier, textFieldConverter: (T) -> String = { it.toString() }, dropdownItemView: @Composable ((T) -> Unit)? = null, isEnabled: Boolean = true, @@ -66,6 +67,7 @@ fun OutlinedSpinner( onValueChange = {}, label = { Text(label) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = rIsExpanded.value) }, + colors = TangemTextFieldsDefault.defaultTextFieldColors, ) if (isEnabled) { @@ -94,13 +96,11 @@ class ClosePopupTrigger { @Preview @Composable -fun TestSpinnerPreview() { - Scaffold() { - OutlinedSpinner( - label = "Blockchain name", - itemList = listOf(Blockchain.values()), - selectedItem = Field.Data(Blockchain.Avalanche, false), - onItemSelected = {}, - ) - } -} +private fun TestSpinnerPreview() { + OutlinedSpinner( + label = "Blockchain name", + itemList = listOf(Blockchain.values()), + selectedItem = Field.Data(Blockchain.Avalanche, false), + onItemSelected = {}, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt b/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt index deacbaf05f..113dbfb809 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt @@ -6,6 +6,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -13,8 +14,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Scaffold import androidx.compose.material.Text +import androidx.compose.material.TextFieldColors import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -28,6 +29,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tangem.common.module.ModuleError +import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.common.form.Field import com.tangem.tap.common.CompositionLogger import com.tangem.tap.common.compose.extensions.stringResourceDefault @@ -38,7 +40,6 @@ import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter */ @Composable fun OutlinedTextFieldWidget( - modifier: Modifier = Modifier, fieldData: Field.Data, labelId: Int? = null, label: String = "", @@ -53,15 +54,12 @@ fun OutlinedTextFieldWidget( debounceTextChanges: Long = 400, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, - onTextChanged: (String) -> Unit, + onTextChange: (String) -> Unit, ) { if (!isVisible) return - Column( - modifier = modifier.animateContentSize(), - ) { + Column(modifier = Modifier.animateContentSize()) { OutlinedProgressTextField( - modifier = modifier, fieldData = fieldData, label = stringResourceDefault(labelId, label), placeholder = stringResourceDefault(placeholderId, placeholder), @@ -72,15 +70,15 @@ fun OutlinedTextFieldWidget( debounce = debounceTextChanges, visualTransformation = visualTransformation, keyboardOptions = keyboardOptions, - onTextChanged = onTextChanged, + onTextChange = onTextChange, ) - errorConverter?.let { AnimatedErrorView(error, it) } + errorConverter?.let { AnimatedErrorView(errorConverter = it, error = error) } } } +@Suppress("LongMethod", "NestedBlockDepth", "MagicNumber", "MaxLineLength") @Composable private fun OutlinedProgressTextField( - modifier: Modifier = Modifier, fieldData: Field.Data, label: String = "", placeholder: String = "", @@ -90,8 +88,10 @@ private fun OutlinedProgressTextField( debounce: Long = 400, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + colors: TextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, trailingIcon: @Composable (() -> Unit)? = null, - onTextChanged: (String) -> Unit, + onTextChange: (String) -> Unit, ) { val logger = remember { CompositionLogger(label, "OutlinedProgressTextField", listOf("Символ токена")) @@ -102,19 +102,19 @@ private fun OutlinedProgressTextField( val textDebouncer = valueDebouncerAsState( initialValue = fieldData.value, debounce = debounce, - onEmitValueReceived = { + onEmitValueReceive = { logger.log("DEBOUNCER: onEmitValueReceived: [$it]") logger.log("DEBOUNCER: start RECOMPOSE by new value for textValueState.value = [$it]") textValueState.value = it }, - onValueChanged = { + onValueChange = { logger.log("DEBOUNCER: onValueChanged: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> dispatch.toStore([$it])") - onTextChanged(it) + onTextChange(it) }, ) logger.log("RECOMPOSE ---------------------------------------------------------------START [${logger.count}]") - logger.log("RECOMPOSE --data: fieldData.value: [${fieldData}]") + logger.log("RECOMPOSE --data: fieldData.value: [$fieldData]") logger.log("RECOMPOSE --data: textValueState.value: [${textValueState.value}]") logger.log("RECOMPOSE --data: textDebouncer.emittedValue = [${textDebouncer.emittedValue}]") logger.log("RECOMPOSE --data: textDebouncer.debounced = [${textDebouncer.debounced}]") @@ -127,21 +127,32 @@ private fun OutlinedProgressTextField( logger.log("$isNotUserInput: внешние данные ОДИНАКОВЫ с данными в поле") } else { logger.log("$isNotUserInput: внешние данные РАЗЛИЧАЮТСЯ с данными в поле") - if ((textDebouncer.emittedValue != textDebouncer.debounced) || textDebouncer.emitsCountBeforeDebounce > 0) { + if (textDebouncer.emittedValue != textDebouncer.debounced || textDebouncer.emitsCountBeforeDebounce > 0) { logger.log("$isNotUserInput: пользователь ВВОДИТ данные -> внешние данные игнорируем, ждем RECOMPOSE") } else { logger.log("$isNotUserInput: пользователь НЕ вводит данные -> пытаемся обработать внешние данные") - if (textValueState.value != textDebouncer.emittedValue || textValueState.value != textDebouncer.debounced) { + if (textValueState.value != textDebouncer.emittedValue || + textValueState.value != textDebouncer.debounced + ) { logger.log("$isNotUserInput: даннные в поле не соответствуют данным из textDebouncer") if (textDebouncer.emittedValue.isEmpty() && textDebouncer.debounced.isEmpty()) { - logger.log("$isNotUserInput: даннные в textDebouncer ПУСТЫ -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]") + logger.log( + "$isNotUserInput: даннные в textDebouncer ПУСТЫ -> start RECOMPOSE новые данные для " + + "textValueState.value = [${fieldData.value}]", + ) textValueState.value = fieldData.value } else { - logger.log("$isNotUserInput: даннные в textDebouncer НЕ ПУСТЫ -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]") + logger.log( + "$isNotUserInput: даннные в textDebouncer НЕ ПУСТЫ -> start RECOMPOSE новые данные для " + + "textValueState.value = [${fieldData.value}]", + ) textValueState.value = fieldData.value } } else { - logger.log("$isNotUserInput: в пустое поле вставляются данные -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]") + logger.log( + "$isNotUserInput: в пустое поле вставляются данные -> start RECOMPOSE новые данные для " + + "textValueState.value = [${fieldData.value}]", + ) textValueState.value = fieldData.value } } @@ -159,10 +170,22 @@ private fun OutlinedProgressTextField( textDebouncer.emmit(it) }, keyboardOptions = keyboardOptions, - label = { Text(label) }, + label = { + Text( + text = label, + style = TangemTheme.typography.caption, + color = colors.labelColor( + enabled = isEnabled, + error = error != null, + interactionSource = interactionSource, + ).value, + ) + }, placeholder = { Text( text = placeholder, + style = TangemTheme.typography.body1, + color = colors.placeholderColor(enabled = isEnabled).value, maxLines = 1, overflow = TextOverflow.Ellipsis, ) @@ -172,21 +195,27 @@ private fun OutlinedProgressTextField( enabled = isEnabled, isError = error != null, visualTransformation = visualTransformation, + colors = colors, + interactionSource = interactionSource, ) AnimatedVisibility( - modifier = modifier + modifier = Modifier .fillMaxWidth() .align(Alignment.BottomCenter) .padding(start = 6.dp, top = 0.dp, end = 6.dp, bottom = 6.dp), visible = isLoading, - ) { LinearProgressIndicator() } + ) { + LinearProgressIndicator( + color = TangemTheme.colors.icon.primary1, + ) + } } } @Composable private fun AnimatedErrorView( - error: ModuleError? = null, errorConverter: ModuleMessageConverter, + error: ModuleError? = null, ) { AnimatedVisibility( visible = error != null, @@ -204,7 +233,7 @@ private fun AnimatedErrorView( @Preview @Composable -fun OutlinedTextFieldWithErrorTest() { +private fun OutlinedTextFieldWithErrorTest() { val context = LocalContext.current val converter = remember { ModuleMessageConverter(context) } @@ -217,41 +246,35 @@ fun OutlinedTextFieldWithErrorTest() { val modifier = Modifier .fillMaxWidth() .padding(16.dp) - Scaffold { - Column { - OutlinedTextFieldWidget( - modifier = modifier, - fieldData = Field.Data("", false), - label = "First label", - placeholder = "1 placeholder", - error = null, - errorConverter = converter, - ) {} - OutlinedTextFieldWidget( - modifier = modifier, - fieldData = Field.Data("First", false), - label = "First label", - placeholder = "1 placeholder", - error = null, - errorConverter = converter, - ) {} - OutlinedTextFieldWidget( - modifier = modifier, - fieldData = Field.Data("First", false), - label = "First label", - placeholder = "1 placeholder", - isLoading = true, - error = null, - errorConverter = converter, - ) {} - OutlinedTextFieldWidget( - modifier = modifier, - fieldData = Field.Data("First", false), - label = "First label", - placeholder = "1 placeholder", - error = SimpleError(), - errorConverter = converter, - ) {} - } + Column { + OutlinedTextFieldWidget( + fieldData = Field.Data("", false), + label = "First label", + placeholder = "1 placeholder", + error = null, + errorConverter = converter, + ) {} + OutlinedTextFieldWidget( + fieldData = Field.Data("First", false), + label = "First label", + placeholder = "1 placeholder", + error = null, + errorConverter = converter, + ) {} + OutlinedTextFieldWidget( + fieldData = Field.Data("First", false), + label = "First label", + placeholder = "1 placeholder", + isLoading = true, + error = null, + errorConverter = converter, + ) {} + OutlinedTextFieldWidget( + fieldData = Field.Data("First", false), + label = "First label", + placeholder = "1 placeholder", + error = SimpleError(), + errorConverter = converter, + ) {} } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt b/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt index ad7bbe794b..f742bb5731 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt @@ -50,7 +50,7 @@ import androidx.core.text.isDigitsOnly @Composable fun PinCodeWidget( config: PinViewConfig = tangemPinConfig, - onPinChanged: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> }, + onPinChange: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> }, ) { val focusRequester = remember { FocusRequester() } val keyboardController = LocalSoftwareKeyboardController.current @@ -65,7 +65,7 @@ fun PinCodeWidget( if (value.text.length <= config.pinsCount) { rTextFieldValue.value = value - onPinChanged(value.text, isLastSymbolEntered()) + onPinChange(value.text, isLastSymbolEntered()) } } @@ -125,10 +125,6 @@ private fun PinElement( } } -@Composable -private fun HiddenInputField() { -} - private fun createPinSymbolsList(size: Int, text: String): List = List(size) { try { text[it].toString() @@ -165,7 +161,7 @@ private val tangemPinConfig = PinViewConfig( @Preview @Composable -fun PinCodeWidgetPreview() { +private fun PinCodeWidgetPreview() { Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { PinCodeWidget(tangemPinConfig) } diff --git a/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt b/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt new file mode 100644 index 0000000000..694fd8c376 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt @@ -0,0 +1,156 @@ +package com.tangem.tap.common.compose + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.material.TextFieldColors +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Color +import com.tangem.core.ui.res.TangemTheme + +// Copy from com.tangem.core.ui.components +// TODO: Delete this after fields has been moved to core-ui module +internal object TangemTextFieldsDefault { + val defaultTextFieldColors: TangemTextFieldColors + @Composable @Stable get() = TangemTextFieldColors( + textColor = TangemTheme.colors.text.primary1, + disabledTextColor = TangemTheme.colors.text.disabled, + backgroundColor = Color.Transparent, + cursorColor = TangemTheme.colors.icon.primary1, + errorCursorColor = TangemTheme.colors.icon.warning, + focusedIndicatorColor = TangemTheme.colors.icon.primary1, + unfocusedIndicatorColor = TangemTheme.colors.stroke.primary, + disabledIndicatorColor = TangemTheme.colors.stroke.primary, + errorIndicatorColor = TangemTheme.colors.icon.warning, + leadingIconColor = TangemTheme.colors.icon.informative, + disabledLeadingIconColor = Color.Transparent, + errorLeadingIconColor = TangemTheme.colors.icon.warning, + trailingIconColor = TangemTheme.colors.icon.informative, + disabledTrailingIconColor = Color.Transparent, + errorTrailingIconColor = TangemTheme.colors.icon.warning, + focusedLabelColor = TangemTheme.colors.text.primary1, + unfocusedLabelColor = TangemTheme.colors.text.secondary, + disabledLabelColor = TangemTheme.colors.text.disabled, + errorLabelColor = TangemTheme.colors.icon.warning, + placeholderColor = TangemTheme.colors.text.secondary, + disabledPlaceholderColor = TangemTheme.colors.text.disabled, + captionColor = TangemTheme.colors.text.tertiary, + disabledCaptionColor = TangemTheme.colors.text.disabled, + errorCaptionColor = TangemTheme.colors.icon.warning, + ) +} + +@Immutable +internal data class TangemTextFieldColors( + private val textColor: Color, + private val disabledTextColor: Color, + private val cursorColor: Color, + private val errorCursorColor: Color, + private val focusedIndicatorColor: Color, + private val unfocusedIndicatorColor: Color, + private val errorIndicatorColor: Color, + private val disabledIndicatorColor: Color, + private val leadingIconColor: Color, + private val disabledLeadingIconColor: Color, + private val errorLeadingIconColor: Color, + private val trailingIconColor: Color, + private val disabledTrailingIconColor: Color, + private val errorTrailingIconColor: Color, + private val backgroundColor: Color, + private val focusedLabelColor: Color, + private val unfocusedLabelColor: Color, + private val disabledLabelColor: Color, + private val errorLabelColor: Color, + private val placeholderColor: Color, + private val disabledPlaceholderColor: Color, + private val captionColor: Color, + private val disabledCaptionColor: Color, + private val errorCaptionColor: Color, +) : TextFieldColors { + + @Composable + override fun leadingIconColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + when { + !enabled -> disabledLeadingIconColor + isError -> errorLeadingIconColor + else -> leadingIconColor + }, + ) + } + + @Composable + override fun trailingIconColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + when { + !enabled -> disabledTrailingIconColor + isError -> errorTrailingIconColor + else -> trailingIconColor + }, + ) + } + + @Composable + override fun indicatorColor( + enabled: Boolean, + isError: Boolean, + interactionSource: InteractionSource, + ): State { + val focused by interactionSource.collectIsFocusedAsState() + + val targetValue = when { + !enabled -> disabledIndicatorColor + isError -> errorIndicatorColor + focused -> focusedIndicatorColor + else -> unfocusedIndicatorColor + } + return if (enabled) { + animateColorAsState(targetValue, tween(durationMillis = 120)) + } else { + rememberUpdatedState(targetValue) + } + } + + @Composable + override fun backgroundColor(enabled: Boolean): State { + return rememberUpdatedState(backgroundColor) + } + + @Composable + override fun placeholderColor(enabled: Boolean): State { + return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor) + } + + @Composable + override fun labelColor( + enabled: Boolean, + error: Boolean, + interactionSource: InteractionSource, + ): State { + val focused by interactionSource.collectIsFocusedAsState() + + val targetValue = when { + !enabled -> disabledLabelColor + error -> errorLabelColor + focused -> focusedLabelColor + else -> unfocusedLabelColor + } + return rememberUpdatedState(targetValue) + } + + @Composable + override fun textColor(enabled: Boolean): State { + return rememberUpdatedState(if (enabled) textColor else disabledTextColor) + } + + @Composable + override fun cursorColor(isError: Boolean): State { + return rememberUpdatedState(if (isError) errorCursorColor else cursorColor) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/Undefined.kt b/app/src/main/java/com/tangem/tap/common/compose/Undefined.kt index 6cd0b70d72..10e2ec9956 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/Undefined.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/Undefined.kt @@ -16,7 +16,7 @@ fun TitleSubtitle( title: String, subtitle: String ) { - Column() { + Column { Text(text = title) Text( text = subtitle, diff --git a/app/src/main/java/com/tangem/tap/common/compose/Warning.kt b/app/src/main/java/com/tangem/tap/common/compose/Warning.kt index 0d19a8e2ea..ea4d150e51 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/Warning.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/Warning.kt @@ -22,9 +22,9 @@ import com.tangem.wallet.R */ @Composable fun AddCustomTokenWarning( - modifier: Modifier = Modifier, warning: ModuleMessage, - converter: ModuleMessageConverter + converter: ModuleMessageConverter, + modifier: Modifier = Modifier, ) { Surface( modifier = modifier, @@ -49,6 +49,5 @@ fun AddCustomTokenWarning( lineHeight = 18.sp ) } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt deleted file mode 100644 index d25bc9e26b..0000000000 --- a/app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.tap.common.compose.extensions - -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.core.graphics.alpha -import androidx.core.graphics.blue -import androidx.core.graphics.green -import androidx.core.graphics.red - -/** -[REDACTED_AUTHOR] - */ -fun Color.toAndroidGraphicsColor(): Int { - val argb = this.toArgb() - return android.graphics.Color.argb(argb.alpha, argb.red, argb.green, argb.blue) -} - -fun Color.parse(hexColor: String): Color { - return Color(hexColor.removePrefix("#").toInt(16)) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Context.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Context.kt index 25ae94623c..719c80d0d4 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/extensions/Context.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/Context.kt @@ -8,11 +8,13 @@ import com.tangem.tap.common.extensions.getFromClipboard /** [REDACTED_AUTHOR] */ +@Suppress("ComposableFunctionName") @Composable fun copyToClipboard(value: Any, label: String = "") { LocalContext.current.copyToClipboard(value, label) } +@Suppress("ComposableFunctionName") @Composable fun getFromClipboard(default: CharSequence? = null): CharSequence? { return LocalContext.current.getFromClipboard(default) diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt index 1692b4c51f..99911d9e6f 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt @@ -13,7 +13,8 @@ import androidx.core.graphics.drawable.toBitmap */ @Composable fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap { - val drawable = AppCompatResources.getDrawable(LocalContext.current, drawableId) - ?: throw NullPointerException() + val drawable = requireNotNull(AppCompatResources.getDrawable(LocalContext.current, drawableId)) { + "drawable is null" + } return drawable.toBitmap().asImageBitmap() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/LazyListState.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/LazyListState.kt index f05044368f..dab9b8f654 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/extensions/LazyListState.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/LazyListState.kt @@ -1,12 +1,16 @@ package com.tangem.tap.common.compose.extensions import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalView import com.tangem.tap.common.extensions.hideKeyboard @Composable -fun LazyListState.OnBottomReached(loadMoreThreshold: Int, loadMore: () -> Unit) { +fun LazyListState.OnBottomReached(loadMoreThreshold: Int, onLoadMore: () -> Unit) { require(loadMoreThreshold >= 0) val shouldLoadMore by remember { derivedStateOf { @@ -17,7 +21,7 @@ fun LazyListState.OnBottomReached(loadMoreThreshold: Int, loadMore: () -> Unit) } LaunchedEffect(shouldLoadMore) { - if (shouldLoadMore) loadMore() + if (shouldLoadMore) onLoadMore() } } diff --git a/app/src/main/java/com/tangem/tap/common/entities/Button.kt b/app/src/main/java/com/tangem/tap/common/entities/Button.kt index a2d4127c8f..d9ff2b27f8 100644 --- a/app/src/main/java/com/tangem/tap/common/entities/Button.kt +++ b/app/src/main/java/com/tangem/tap/common/entities/Button.kt @@ -6,7 +6,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState open class Button(val enabled: Boolean) open class IndeterminateProgressButton( - val state: ButtonState + val state: ButtonState ) : Button(state != ButtonState.DISABLED) { val progressState: ProgressState diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt b/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt index 8ed146440b..5f531a9bc8 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt @@ -26,11 +26,11 @@ fun Activity.sendEmail( ) { fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent { val builder = ShareCompat.IntentBuilder.from(this) - .setType("message/rfc822") - .setEmailTo(arrayOf(recipient)) - .setSubject(subject) - .setText(text) - file?.let { builder.setStream(FileProvider.getUriForFile(this, "${packageName}.provider", it)) } + .setType("message/rfc822") + .setEmailTo(arrayOf(recipient)) + .setSubject(subject) + .setText(text) + file?.let { builder.setStream(FileProvider.getUriForFile(this, "$packageName.provider", it)) } return builder.intent } @@ -41,17 +41,17 @@ fun Activity.sendEmail( val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0) val targetedIntents = originalIntentResults - .filter { originalResult -> - emailFilterIntentResults.any { - originalResult.activityInfo.packageName == it.activityInfo.packageName - } + .filter { originalResult -> + emailFilterIntentResults.any { + originalResult.activityInfo.packageName == it.activityInfo.packageName } - .map { - createEmailShareIntent(email, subject, message, file).apply { - setPackage(it.activityInfo.packageName) - } + } + .map { + createEmailShareIntent(email, subject, message, file).apply { + setPackage(it.activityInfo.packageName) } - .toMutableList() + } + .toMutableList() try { val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...") chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray()) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt index b2905ec473..9130b6c91d 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt @@ -3,4 +3,4 @@ package com.tangem.tap.common.extensions import android.content.res.AssetManager fun AssetManager.readJsonFileToString(fileName: String): String = - this.open("$fileName.json").bufferedReader().readText() \ No newline at end of file + this.open("$fileName.json").bufferedReader().readText() \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt b/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt index abe1863c6d..140f0946b4 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt @@ -6,8 +6,7 @@ import com.tangem.operations.backup.BackupService /** [REDACTED_AUTHOR] */ +@Suppress("MagicNumber") fun BackupService.primaryCardIsSaltPayVisa(): Boolean { - return primaryCardId?.slice(0..3)?.let { - SaltPayWorkaround.isVisaBatchId(it) - } ?: false + return primaryCardId?.slice(0..3)?.let(SaltPayWorkaround::isVisaBatchId) ?: false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt b/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt index e8b3891ce9..80e756c21c 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt @@ -4,12 +4,14 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import java.io.ByteArrayOutputStream +@Suppress("MagicNumber") fun Bitmap.toByteArray(): ByteArray { val stream = ByteArrayOutputStream() this.compress(Bitmap.CompressFormat.JPEG, 20, stream) return stream.toByteArray() } +@Suppress("MagicNumber") fun ByteArray.toBitmap(): Bitmap { return BitmapFactory.decodeByteArray(this, 0, this.size) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt index e53e1effbe..133869ee22 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt @@ -5,49 +5,17 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.remove import com.tangem.wallet.R -@DrawableRes -fun Blockchain.getRoundIconRes(): Int { - return when (this) { - Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_round - Blockchain.Ducatus -> R.drawable.ic_ducatus - Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_round - Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_round - Blockchain.Litecoin -> R.drawable.ic_litecoin_round - Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_round - Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_round - Blockchain.RSK -> R.drawable.ic_rsk_round - Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_round - Blockchain.Tezos -> R.drawable.ic_tezos_round - Blockchain.XRP -> R.drawable.ic_xrp_round - Blockchain.Stellar -> R.drawable.ic_stellar_round - Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.ic_avalanche_round - Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_round - Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_round - Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_round - Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_round - Blockchain.Dogecoin -> R.drawable.ic_dogecoin_round - Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round - Blockchain.Gnosis -> R.drawable.ic_gnosis_round - Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_round - Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_round - Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_round - Blockchain.Kusama -> R.drawable.ic_kusama_round - Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism - Blockchain.Dash -> R.drawable.ic_dash - else -> R.drawable.ic_tangem_logo - } -} - +@Suppress("ComplexMethod") @DrawableRes fun Blockchain.getGreyedOutIconRes(): Int { return when (this) { Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_no_color // Blockchain.Ducatus -> R.drawable.ic_ducatus - Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_no_color + Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> R.drawable.ic_bitcoin_no_color Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_no_color Blockchain.Litecoin -> R.drawable.ic_litecoin_no_color - Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_no_color - Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet, -> R.drawable.ic_eth_no_color + Blockchain.Ethereum, Blockchain.EthereumTestnet -> R.drawable.ic_eth_no_color + Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_no_color Blockchain.RSK -> R.drawable.ic_rsk_no_color Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_no_color Blockchain.Tezos -> R.drawable.ic_tezos_no_color @@ -57,7 +25,8 @@ fun Blockchain.getGreyedOutIconRes(): Int { Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_no_color Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_no_color Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_no_color - Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_no_color + Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> + R.drawable.ic_bsc_no_color Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color @@ -82,4 +51,4 @@ fun Blockchain.getNetworkName(): String { } val Blockchain.fullNameWithoutTestnet -get() = this.fullName.remove(" Testnet") \ No newline at end of file + get() = this.fullName.remove(" Testnet") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Context.kt b/app/src/main/java/com/tangem/tap/common/extensions/Context.kt index cba31fe77c..556f261f46 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Context.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Context.kt @@ -1,7 +1,11 @@ package com.tangem.tap.common.extensions +import android.content.ContentResolver import android.content.Context import android.content.pm.PackageManager +import android.content.res.Resources +import android.net.Uri +import androidx.annotation.AnyRes import androidx.core.content.ContextCompat fun Context.readFile(fileName: String): String = @@ -13,10 +17,22 @@ fun Context.rewriteFile(content: String, fileName: String) { } } -fun Context.readAssetAsString(fileName: String): String { - return this.assets.open("$fileName.json").bufferedReader().readText() -} - fun Context.isPermissionGranted(permission: String): Boolean { return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED +} + +/** + * Get uri to any resource type via given Resource Instance + * @param resId - resource id + * @throws Resources.NotFoundException if the given ID does not exist. + * @return - Uri to resource by the given ID + */ +@Throws(Resources.NotFoundException::class) +fun Context.resourceUri(@AnyRes resId: Int): Uri { + return Uri.parse( + ContentResolver.SCHEME_ANDROID_RESOURCE + + "://" + resources.getResourcePackageName(resId) + + '/' + resources.getResourceTypeName(resId) + + '/' + resources.getResourceEntryName(resId), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 711e7ed172..6cb707b071 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -1,12 +1,12 @@ package com.tangem.tap.common.extensions -import androidx.activity.OnBackPressedCallback +import android.os.Bundle import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager -import com.tangem.common.extensions.VoidCallback import com.tangem.feature.referral.ReferralFragment +import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment @@ -37,10 +37,12 @@ import timber.log.Timber fun FragmentActivity.openFragment( screen: AppScreen, addToBackstack: Boolean, + bundle: Bundle? = null, fgShareTransition: FragmentShareTransition? = null, ) { val transaction = this.supportFragmentManager.beginTransaction() val fragment = fragmentFactory(screen) + fragment.arguments = bundle fgShareTransition?.apply { fragment.sharedElementEnterTransition = enterTransitionSet fragment.sharedElementReturnTransition = exitTransitionSet @@ -80,21 +82,15 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? { } else { 0 } - val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount) + val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount) { this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name - else null + } else { + null + } return tag?.let { AppScreen.valueOf(tag) } } -fun FragmentActivity.addOnBackPressedDispatcher( - isEnabled: Boolean = true, - onBackPressed: VoidCallback -): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) { - override fun handleOnBackPressed() { - onBackPressed() - } -}).also { this.onBackPressedDispatcher.addCallback(it) } - +@Suppress("ComplexMethod") private fun fragmentFactory(screen: AppScreen): Fragment { return when (screen) { AppScreen.Home -> HomeFragment() @@ -117,6 +113,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.WalletConnectSessions -> WalletConnectFragment() AppScreen.QrScan -> QrScanFragment() AppScreen.ReferralProgram -> ReferralFragment() + AppScreen.Swap -> SwapFragment() AppScreen.Welcome -> WelcomeFragment() AppScreen.SaveWallet -> SaveWalletBottomSheetFragment() AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment() 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 8ff6116155..ff4f898d8d 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 @@ -11,8 +11,11 @@ import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.* +// TODO: move extensions to utils fun BigDecimal.toFormattedString( - decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US + decimals: Int, + roundingMode: RoundingMode = RoundingMode.DOWN, + locale: Locale = Locale.US, ): String { val symbols = DecimalFormatSymbols(locale) val df = DecimalFormat() @@ -24,9 +27,12 @@ fun BigDecimal.toFormattedString( return df.format(this) } +@Suppress("MagicNumber") fun BigDecimal.toFormattedCurrencyString( - decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN, - limitNumberOfDecimals: Boolean = true + decimals: Int, + currency: String, + roundingMode: RoundingMode = RoundingMode.DOWN, + limitNumberOfDecimals: Boolean = true, ): String { val decimalsForRounding = if (limitNumberOfDecimals) { if (decimals > 8) 8 else decimals @@ -34,13 +40,14 @@ fun BigDecimal.toFormattedCurrencyString( decimals } val formattedAmount = this.toFormattedString( - decimals = decimalsForRounding, roundingMode = roundingMode + decimals = decimalsForRounding, + roundingMode = roundingMode, ) return "$formattedAmount $currency" } fun BigDecimal.toFiatRateString( - fiatCurrencyName: String + fiatCurrencyName: String, ): String { val value = this .setScale(2, RoundingMode.HALF_UP) @@ -51,7 +58,7 @@ fun BigDecimal.toFiatRateString( fun BigDecimal.toFiatString( rateValue: BigDecimal, fiatCurrencyName: String, - formatWithSpaces: Boolean = false + formatWithSpaces: Boolean = false, ): String { val fiatValue = rateValue.multiply(this) return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces) @@ -64,7 +71,7 @@ fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal { fun BigDecimal.toFormattedFiatValue( fiatCurrencyName: String, - formatWithSpaces: Boolean = false + formatWithSpaces: Boolean = false, ): String { val fiatValue = this.setScale(2, RoundingMode.HALF_UP) .let { if (formatWithSpaces) it.formatWithSpaces() else it } @@ -81,9 +88,7 @@ fun BigDecimal.scaleToFiat(applyPrecision: Boolean = false): BigDecimal { if (this.isZero()) return this val scaledFiat = this.setScale(2, RoundingMode.DOWN) - return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1) - else scaledFiat - + return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1) else scaledFiat } fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = RoundingMode.DOWN): BigDecimal { @@ -108,7 +113,7 @@ fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean { fun BigDecimal.formatAmountAsSpannedString( currencySymbol: String, - reminderPartSizeProportion: Float = 0.7f + reminderPartSizeProportion: Float = 0.7f, ): SpannedString { val amount = this .setScale(2, RoundingMode.HALF_UP) @@ -124,11 +129,12 @@ fun BigDecimal.formatAmountAsSpannedString( append( "$reminder $currencySymbol", RelativeSizeSpan(reminderPartSizeProportion), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE, ) } } +@Suppress("MagicNumber") fun BigDecimal.formatWithSpaces(): String { val str = this.toString() var integerStr = str.substringBefore('.') diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt index 021cc3a7f2..3c4125d422 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt @@ -29,7 +29,7 @@ fun String.colorSegment( context: Context, color: Int, startIndex: Int = 0, - endIndex: Int = this.length + endIndex: Int = this.length, ): Spannable { return this.toSpannable() .also { spannable -> @@ -37,11 +37,12 @@ fun String.colorSegment( ForegroundColorSpan(ContextCompat.getColor(context, color)), startIndex, endIndex, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, ) } } +@Suppress("MagicNumber") fun String.toQrCode(): Bitmap { val hintMap = Hashtable() hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage diff --git a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt index 48ee67afc7..aef0701282 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt @@ -18,13 +18,12 @@ fun EditText.update(text: String?) { val textLength = text?.length ?: 0 - //prevent cursor jumping while editing a text + // prevent cursor jumping while editing a text val cursorPosition = if (selectionEnd > textLength) textLength else selectionEnd this.setText(text) if (!isFocused || textLength == 0) return - if (cursorPosition == 0) setSelection(textLength) - else setSelection(cursorPosition) + if (cursorPosition == 0) setSelection(textLength) else setSelection(cursorPosition) } fun EditText.setOnImeActionListener(action: Int, handler: (EditText) -> Unit) { @@ -56,13 +55,4 @@ fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) { error = null isErrorEnabled = false } -} - -fun TextView.isEllipsized(): Boolean { - val layout = this.layout - if (layout != null) { - val lines: Int = layout.lineCount - return (lines > 0) && (layout.getEllipsisCount(lines - 1) > 0) - } - return false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt index 59c918d736..7acbdfd9fb 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt @@ -6,19 +6,23 @@ import androidx.core.graphics.luminance import androidx.core.graphics.toColorInt import com.tangem.blockchain.common.Token +@Suppress("MagicNumber") @ColorInt fun Token.getColor(isTestnet: Boolean = false): Int { val defaultColor = "#C7C7CC".toColorInt() // equivalent to R.color.lightGray4 - return if (isTestnet) - defaultColor - else try { - ("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt() - } catch (exception: Exception) { + return if (isTestnet) { defaultColor + } else { + try { + ("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt() + } catch (exception: Exception) { + defaultColor + } } } +@Suppress("MagicNumber") @ColorInt fun Token.getTextColor(isTestnet: Boolean = false): Int = when { isTestnet -> Color.WHITE diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt index 885fd337e3..9a1dceb21f 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt @@ -1,16 +1,14 @@ +@file:Suppress("TooManyFunctions") + package com.tangem.tap.common.extensions import android.app.Activity -import android.content.ActivityNotFoundException import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.ContextWrapper import android.content.Intent -import android.content.res.Resources import android.graphics.drawable.Drawable -import android.os.Build -import android.os.Bundle import android.util.DisplayMetrics import android.util.TypedValue import android.view.View @@ -25,11 +23,6 @@ import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.google.android.material.card.MaterialCardView -import com.tangem.common.extensions.VoidCallback - -fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? { - return ContextCompat.getDrawable(requireContext(), drawableResId) -} fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? { return ContextCompat.getDrawable(this, drawableResId) @@ -62,17 +55,8 @@ fun View.getQuantityString(@PluralsRes id: Int, quantity: Int): String { return context.resources.getQuantityString(id, quantity, quantity) } -fun View.getResourceName(): String { - return try { - resources.getResourceEntryName(id) - } catch (ex: Resources.NotFoundException) { - "Not found" - } -} - fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) { - return if (show) this.show(invokeBeforeStateChanged) - else this.hide(invokeBeforeStateChanged) + return if (show) this.show(invokeBeforeStateChanged) else this.hide(invokeBeforeStateChanged) } fun View.show(invokeBeforeStateChanged: (() -> Unit)? = null) { @@ -102,7 +86,9 @@ fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> U fun Context.dpToPixels(dp: Int): Int = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics, + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + this.resources.displayMetrics, ).toInt() fun Context.pixelsToDp(pixels: Int): Int { @@ -111,12 +97,6 @@ fun Context.pixelsToDp(pixels: Int): Int { .toInt() } -fun Context.dpToPixels(dp: Float): Float = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.resources.displayMetrics) - -fun Context.pixelsToDp(pixels: Float): Float = - (pixels / (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)) - tailrec fun Context?.getActivity(): Activity? = this as? Activity ?: (this as? ContextWrapper)?.baseContext?.getActivity() @@ -136,19 +116,6 @@ fun MaterialCardView.setMargins( this.layoutParams = params } -fun Activity.setSystemBarTextColor(setTextDark: Boolean) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val flags = this.window.decorView.systemUiVisibility - // Update the SystemUiVisibility dependening on whether we want a Light or Dark theme. - this.window.decorView.systemUiVisibility = - if (setTextDark) { - flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() - } else { - flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - } - } -} - fun View.hideKeyboard() { val inputMethodManager = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? InputMethodManager @@ -185,21 +152,6 @@ fun Fragment.shareText(text: String) { requireContext().shareText(text) } -fun Context.safeStartActivity( - intent: Intent, - options: Bundle? = null, - fallback: ((ActivityNotFoundException) -> Unit)? = null, - finally: VoidCallback? = null, -) { - try { - this.startActivity(intent, options) - } catch (ex: ActivityNotFoundException) { - fallback?.invoke(ex) - } finally { - finally?.invoke() - } -} - fun View.getString(resId: Int, vararg formatArgs: Any?): String { return context.getString(resId, *formatArgs) } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Any.kt b/app/src/main/java/com/tangem/tap/common/extensions/ValueCallback.kt similarity index 100% rename from app/src/main/java/com/tangem/tap/common/extensions/Any.kt rename to app/src/main/java/com/tangem/tap/common/extensions/ValueCallback.kt diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index cda489f536..97997ba97f 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -21,6 +21,7 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ +@Suppress("MagicNumber") suspend fun WalletManager.safeUpdate(): Result = try { val scanResponse = store.state.globalState.scanResponse @@ -72,8 +73,7 @@ fun WalletManager?.getAddressData(): AddressData? { val wallet = this?.wallet ?: return null val addressDataList = wallet.createAddressesData() - return if (addressDataList.isEmpty()) null - else addressDataList[0] + return if (addressDataList.isEmpty()) null else addressDataList[0] } fun WalletManager.Companion.stub(): T { diff --git a/app/src/main/java/com/tangem/tap/common/feature/Feature.kt b/app/src/main/java/com/tangem/tap/common/feature/Feature.kt index ce1313c772..1eb07546b4 100644 --- a/app/src/main/java/com/tangem/tap/common/feature/Feature.kt +++ b/app/src/main/java/com/tangem/tap/common/feature/Feature.kt @@ -4,5 +4,5 @@ package com.tangem.tap.common.feature [REDACTED_AUTHOR] */ interface Feature { - fun featureIsSwitchedOn():Boolean + fun featureIsSwitchedOn(): Boolean } \ 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 index 589597238f..c6f5bafd0a 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -48,6 +48,9 @@ class AdditionalFeedbackInfo { var fee: String = "" var token: String = "" + private val Address.name: String + get() = type.javaClass.simpleName + fun setCardInfo(data: ScanResponse) { cardId = data.card.cardId cardBlockchain = data.walletData?.blockchain ?: "" @@ -108,6 +111,7 @@ class AdditionalFeedbackInfo { } } + @Suppress("MagicNumber") private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String { return if (addresses.size == 1) { getExploreUrl(address) @@ -117,7 +121,4 @@ class AdditionalFeedbackInfo { .joinToString("\n") } } - - private val Address.name: String - get() = type.javaClass.simpleName } \ 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 index 8a426cccfc..452eb173aa 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt @@ -15,6 +15,7 @@ interface FeedbackData { fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String + @Suppress("MagicNumber") fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String { return StringBuilder().apply { append(context.getString(mainMessageResId)) @@ -42,6 +43,7 @@ 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) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt index b44db4004b..c6513c9188 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt @@ -6,7 +6,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.domain.common.TapWorkarounds import com.tangem.tap.common.extensions.sendEmail import com.tangem.tap.common.log.TangemLogCollector -import com.tangem.tap.common.zendesk.ZendeskConfig +import com.tangem.datasource.config.models.ZendeskConfig import com.tangem.tap.foregroundActivityObserver import com.tangem.tap.persistence.PreferencesStorage import com.tangem.tap.withForegroundActivity diff --git a/app/src/main/java/com/tangem/tap/common/images/Coil.kt b/app/src/main/java/com/tangem/tap/common/images/Coil.kt index 5224c40df0..4e5a88d6e0 100644 --- a/app/src/main/java/com/tangem/tap/common/images/Coil.kt +++ b/app/src/main/java/com/tangem/tap/common/images/Coil.kt @@ -30,13 +30,11 @@ fun createCoilImageLoader( } ) .build() - } } .build() } - private class CoilTimberLogger : Logger { override var level: Int = Log.DEBUG diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt index 1999c35855..98a06327bb 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt @@ -17,13 +17,13 @@ class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) { private lateinit var twinsCardWidget: TwinsCardWidget private val binding: TestLeapfrogFragmentBinding by viewBinding(TestLeapfrogFragmentBinding::bind) - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) exitTransition = inflater.inflateTransition(R.transition.fade) } + @Suppress("MagicNumber") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt b/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt index da69eed9ae..4a86caa20d 100644 --- a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt +++ b/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt @@ -16,11 +16,6 @@ import me.dm7.barcodescanner.zxing.ZXingScannerView [REDACTED_AUTHOR] */ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { - companion object { - val SCAN_QR_REQUEST_CODE = 1001 - val SCAN_RESULT = "scanResult" - } - private lateinit var mScannerView: ZXingScannerView override fun onCreate(state: Bundle?) { @@ -29,7 +24,6 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { setContentView(mScannerView) if (!permissionIsGranted()) requestPermission() - } override fun onResume() { @@ -66,4 +60,9 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { private fun requestPermission() { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE) } + + companion object { + const val SCAN_QR_REQUEST_CODE = 1001 + const val SCAN_RESULT = "scanResult" + } } \ No newline at end of file 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 9f7c34b90f..820f5b3d3f 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 @@ -39,6 +39,7 @@ import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware import com.tangem.tap.features.walletSelector.redux.WalletSelectorState import com.tangem.tap.features.welcome.redux.WelcomeMiddleware import com.tangem.tap.features.welcome.redux.WelcomeState +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import org.rekotlin.Middleware import org.rekotlin.StateType @@ -68,6 +69,12 @@ data class AppState( val domainNetworks: NetworkServices get() = domainState.globalState.networkServices + val featureRepositoryProvider: FeatureRepositoryProvider + get() = FeatureRepositoryProvider( + tangemTechApi = domainNetworks.tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), + ) + companion object { fun getMiddleware(): List> { return listOf( diff --git a/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt b/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt new file mode 100644 index 0000000000..5794edffb9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.common.redux + +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.tap.features.home.data.HomeRepositoryImpl +import com.tangem.tap.features.home.domain.HomeRepository +import com.tangem.tap.features.wallet.data.WalletRepositoryImpl +import com.tangem.tap.features.wallet.domain.WalletRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider + +class FeatureRepositoryProvider(tangemTechApi: TangemTechApi, dispatchers: CoroutineDispatcherProvider) { + + val walletRepository: WalletRepository = WalletRepositoryImpl(tangemTechApi, dispatchers) + + val homeRepository: HomeRepository = HomeRepositoryImpl(tangemTechApi, dispatchers) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt index a32545d675..4a7cb7b8f8 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt @@ -88,6 +88,7 @@ val notificationsMiddleware: Middleware = { dispatch, state -> } } +@Suppress("ComplexMethod") private fun handleNotificationAction(action: Action) { if (action is Debug && !BuildConfig.DEBUG) return 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 1f51374cae..4017f9b32f 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 @@ -13,9 +13,9 @@ import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.ToastNotificationAction -import com.tangem.tap.common.zendesk.ZendeskConfig +import com.tangem.datasource.config.models.ZendeskConfig import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.configurable.config.ConfigManager +import com.tangem.datasource.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.features.details.redux.SecurityOption @@ -47,7 +47,7 @@ sealed class GlobalAction : Action { * For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start. * See more Onboarding.Start, CheckForUnfinishedBackup */ - object StartForUnfinishedBackup : Onboarding() + data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding() object Stop : Onboarding() } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt similarity index 92% rename from app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt rename to app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index dfcdf942bf..b880c71872 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -4,7 +4,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.extensions.ifNotNull -import com.tangem.common.services.Result import com.tangem.domain.common.LogConfig import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -31,10 +30,8 @@ import org.rekotlin.DispatchFunction import org.rekotlin.Middleware import java.util.* -class GlobalMiddleware { - companion object { - val handler = globalMiddlewareHandler - } +object GlobalMiddleware { + val handler = globalMiddlewareHandler } private val globalMiddlewareHandler: Middleware = { dispatch, appState -> @@ -46,6 +43,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } } +@Suppress("LongMethod", "ComplexMethod") private fun handleAction(action: Action, appState: () -> AppState?, dispatch: DispatchFunction) { when (action) { is GlobalAction.ScanFailsCounter.ChooseBehavior -> { @@ -74,7 +72,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di store.state.globalState.warningManager?.let { if (it.hideWarning(action.warning)) { if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) { - //TODO: No appropriate warningMessage identification. Make it better later + // TODO: No appropriate warningMessage identification. Make it better later store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) } @@ -101,7 +99,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di // if config not set -> try to get it based on a scanResponse.productType val unsafeZendeskConfig = action.zendeskConfig ?: when { - scanResponse?.isSaltPay() == true -> config.saltPayConfig?.zendesk + scanResponse?.cardTypesResolver?.isSaltPay() == true -> config.saltPayConfig?.zendesk else -> config.zendesk } @@ -187,23 +185,20 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di } is GlobalAction.FetchUserCountry -> { scope.launch { - val techService = store.state.domainNetworks.tangemTechService - when (val result = techService.userCountry()) { - is Result.Success -> { + // TODO("After adding DI") get dependencies by DI + runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() } + .onSuccess { store.dispatchOnMain( - GlobalAction.FetchUserCountry.Success( - countryCode = result.data.code.lowercase(), - ), + GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()), ) } - is Result.Failure -> { + .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 543e734f8f..7934a195f6 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 @@ -9,8 +9,8 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.proxy.AppStateHolder import org.rekotlin.Action +@Suppress("LongMethod", "ComplexMethod") fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolder): GlobalState { - if (action !is GlobalAction) return state.globalState val globalState = state.globalState @@ -39,6 +39,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde globalState.copy(scanResponse = action.scanResponse) } is GlobalAction.ChangeAppCurrency -> { + appStateHolder.appFiatCurrency = action.appCurrency globalState.copy(appCurrency = action.appCurrency) } is GlobalAction.RestoreAppCurrency.Success -> { 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 b897f1b5dc..845715809d 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 @@ -6,7 +6,7 @@ import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapWalletManager -import com.tangem.tap.domain.configurable.config.ConfigManager +import com.tangem.datasource.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt index 69547d020f..ab7aa07259 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt @@ -10,7 +10,7 @@ import java.lang.ref.WeakReference data class FragmentShareTransition( val shareElements: List, val enterTransitionSet: TransitionSet, - val exitTransitionSet: TransitionSet + val exitTransitionSet: TransitionSet, ) /** @@ -24,12 +24,12 @@ class ShareElement(view: View, name: String? = null) { init { name?.let { view.transitionName = it } elementName = view.transitionName - ?: throw UnsupportedOperationException("ShareElement require the name") + ?: throw UnsupportedOperationException("ShareElement require the name") wView = WeakReference(view) } companion object { - val imvFrontCard = "imv_front_card" - val imvBackCard = "imv_back_card" + const val imvFrontCard = "imv_front_card" + const val imvBackCard = "imv_back_card" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt index 3e4f3a9702..71f3274e4c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.redux.navigation import android.net.Uri +import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import org.rekotlin.Action import java.lang.ref.WeakReference @@ -10,6 +11,7 @@ sealed class NavigationAction : Action { val screen: AppScreen, val fragmentShareTransition: FragmentShareTransition? = null, val addToBackstack: Boolean = true, + val bundle: Bundle? = null, ) : NavigationAction() data class PopBackTo( diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 28357c1efd..bf5df6a9a9 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -25,6 +25,7 @@ val navigationMiddleware: Middleware = { _, state -> screen = action.screen, addToBackstack = action.addToBackstack, fgShareTransition = action.fragmentShareTransition, + bundle = action.bundle, ) } is NavigationAction.PopBackTo -> { @@ -33,8 +34,13 @@ val navigationMiddleware: Middleware = { _, state -> AppScreen.Home, AppScreen.Welcome, -> { - navState?.activity?.get()?.popBackTo(screen, inclusive = true) - store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + if (navState?.backStack?.contains(screen) == false) { + // Pop back to activity + navState.activity?.get()?.popBackTo(screen = null, inclusive = true) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + } else { + navState?.activity?.get()?.popBackTo(screen, action.inclusive) + } } else -> { navState?.activity?.get()?.popBackTo(screen, action.inclusive) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt index b6f7522586..7febcfa59c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt @@ -4,14 +4,11 @@ import com.tangem.tap.common.extensions.getPreviousScreen import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class NavigationReducer { - companion object { - fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state) - } +object NavigationReducer { + fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): NavigationState { - val navigationAction = action as? NavigationAction ?: return state.navigationState val navState = state.navigationState diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index cf60dc79f6..0b40b7bd8e 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -23,6 +23,7 @@ enum class AppScreen( WalletConnectSessions, QrScan, ReferralProgram, + Swap, Welcome, SaveWallet(isDialogFragment = true), WalletSelector(isDialogFragment = true), diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 95b6a0e7db..9685dd54b7 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -10,8 +10,9 @@ import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.common.shop.data.ProductType import com.tangem.tap.common.shop.data.TangemProduct import com.tangem.tap.common.shop.data.TotalSum +import com.tangem.tap.common.shop.googlepay.GooglePayService import com.tangem.tap.common.shop.shopify.ShopifyService -import com.tangem.tap.common.shop.shopify.ShopifyShop +import com.tangem.datasource.config.models.ShopifyShop import com.tangem.tap.common.shop.shopify.data.CheckoutItem import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -87,7 +88,8 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { fun buyWithGooglePay(productType: ProductType) { val totalPrice = checkouts[productType]!!.totalPriceV2.amount googlePayService.payWithGooglePay( - totalPriceCents = totalPrice, currencyCode = checkouts[productType]!!.currencyCode.name, + totalPriceCents = totalPrice, + currencyCode = checkouts[productType]!!.currencyCode.name, merchantID = shopifyService.shop.merchantID, ) } @@ -188,7 +190,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { appliedDiscount = it.getAppliedDiscount(), ), - ) + ) } return Result.failure(result.exceptionOrNull()!!) } diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt index 13c3913ebe..757b207741 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.common.shop +package com.tangem.tap.common.shop.googlepay import android.app.Activity import android.app.Activity.RESULT_CANCELED @@ -6,9 +6,12 @@ import android.app.Activity.RESULT_OK import android.content.Intent import android.util.Log import com.google.android.gms.common.api.ApiException -import com.google.android.gms.wallet.* +import com.google.android.gms.wallet.AutoResolveHelper +import com.google.android.gms.wallet.IsReadyToPayRequest +import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.PaymentDataRequest +import com.google.android.gms.wallet.PaymentsClient import com.tangem.common.core.TangemSdkError -import com.tangem.tap.common.shop.googlepay.GooglePayUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONException @@ -22,7 +25,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a // var responseCallback: ((Result) -> Unit)? = null suspend fun checkIfGooglePayAvailable(): Result { - val isReadyToPayJson = GooglePayUtil.isReadyToPayRequest() ?: return Result.success(false) val request = IsReadyToPayRequest.fromJson(isReadyToPayJson.toString()) @@ -48,7 +50,7 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a totalPriceCents, currencyCode = currencyCode, countryCode = "RU", - merchantID = merchantID + merchantID = merchantID, ) if (paymentDataRequestJson == null) { Timber.e("RequestPayment: can't fetch payment data request") @@ -57,7 +59,9 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString()) AutoResolveHelper.resolveTask( - paymentsClient.loadPaymentData(request), activity, LOAD_PAYMENT_DATA_REQUEST_CODE + paymentsClient.loadPaymentData(request), + activity, + LOAD_PAYMENT_DATA_REQUEST_CODE, ) } @@ -81,7 +85,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a } else { Result.failure(Exception("$statusCode")) } - } else -> Result.failure(Exception("Unknown Status")) } @@ -117,7 +120,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a .getString("token") return GooglePayResponse(address, token) - } catch (e: JSONException) { Log.e("handlePaymentSuccess", "Error: " + e.toString()) } @@ -144,5 +146,5 @@ data class Address( val address3: String, val locality: String, val administrativeArea: String, - val sortingCode: String + val sortingCode: String, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt index 610ab10c9d..82509e1180 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt @@ -14,20 +14,7 @@ object GooglePayUtil { put("apiVersionMinor", 0) } - private fun gatewayTokenizationSpecification(merchantID: String): JSONObject { - return JSONObject().apply { - put("type", "PAYMENT_GATEWAY") - put( - "parameters", JSONObject( - mapOf( - "gateway" to "shopify", - "gatewayMerchantId" to merchantID - ) - ) - ) - } - } - + @Suppress("MagicNumber") private val allowedCardNetworks = JSONArray( listOf( "AMEX", @@ -35,27 +22,46 @@ object GooglePayUtil { "INTERAC", "JCB", "MASTERCARD", - "VISA" - ) + "VISA", + ), ) private val allowedCardAuthMethods = JSONArray( listOf( "PAN_ONLY", - "CRYPTOGRAM_3DS" - ) + "CRYPTOGRAM_3DS", + ), ) + private val merchantInfo: JSONObject = JSONObject().put("merchantName", "Example Merchant") + + private fun gatewayTokenizationSpecification(merchantID: String): JSONObject { + return JSONObject().apply { + put("type", "PAYMENT_GATEWAY") + put( + "parameters", + JSONObject( + mapOf( + "gateway" to "shopify", + "gatewayMerchantId" to merchantID, + ), + ), + ) + } + } + private fun baseCardPaymentMethod(): JSONObject { return JSONObject().apply { - val parameters = JSONObject().apply { put("allowedAuthMethods", allowedCardAuthMethods) put("allowedCardNetworks", allowedCardNetworks) put("billingAddressRequired", true) - put("billingAddressParameters", JSONObject().apply { - put("format", "FULL") - }) + put( + "billingAddressParameters", + JSONObject().apply { + put("format", "FULL") + }, + ) } put("type", "CARD") @@ -83,7 +89,6 @@ object GooglePayUtil { baseRequest.apply { put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod())) } - } catch (e: JSONException) { null } @@ -92,7 +97,7 @@ object GooglePayUtil { private fun getTransactionInfo( price: String, countryCode: String, - currencyCode: String + currencyCode: String, ): JSONObject { return JSONObject().apply { put("totalPrice", price) @@ -102,15 +107,11 @@ object GooglePayUtil { } } - private val merchantInfo: JSONObject = - JSONObject().put("merchantName", "Example Merchant") - - fun getPaymentDataRequest( price: String, countryCode: String, currencyCode: String, - merchantID: String + merchantID: String, ): JSONObject? { try { return baseRequest.apply { @@ -133,5 +134,4 @@ object GooglePayUtil { } } - const val PAYMENTS_ENVIRONMENT = WalletConstants.ENVIRONMENT_TEST \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt index a33fb82b84..71081fc7b1 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt @@ -7,6 +7,7 @@ import com.shopify.buy3.RetryHandler import com.shopify.buy3.Storefront.* import com.shopify.graphql.support.ID import com.shopify.graphql.support.Input +import com.tangem.datasource.config.models.ShopifyShop import com.tangem.tap.common.shop.shopify.data.CheckoutItem import com.tangem.tap.common.shop.shopify.data.checkoutFieldsFragment import com.tangem.tap.common.shop.shopify.data.collectionFieldsFragment @@ -16,11 +17,10 @@ import java.util.concurrent.TimeUnit import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine - +@Suppress("LargeClass") class ShopifyService(private val application: Application, val shop: ShopifyShop) { val client: GraphClient by lazy { initClient() } - suspend fun getShopName(): Result { val query = query { rootQuery: QueryRootQuery -> rootQuery @@ -40,16 +40,15 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } } + @Suppress("MagicNumber") suspend fun getProducts(collectionTitleFilter: String? = null): Result> { val filter = collectionTitleFilter?.let { "title:\"$it\"" } val query = query { rootQuery: QueryRootQuery -> - rootQuery - .collections( - { arg -> arg.first(250).query(filter) }, - ) { collectionConnectionQuery -> - collectionConnectionQuery.collectionFieldsFragment() - } + rootQuery.collections( + { arg -> arg.first(250).query(filter) }, + CollectionConnectionQuery::collectionFieldsFragment, + ) } return when (val result = queryAsync(query)) { is GraphCallResult.Success -> { @@ -66,7 +65,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } suspend fun checkout(pollUntilOrder: Boolean, checkoutID: ID): Result { - val query = query { rootQuery: QueryRootQuery -> rootQuery .node(checkoutID) { query -> @@ -78,7 +76,8 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } } val retryHandler = RetryHandler.build( - 1, TimeUnit.SECONDS + delay = 1, + timeUnit = TimeUnit.SECONDS, ) { this.retryWhen { result -> when (result) { @@ -100,7 +99,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } else { Result.failure(ShopifyError.Unknown) } - } is GraphCallResult.Failure -> { Result.failure(result.error) @@ -110,10 +108,8 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop suspend fun createCheckout( checkoutItems: List, - checkoutID: ID? = null + checkoutID: ID? = null, ): Result { - - val storefrontLineItems: MutableList = checkoutItems .map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList() @@ -121,13 +117,14 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutLineItemsReplace( - storefrontLineItems, checkoutID + storefrontLineItems, + checkoutID, ) { payloadQuery: CheckoutLineItemsReplacePayloadQuery -> payloadQuery .checkout { checkoutQuery: CheckoutQuery -> checkoutQuery.checkoutFieldsFragment() } - .userErrors() { userErrorQuery: CheckoutUserErrorQuery -> + .userErrors { userErrorQuery: CheckoutUserErrorQuery -> userErrorQuery .field() .message() @@ -137,18 +134,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } else { val input = CheckoutCreateInput() .setLineItemsInput( - Input.value(storefrontLineItems) + Input.value(storefrontLineItems), ) mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutCreate( - input + input, ) { payloadQuery: CheckoutCreatePayloadQuery -> payloadQuery .checkout { checkoutQuery: CheckoutQuery -> checkoutQuery.checkoutFieldsFragment() } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> + .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> userErrorQuery .field() .message() @@ -163,13 +160,14 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop val query = mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutDiscountCodeApplyV2( - discountCode, checkoutID + discountCode, + checkoutID, ) { payloadQuery: CheckoutDiscountCodeApplyV2PayloadQuery -> payloadQuery .checkout { checkoutQuery: CheckoutQuery -> checkoutQuery.checkoutFieldsFragment() } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> + .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> userErrorQuery .field() .message() @@ -183,44 +181,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop val query = mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutDiscountCodeRemove( - checkoutID + checkoutID, ) { payloadQuery: CheckoutDiscountCodeRemovePayloadQuery -> payloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } - return runCheckoutMutation(query) - } - - suspend fun updateAddress( - address: MailingAddress, - checkoutID: ID, - waitForShippingRates: Boolean - ): Result { - val input = MailingAddressInput() - .setAddress1(address.address1) - .setAddress2(address.address2) - .setCity(address.city) - .setCountry(address.country) - .setFirstName(address.firstName) - .setLastName(address.lastName) - .setPhone(address.phone) - .setProvince(address.province) - .setZip(address.zip) - - val query = mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutShippingAddressUpdateV2( - input, checkoutID - ) { shippingAddressUpdatePayloadQuery: CheckoutShippingAddressUpdateV2PayloadQuery -> - shippingAddressUpdatePayloadQuery .checkout { checkoutQuery: CheckoutQuery -> checkoutQuery.checkoutFieldsFragment() } @@ -231,59 +194,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } } } - - return runCheckoutMutation(query) - } - - suspend fun updateEmail(email: String?, checkoutID: ID): Result { - val query = mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutEmailUpdateV2( - checkoutID, email - ) { emailUpdatePayloadQuery: CheckoutEmailUpdateV2PayloadQuery -> - emailUpdatePayloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } - return runCheckoutMutation(query) - } - - suspend fun updateShippingRate(handle: String?, checkoutID: ID): Result { - val query = mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutShippingLineUpdate( - checkoutID, handle - ) { shippingLineUpdatePayloadQuery: CheckoutShippingLineUpdatePayloadQuery -> - shippingLineUpdatePayloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } return runCheckoutMutation(query) } suspend fun completeWithTokenizedPayment( payment: TokenizedPaymentInputV3, - checkoutID: ID + checkoutID: ID, ): Result { - val query = mutation { mutationQuery: MutationQuery -> mutationQuery .checkoutCompleteWithTokenizedPaymentV3( - checkoutID, payment + checkoutID, + payment, ) { payloadQuery: CheckoutCompleteWithTokenizedPaymentV3PayloadQuery -> payloadQuery .payment { paymentQuery: PaymentQuery -> @@ -295,7 +217,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop checkoutQuery .ready() } - .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery -> + .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> userErrorQuery .field() .message() @@ -305,10 +227,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop return runCheckoutMutation(query) } - fun startGooglePaySession() { -// PaySession() - } - private suspend fun runCheckoutMutation(mutation: MutationQuery): Result { return when (val result = mutationQueryAsync(mutation)) { is GraphCallResult.Success -> { @@ -322,13 +240,13 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop Result.success(checkout) } - is GraphCallResult.Failure -> Result.failure(result.error) + is GraphCallResult.Failure -> Result.failure(result.error) } } private suspend fun queryAsync( query: QueryRootQuery, - retryHandler: RetryHandler + retryHandler: RetryHandler, ): GraphCallResult = withContext(Dispatchers.IO) { suspendCoroutine { continuation -> @@ -358,12 +276,11 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop } } - private fun initClient(): GraphClient { return GraphClient.build( application, shop.domain, - shop.storefrontApiKey + shop.storefrontApiKey, ) { // httpCache(application.filesDir) { // cacheMaxSizeBytes = (1024 * 1024 * 10) @@ -371,13 +288,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop // HttpCachePolicy.Default.CACHE_FIRST.expireAfter(20, TimeUnit.MINUTES) // } } - } } - sealed class ShopifyError : Throwable() { object Unknown : ShopifyError() - object GooglePayFailed : ShopifyError() - class UserError(val errorMessage: String) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt index 849c1f33c3..7565db2447 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt @@ -2,23 +2,24 @@ package com.tangem.tap.common.shop.shopify.data import com.shopify.buy3.Storefront +@Suppress("LongMethod", "MagicNumber") fun Storefront.CheckoutQuery.checkoutFieldsFragment() { - // id() ready() webUrl() currencyCode() lineItemsSubtotalPrice { it.amount() } totalPriceV2 { it.currencyCode() - it.amount() } + it.amount() + } lineItems({ arg -> arg.first(250) }) { it.edges { it.node { // it.id() it.title() it.quantity() - it.variant() { - it.priceV2() { it.amount() } + it.variant { + it.priceV2 { it.amount() } } } } diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt index 572a7b68c7..1527fc9558 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt @@ -2,13 +2,15 @@ package com.tangem.tap.common.shop.shopify.data import com.shopify.buy3.Storefront +@Suppress("MagicNumber") fun Storefront.CollectionConnectionQuery.collectionFieldsFragment() { edges { collectionEdgeQuery -> collectionEdgeQuery .node { collectionQuery -> collectionQuery .title() - .products({ arg -> arg.first(250) } + .products( + { arg -> arg.first(250) }, ) { productConnectionQuery -> productConnectionQuery .edges { productEdgeQuery -> diff --git a/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt b/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt index 69f2114528..d224143c98 100644 --- a/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt +++ b/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt @@ -17,15 +17,15 @@ import com.tangem.wallet.R [REDACTED_AUTHOR] */ class MaxAmountSnackbar( - parent: ViewGroup, - content: MaxAmountSnackbarView + parent: ViewGroup, + content: MaxAmountSnackbarView ) : BaseTransientBottomBar(parent, content, content) { companion object { fun make(view: View, onClick: () -> Unit): MaxAmountSnackbar { val parent = view.findSuitableParent() ?: throw IllegalArgumentException( - "No suitable parent found from the given view. Please provide a valid view." + "No suitable parent found from the given view. Please provide a valid view." ) val inflater = LayoutInflater.from(view.context) val customView = inflater.inflate(R.layout.view_snackbar_max_amount, parent, false) as MaxAmountSnackbarView @@ -57,14 +57,13 @@ class MaxAmountSnackbar( } while (view != null) return fallback } - } } class MaxAmountSnackbarView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), ContentViewCallback { init { @@ -72,10 +71,9 @@ class MaxAmountSnackbarView @JvmOverloads constructor( clipToPadding = false } - override fun animateContentIn(delay: Int, duration: Int) { } override fun animateContentOut(delay: Int, duration: Int) { } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt b/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt index 689095c534..2201e794cc 100644 --- a/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt @@ -8,13 +8,23 @@ import java.util.regex.Pattern [REDACTED_AUTHOR] */ class DecimalDigitsInputFilter( - digitsBeforeDecimal: Int, - digitsAfterDecimal: Int, - private val decimalSeparator: String + digitsBeforeDecimal: Int, + digitsAfterDecimal: Int, + private val decimalSeparator: String, ) : InputFilter { - private val pattern: Pattern = Pattern.compile("(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?") + private val pattern: Pattern = Pattern.compile( + "(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})" + + "((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?", + ) - override fun filter(source: CharSequence, sourceStart: Int, sourceEnd: Int, destination: Spanned, destinationStart: Int, destinationEnd: Int): CharSequence? { + override fun filter( + source: CharSequence, + sourceStart: Int, + sourceEnd: Int, + destination: Spanned, + destinationStart: Int, + destinationEnd: Int, + ): CharSequence? { val destString = destination.toString() val prefix = destString.substring(0, destinationStart) val suffix = destString.substring(destinationEnd, destString.length) @@ -47,4 +57,4 @@ class DecimalDigitsInputFilter( } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/text/Truncate.kt b/app/src/main/java/com/tangem/tap/common/text/Truncate.kt index 3ed50ca1d2..0aeb17d6d2 100644 --- a/app/src/main/java/com/tangem/tap/common/text/Truncate.kt +++ b/app/src/main/java/com/tangem/tap/common/text/Truncate.kt @@ -16,9 +16,9 @@ interface Truncate { companion object { fun create(type: TruncateType): Truncate { return when (type) { - TruncateType.START -> TruncateStart() - TruncateType.MIDDLE -> TruncateMiddle() - TruncateType.END -> TruncateEnd() + TruncateType.START -> TruncateStart() + TruncateType.MIDDLE -> TruncateMiddle() + TruncateType.END -> TruncateEnd() } } } @@ -127,10 +127,10 @@ fun TextView.truncateWith(text: String, type: TruncateType, with: String = "..." } fun TextView.truncateStartWith(text: String, with: String = "..."): String = - this.truncateWith(text, TruncateType.START, with) + this.truncateWith(text, TruncateType.START, with) fun TextView.truncateMiddleWith(text: String, with: String = "..."): String = - this.truncateWith(text, TruncateType.MIDDLE, with) + this.truncateWith(text, TruncateType.MIDDLE, with) fun TextView.truncateEndWith(text: String, with: String = "..."): String = - this.truncateWith(text, TruncateType.END, with) \ No newline at end of file + this.truncateWith(text, TruncateType.END, with) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt index 4b15ee3cb5..56f80d5d78 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt @@ -38,6 +38,7 @@ open class IndeterminateProgressButtonWidget( when (progressState) { ProgressState.Done, ProgressState.Error -> switchToNone() ProgressState.Loading -> switchToProgress() + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt index 7bccdffc08..6a5bd17f60 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt @@ -17,6 +17,7 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ +@Suppress("MagicNumber") class RefreshBalanceWidget( private val root: ViewGroup, ) : ViewStateWidget { @@ -57,6 +58,7 @@ class RefreshBalanceWidget( viewSwitcher.showNext() } + @Suppress("MagicNumber") private fun animateState(state: ProgressState) { when (state) { ProgressState.Done, ProgressState.Error -> { @@ -65,15 +67,25 @@ class RefreshBalanceWidget( } ProgressState.Loading -> { progressViewAnimation = RotateAnimation( - 0f, 360f, - Animation.RELATIVE_TO_SELF, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f, + /* fromDegrees = */ + 0f, + /* toDegrees = */ + 360f, + /* pivotXType = */ + Animation.RELATIVE_TO_SELF, + /* pivotXValue = */ + 0.5f, + /* pivotYType = */ + Animation.RELATIVE_TO_SELF, + /* pivotYValue = */ + 0.5f, ) progressViewAnimation?.duration = 700 progressViewAnimation?.interpolator = AccelerateInterpolator() progressViewAnimation?.repeatCount = -1 progressViewAnimation?.let { arrowView.startAnimation(it) } } + else -> {} } } @@ -93,14 +105,19 @@ class RefreshBalanceWidget( private fun getState(): ProgressState = if (isArrowRefreshActive()) ProgressState.Done else ProgressState.Loading } +@Suppress("MagicNumber") class ShowAnimation : AnimationSet(true) { init { addAnimation( ScaleAnimation( - 0f, 1f, - 0f, 1f, - Animation.RELATIVE_TO_SELF, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f, + 0f, + 1f, + 0f, + 1f, + Animation.RELATIVE_TO_SELF, + 0.5f, + Animation.RELATIVE_TO_SELF, + 0.5f, ), ) addAnimation(AlphaAnimation(0f, 1f)) diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt index 48ba0a7291..10b6a1f7a6 100644 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt @@ -2,7 +2,6 @@ package com.tangem.tap.common.toggleWidget import android.view.View - /** [REDACTED_AUTHOR] */ diff --git a/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt index 32785f0147..e250846a42 100644 --- a/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt +++ b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt @@ -1,33 +1,13 @@ package com.tangem.tap.common.transitions -import androidx.transition.* - -/** -[REDACTED_AUTHOR] - */ -class FrontCardEnterTransition : TransitionSet() { - init { - ordering = ORDERING_TOGETHER; - addTransition(Fade()) - addTransition(ChangeImageTransform()) // изменения внутри ImageView - addTransition(ChangeTransform()) // изменение размеров, углов накона - addTransition(ChangeBounds()) // изменение положения - } -} - -class FrontCardExitTransition : TransitionSet() { - init { - ordering = ORDERING_TOGETHER; - addTransition(Fade()) - addTransition(ChangeImageTransform()) - addTransition(ChangeTransform()) - addTransition(ChangeBounds()) - } -} +import androidx.transition.ChangeBounds +import androidx.transition.ChangeTransform +import androidx.transition.Fade +import androidx.transition.TransitionSet class HomeToOnboardingTransition : TransitionSet() { init { - ordering = ORDERING_TOGETHER; + ordering = ORDERING_TOGETHER addTransition(Fade()) addTransition(ChangeTransform()) addTransition(ChangeBounds()) @@ -36,7 +16,7 @@ class HomeToOnboardingTransition : TransitionSet() { class InternalNoteLayoutTransition : TransitionSet() { init { - ordering = ORDERING_TOGETHER; + ordering = ORDERING_TOGETHER addTransition(ChangeTransform()) addTransition(ChangeBounds()) addTransition(Fade()) diff --git a/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt index 8cd6e7e5ba..365f235f11 100644 --- a/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt @@ -6,54 +6,49 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.store import com.tangem.wallet.R -class SimpleAlertDialog { - companion object { - fun create( - titleRes: Int? = null, - messageRes: Int? = null, - title: String? = null, - message: String? = null, - primaryButtonRes: Int = R.string.common_ok, - context: Context, - ): AlertDialog { - return SimpleCancelableAlertDialog.create( - titleRes = titleRes, - messageRes = messageRes, - title = title, - message = message, - primaryButtonRes = primaryButtonRes, - secondaryButtonRes = null, - context = context - ) - } +object SimpleAlertDialog { + fun create( + titleRes: Int? = null, + messageRes: Int? = null, + title: String? = null, + message: String? = null, + primaryButtonRes: Int = R.string.common_ok, + context: Context, + ): AlertDialog { + return SimpleCancelableAlertDialog.create( + titleRes = titleRes, + messageRes = messageRes, + title = title, + message = message, + primaryButtonRes = primaryButtonRes, + secondaryButtonRes = null, + context = context, + ) } } -class SimpleCancelableAlertDialog { - companion object { - fun create( - titleRes: Int? = null, - messageRes: Int? = null, - title: String? = null, - message: String? = null, - primaryButtonRes: Int = R.string.common_ok, - secondaryButtonRes: Int? = R.string.common_cancel, - primaryButtonAction: () -> Unit = {}, - secondaryButtonAction: () -> Unit = {}, - context: Context, - ): AlertDialog { - - return AlertDialog.Builder(context).apply { - setTitle(titleRes?.let { context.getString(it) } ?: title ) - setMessage(messageRes?.let { context.getString(it) } ?: message) - setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() } - if (secondaryButtonRes != null) { - setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction()} - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } +object SimpleCancelableAlertDialog { + fun create( + titleRes: Int? = null, + messageRes: Int? = null, + title: String? = null, + message: String? = null, + primaryButtonRes: Int = R.string.common_ok, + secondaryButtonRes: Int? = R.string.common_cancel, + primaryButtonAction: () -> Unit = {}, + secondaryButtonAction: () -> Unit = {}, + context: Context, + ): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(titleRes?.let { context.getString(it) } ?: title) + setMessage(messageRes?.let { context.getString(it) } ?: message) + setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() } + if (secondaryButtonRes != null) { + setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction() } + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt index cfd5c2b5c2..d66ac2ed73 100644 --- a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt @@ -10,7 +10,11 @@ import java.util.* class PayIdManager { - suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result = withContext(Dispatchers.IO) { + @Suppress("MagicNumber") + suspend fun verifyPayId( + payId: String, + blockchain: Blockchain, + ): Result = withContext(Dispatchers.IO) { val splitPayId = payId.split("\$") val user = splitPayId[0] val baseUrl = "https://${splitPayId[1]}/" @@ -22,25 +26,26 @@ class PayIdManager { Blockchain.XRP -> "XRPL" Blockchain.RSK -> "RSK" else -> this.currency - }.toLowerCase() + }.lowercase(Locale.getDefault()) } companion object { - val payIdRegExp = "^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$".toRegex() + private val payIdRegExp = ("^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9]" + + "(?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$").toRegex() val payIdSupported: EnumSet = EnumSet.of( - Blockchain.XRP, - Blockchain.Ethereum, - Blockchain.Bitcoin, - Blockchain.Litecoin, - Blockchain.Stellar, - Blockchain.Cardano, - Blockchain.CardanoShelley, - Blockchain.Ducatus, - Blockchain.BitcoinCash, - Blockchain.Binance, - Blockchain.RSK, - Blockchain.Tezos + Blockchain.XRP, + Blockchain.Ethereum, + Blockchain.Bitcoin, + Blockchain.Litecoin, + Blockchain.Stellar, + Blockchain.Cardano, + Blockchain.CardanoShelley, + Blockchain.Ducatus, + Blockchain.BitcoinCash, + Blockchain.Binance, + Blockchain.RSK, + Blockchain.Tezos, ) fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false diff --git a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt index 7cee3bcfd9..3d2a5764e2 100644 --- a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt @@ -1,48 +1,49 @@ package com.tangem.tap.domain import com.tangem.common.services.Result +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.domain.common.ThrottlerWithValues -import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.store +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext import java.math.BigDecimal -//TODO: refactoring: move to domain -class RatesRepository { - - private val tangemTechService: TangemTechService - get() = store.state.domainNetworks.tangemTechService - +class RatesRepository( + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, +) { private val throttler = ThrottlerWithValues?>(60000) - suspend fun loadFiatRate(currencyId: String, coinsList: List): Result { -// get and submit previous result of equivalents. - val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map { - Pair(it, throttler.geValue(it)) - } - - val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) } - val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct() - if (coinIds.isEmpty()) return handleFiatRatesResult(throttledResult.toMap()) - - return when (val result = tangemTechService.rates(currencyId, coinIds)) { - is Result.Success -> { - val ratesResultList: Map> = result.data.rates.mapValues { - Result.Success(it.value.toBigDecimal()) - } - val updatedCurrencies = throttledResult.toMap().toMutableMap() - coinsList.forEach { currency -> - ratesResultList[currency.coinId]?.let { - updatedCurrencies[currency] = it - throttler.updateThrottlingTo(currency) - throttler.setValue(currency, it) - } - } - handleFiatRatesResult(updatedCurrencies) + suspend fun loadFiatRate(currencyId: String, coinsList: List): Result = + withContext(dispatchers.io) { + // get and submit previous result of equivalents. + val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map { + Pair(it, throttler.geValue(it)) } - is Result.Failure -> Result.Failure(result.error) + + val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) } + val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct() + if (coinIds.isEmpty()) return@withContext handleFiatRatesResult(throttledResult.toMap()) + + runCatching { tangemTechApi.getRates(currencyId.lowercase(), coinIds.joinToString(",")) } + .onSuccess { response -> + val ratesResultList: Map> = response.rates.mapValues { + Result.Success(it.value.toBigDecimal()) + } + val updatedCurrencies = throttledResult.toMap().toMutableMap() + coinsList.forEach { currency -> + ratesResultList[currency.coinId]?.let { + updatedCurrencies[currency] = it + throttler.updateThrottlingTo(currency) + throttler.setValue(currency, it) + } + } + return@withContext handleFiatRatesResult(updatedCurrencies) + } + .onFailure { Result.Failure(it) } + + error("Unreachable code because runCatching must return result") } - } private fun handleFiatRatesResult(rates: Map?>): Result.Success { val success = mutableMapOf() diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index cf8a13681c..288d399817 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -21,6 +21,7 @@ import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.hdWallet.DerivationPath import com.tangem.common.map import com.tangem.common.usersCode.UserCodeRepository +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.operations.CommandResponse @@ -30,7 +31,6 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTask @@ -85,7 +85,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co scanResponse: ScanResponse, ): CompletionResult { return runTaskAsync( - CreateProductWalletTask(scanResponse.productType), + CreateProductWalletTask(scanResponse.cardTypesResolver), scanResponse.card.cardId, Message(context.getString(R.string.initial_message_create_wallet_body)), ) @@ -211,11 +211,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return withContext(Dispatchers.Main) { result } } + @Suppress("MagicNumber") fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) { tangemSdk.config.cardIdDisplayFormat = when { scanResponse == null -> CardIdDisplayFormat.Full - scanResponse.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4) - scanResponse.isSaltPay() -> CardIdDisplayFormat.None + scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4) + scanResponse.cardTypesResolver.isSaltPay() -> CardIdDisplayFormat.None else -> CardIdDisplayFormat.Full } } diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index aae3cf8e9a..b0ee231286 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain import androidx.annotation.StringRes import com.tangem.common.core.TangemError -import com.tangem.datasource.api.tangemTech.TangemTechError import com.tangem.wallet.R interface TapErrors @@ -18,21 +17,20 @@ interface MultiMessageError : TapErrors { sealed class TapError( @StringRes val messageResource: Int, - override val args: List? = null + override val args: List? = null, ) : Throwable(), TapErrors, ArgError { object UnknownError : TapError(R.string.send_error_unknown) open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) object ScanCardError : TapError(R.string.scan_card_error) - object PayIdAlreadyCreated : TapError(R.string.wallet_create_payid_error_already_created) - object PayIdCreatingError : TapError(R.string.wallet_create_payid_error_message) - object PayIdEmptyField : TapError(R.string.wallet_create_payid_empty) object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle) object NoInternetConnection : TapError(R.string.wallet_notification_no_internet) - object InsufficientBalance : TapError(R.string.send_error_insufficient_balance) object BlockchainInternalError : TapError(R.string.send_error_blockchain_internal) object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance) - data class AmountLowerExistentialDeposit(override val args: List) : TapError(R.string.send_error_minimum_balance_format) + data class AmountLowerExistentialDeposit( + override val args: List, + ) : TapError(R.string.send_error_minimum_balance_format) + object FeeExceedsBalance : TapError(R.string.send_validation_invalid_fee) object TotalExceedsBalance : TapError(R.string.send_validation_invalid_total) object InvalidAmountValue : TapError(R.string.send_validation_invalid_amount) @@ -49,7 +47,6 @@ sealed class TapError( object CreationError : CustomError("Can't create wallet manager") class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount) class InternalError(message: String) : CustomError(message) - object BlockchainIsUnreachable : TapError(R.string.wallet_balance_blockchain_unreachable) object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later) } @@ -78,13 +75,4 @@ fun TapErrors.assembleErrors(): MutableList?>> { is TapError -> idList.add(Pair(this.messageResource, this.args)) } return idList -} - -fun TangemTechError.toTapError(): TapError { - return when (this.code) { - 404 -> NoDataError(this.description) - else -> TapError.CustomError(customMessage = this.description) - } -} - -class NoDataError(message: String) : TapError.CustomError(customMessage = message) \ No newline at end of file +} \ No newline at end of file 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 df19afc1c4..7bbfdc4f1d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -12,7 +12,6 @@ import com.tangem.common.doOnSuccess import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.ThrottlerWithValues import com.tangem.domain.common.extensions.withMainContext @@ -20,8 +19,7 @@ import com.tangem.operations.attestation.Attestation import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.configurable.config.ConfigManager -import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.datasource.config.ConfigManager import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.domain.extensions.makeWalletManagersForApp import com.tangem.tap.domain.model.UserWallet @@ -38,6 +36,7 @@ import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.userTokensRepository import com.tangem.tap.walletStoresManager +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.rekotlin.Action @@ -46,7 +45,14 @@ import timber.log.Timber class TapWalletManager { val walletManagerFactory: WalletManagerFactory by lazy { WalletManagerFactory(blockchainSdkConfig) } - val rates: RatesRepository = RatesRepository() + + // TODO("After adding DI") get dependencies by DI + val rates: RatesRepository by lazy { + RatesRepository( + tangemTechApi = store.state.domainNetworks.tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), + ) + } private val blockchainSdkConfig by lazy { store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig() @@ -157,12 +163,16 @@ class TapWalletManager { store.dispatch(WalletConnectAction.ResetState) store.dispatch(GlobalAction.SaveScanResponse(data)) store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) - store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed)) + store.dispatch( + WalletAction.MultiWallet.SetIsMultiwalletAllowed( + data.cardTypesResolver.isMultiwalletAllowed(), + ), + ) store.dispatch(WalletConnectAction.RestoreSessions(data)) store.dispatch( WalletAction.MultiWallet.ShowWalletBackupWarning( - show = data.card.settings.isBackupAllowed - && data.card.backupStatus == CardDTO.BackupStatus.NoBackup, + show = data.card.settings.isBackupAllowed && + data.card.backupStatus == CardDTO.BackupStatus.NoBackup, ), ) loadData(data) @@ -171,18 +181,18 @@ class TapWalletManager { fun updateConfigManager(data: ScanResponse) { val configManager = store.state.globalState.configManager - val blockchain = data.getBlockchain() - if (data.card.isStart2Coin) { - configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled) - configManager?.turnOff(ConfigManager.isTopUpEnabled) - } else if (blockchain == Blockchain.Bitcoin - || data.walletData?.blockchain == Blockchain.Bitcoin.id + val blockchain = data.cardTypesResolver.getBlockchain() + if (data.cardTypesResolver.isStart2Coin()) { + configManager?.turnOff(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED) + configManager?.turnOff(ConfigManager.IS_TOP_UP_ENABLED) + } else if (blockchain == Blockchain.Bitcoin || + data.walletData?.blockchain == Blockchain.Bitcoin.id ) { - configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled) - configManager?.resetToDefault(ConfigManager.isTopUpEnabled) + configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED) + configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED) } else { - configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled) - configManager?.resetToDefault(ConfigManager.isTopUpEnabled) + configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED) + configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED) } } @@ -193,7 +203,7 @@ class TapWalletManager { return } - if (data.card.isMultiwalletAllowed) { + if (data.cardTypesResolver.isMultiwalletAllowed()) { dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation) loadMultiWalletData(data) } else { @@ -221,7 +231,7 @@ class TapWalletManager { } private suspend fun loadSingleWalletData(data: ScanResponse) { - val blockchain = data.getBlockchain() + val blockchain = data.cardTypesResolver.getBlockchain() val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { @@ -231,7 +241,7 @@ class TapWalletManager { blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)), walletManagers = listOf(primaryWalletManager), ), - data.getPrimaryToken()?.let { + data.cardTypesResolver.getPrimaryToken()?.let { primaryWalletManager.addToken(it) primaryWalletManager.wallet.setAmount(Amount(it)) WalletAction.MultiWallet.AddToken(it, blockchainNetwork, false) @@ -269,7 +279,7 @@ class TapWalletManager { } suspend fun reloadData(data: ScanResponse) { - if (data.card.isMultiwalletAllowed) { + if (data.cardTypesResolver.isMultiwalletAllowed()) { loadUserCurrencies(data, walletManagerFactory) } withContext(Dispatchers.Main) { @@ -289,10 +299,11 @@ class TapWalletManager { private fun getActionIfUnknownBlockchainOrEmptyWallet(data: ScanResponse): WalletAction? { return when { // check order is important - data.isTangemTwins() && !data.twinsIsTwinned() -> { + data.cardTypesResolver.isTangemTwins() && !data.twinsIsTwinned() -> { WalletAction.EmptyWallet } - data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { + data.cardTypesResolver.getBlockchain() == Blockchain.Unknown && + !data.cardTypesResolver.isMultiwalletAllowed() -> { WalletAction.LoadData.Failure(TapError.UnknownBlockchain) } data.isDemoCard() -> { diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt deleted file mode 100644 index 5354d0b219..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.tap.domain.configurable - -import com.tangem.wallet.BuildConfig - -/** -[REDACTED_AUTHOR] - */ -interface Loader { - fun load(onComplete: (T) -> Unit) - - companion object { - const val featuresName = "features_${BuildConfig.ENVIRONMENT}" - const val configValuesName = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}" - const val warnings = "warnings_${BuildConfig.ENVIRONMENT}" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt deleted file mode 100644 index 8a2a6032cc..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.tangem.tap.domain.configurable.config - -import com.tangem.blockchain.common.BlockchainSdkConfig -import com.tangem.blockchain.common.BlockchairCredentials -import com.tangem.blockchain.common.GetBlockCredentials -import com.tangem.blockchain.common.NowNodeCredentials -import com.tangem.blockchain.common.QuickNodeCredentials -import com.tangem.tap.common.shop.shopify.ShopifyShop -import com.tangem.tap.common.zendesk.ZendeskConfig -import com.tangem.tap.domain.configurable.Loader -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig - -/** -[REDACTED_AUTHOR] - */ -data class Config( - val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de", - val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE", - val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C", - val mercuryoWidgetId: String = "", - val mercuryoSecret: String = "", - val appsFlyerDevKey: String = "", - val amplitudeApiKey: String = "", - val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(), - val isSendingToPayIdEnabled: Boolean = true, - val isTopUpEnabled: Boolean = false, - @Deprecated("Not relevant since version 3.23") - val isCreatingTwinCardsAllowed: Boolean = false, - val shopify: ShopifyShop? = null, - val zendesk: ZendeskConfig? = null, - val saltPayConfig: SaltPayConfig? = null, -) - -class ConfigManager { - - var config: Config = Config() - private set - - private var defaultConfig = Config() - - fun load(configLoader: Loader, onComplete: ((config: Config) -> Unit)? = null) { - configLoader.load { configModel -> - setupFeature(configModel.features) - setupKey(configModel.configValues) - onComplete?.invoke(config) - } - } - - fun turnOff(name: String) { - when (name) { - isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = false) - isTopUpEnabled -> config = config.copy(isTopUpEnabled = false) - isCreatingTwinCardsAllowed -> config = config.copy(isCreatingTwinCardsAllowed = false) - } - } - - fun resetToDefault(name: String) { - when (name) { - isSendingToPayIdEnabled -> config = - config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled) - isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled) - isCreatingTwinCardsAllowed -> config = - config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed) - } - } - - private fun setupFeature(featureModel: FeatureModel?) { - val model = featureModel ?: return - - config = config.copy( - isTopUpEnabled = model.isTopUpEnabled, - isSendingToPayIdEnabled = model.isSendingToPayIdEnabled, - isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed, - ) - defaultConfig = defaultConfig.copy( - isTopUpEnabled = model.isTopUpEnabled, - isSendingToPayIdEnabled = model.isSendingToPayIdEnabled, - isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed, - ) - } - - private fun setupKey(configValues: ConfigValueModel?) { - val values = configValues ?: return - config = config.copy( - coinMarketCapKey = values.coinMarketCapKey, - moonPayApiKey = values.moonPayApiKey, - moonPayApiSecretKey = values.moonPayApiSecretKey, - mercuryoWidgetId = values.mercuryoWidgetId, - mercuryoSecret = values.mercuryoSecret, - blockchainSdkConfig = BlockchainSdkConfig( - blockchairCredentials = BlockchairCredentials( - apiKey = values.blockchairApiKeys, - authToken = values.blockchairAuthorizationToken, - ), - blockcypherTokens = values.blockcypherTokens, - quickNodeSolanaCredentials = QuickNodeCredentials( - apiKey = values.quiknodeApiKey, - subdomain = values.quiknodeSubdomain, - ), - quickNodeBscCredentials = QuickNodeCredentials( - apiKey = values.bscQuiknodeApiKey, - subdomain = values.bscQuiknodeSubdomain, - ), - nowNodeCredentials = NowNodeCredentials(values.nowNodesApiKey), - getBlockCredentials = GetBlockCredentials(values.getBlockApiKey), - infuraProjectId = values.infuraProjectId, - tronGridApiKey = values.tronGridApiKey, - saltPayAuthToken = values.saltPay.credentials.token, - ), - appsFlyerDevKey = values.appsFlyer.appsFlyerDevKey, - amplitudeApiKey = values.amplitudeApiKey, - shopify = values.shopifyShop, - zendesk = values.zendesk, - saltPayConfig = values.saltPay, - ) - defaultConfig = defaultConfig.copy( - coinMarketCapKey = values.coinMarketCapKey, - moonPayApiKey = values.moonPayApiKey, - moonPayApiSecretKey = values.moonPayApiSecretKey, - mercuryoWidgetId = values.mercuryoWidgetId, - mercuryoSecret = values.mercuryoSecret, - blockchainSdkConfig = BlockchainSdkConfig( - blockchairCredentials = BlockchairCredentials( - apiKey = values.blockchairApiKeys, - authToken = values.blockchairAuthorizationToken, - ), - blockcypherTokens = values.blockcypherTokens, - quickNodeSolanaCredentials = QuickNodeCredentials( - apiKey = values.quiknodeApiKey, - subdomain = values.quiknodeSubdomain, - ), - quickNodeBscCredentials = QuickNodeCredentials( - apiKey = values.bscQuiknodeApiKey, - subdomain = values.bscQuiknodeSubdomain, - ), - nowNodeCredentials = NowNodeCredentials(values.nowNodesApiKey), - getBlockCredentials = GetBlockCredentials(values.getBlockApiKey), - infuraProjectId = values.infuraProjectId, - saltPayAuthToken = values.saltPay.credentials.token, - ), - appsFlyerDevKey = values.appsFlyer.appsFlyerDevKey, - amplitudeApiKey = values.amplitudeApiKey, - shopify = values.shopifyShop, - zendesk = values.zendesk, - saltPayConfig = values.saltPay, - ) - } - - companion object { - const val isSendingToPayIdEnabled = "isSendingToPayIdEnabled" - const val isCreatingTwinCardsAllowed = "isCreatingTwinCardsAllowed" - const val isTopUpEnabled = "isTopUpEnabled" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt index 03c10daf6f..ddadfcfc70 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt @@ -24,7 +24,6 @@ data class WarningMessage( blockchains?.map { Blockchain.fromId(it.uppercase()) } } - var isHidden = false enum class Priority { @@ -40,15 +39,14 @@ data class WarningMessage( enum class Type { @Json(name = "permanent") - Permanent, // нельзя скрыть + Permanent, // нельзя скрыть @Json(name = "temporary") - Temporary, // можно скрыть (кнопка ОК) + Temporary, // можно скрыть (кнопка ОК) AppRating, TestCard - } enum class Location { diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt index f75a4922ce..a0e382136a 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt @@ -39,8 +39,8 @@ class WarningMessagesManager { val foundWarning = findWarning(warning) return when { foundWarning == null -> false - foundWarning.type == WarningMessage.Type.Temporary - || foundWarning.type == WarningMessage.Type.AppRating -> { + foundWarning.type == WarningMessage.Type.Temporary || + foundWarning.type == WarningMessage.Type.AppRating -> { if (foundWarning.isHidden) { false } else { @@ -70,6 +70,8 @@ class WarningMessagesManager { } companion object { + const val REMAINING_SIGNATURES_WARNING = 10 + fun devCardWarning(): WarningMessage = WarningMessage( "", "", @@ -171,7 +173,5 @@ class WarningMessagesManager { R.string.alert_demo_message, WarningMessage.Origin.Local, ) - - const val REMAINING_SIGNATURES_WARNING = 10 } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt index 673ef1787b..9adb6b24aa 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt @@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Token import com.tangem.common.card.EllipticCurve import java.math.BigDecimal - +@Suppress("MagicNumber") fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? { return when (this) { Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ONE diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index 486a9f6b89..005f05500c 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -1,16 +1,12 @@ package com.tangem.tap.domain.extensions -import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isSaltPay -import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.getTwinCardNumber -import com.tangem.domain.common.isTangemTwin import com.tangem.operations.attestation.CardVerifyAndGetInfo import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.features.wallet.redux.Artwork @@ -21,22 +17,13 @@ val CardDTO.remainingSignatures: Int? val CardDTO.isWalletDataSupported: Boolean get() = this.firmwareVersion.major >= 4 -val CardDTO.isMultiwalletAllowed: Boolean - get() { - return !isTangemTwin() && !isStart2Coin && !isTangemNote && !isSaltPay && - (firmwareVersion >= FirmwareVersion.MultiWalletAvailable || - wallets.firstOrNull()?.curve == EllipticCurve.Secp256k1) - } +val CardDTO.isFirmwareMultiwalletAllowed: Boolean + get() = firmwareVersion >= FirmwareVersion.MultiWalletAvailable && settings.maxWalletsCount > 1 val CardDTO.isHdWalletAllowedByApp: Boolean get() = settings.isHDWalletAllowed && !isSaltPay -val CardDTO.isTangemWallet: Boolean - get() = settings.isBackupAllowed - && settings.isHDWalletAllowed - && firmwareVersion >= FirmwareVersion.MultiWalletAvailable - && !isSaltPay - +@Suppress("UnnecessaryParentheses") fun CardDTO.hasSignedHashes(): Boolean { return wallets.any { (it.totalSignedHashes ?: 0) > 0 } } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index 989793e59a..07e92a313f 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -37,7 +37,7 @@ fun WalletManagerFactory.makeWalletManagerForApp( val seedKey = wallet.extendedPublicKey return when { - scanResponse.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> { + scanResponse.cardTypesResolver.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> { makeTwinWalletManager( walletPublicKey = wallet.publicKey, pairPublicKey = scanResponse.secondTwinPublicKey!!.hexToBytes(), @@ -45,7 +45,7 @@ fun WalletManagerFactory.makeWalletManagerForApp( curve = wallet.curve, ) } - scanResponse.card.isHdWalletAllowedByApp && (seedKey != null && derivationParams != null) -> { + scanResponse.card.isHdWalletAllowedByApp && seedKey != null && derivationParams != null -> { val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()] val derivationPath = when (derivationParams) { is DerivationParams.Default -> blockchain.derivationPath(derivationParams.style) @@ -72,7 +72,8 @@ fun WalletManagerFactory.makeWalletManagerForApp( } fun WalletManagerFactory.makeWalletManagerForApp( - scanResponse: ScanResponse, blockchainNetwork: BlockchainNetwork, + scanResponse: ScanResponse, + blockchainNetwork: BlockchainNetwork, ): WalletManager? { return makeWalletManagerForApp( scanResponse, @@ -119,9 +120,9 @@ fun WalletManagerFactory.makePrimaryWalletManager( scanResponse: ScanResponse, ): WalletManager? { val blockchain = if (scanResponse.card.isTestCard) { - scanResponse.getBlockchain().getTestnetVersion() ?: return null + scanResponse.cardTypesResolver.getBlockchain().getTestnetVersion() ?: return null } else { - scanResponse.getBlockchain() + scanResponse.cardTypesResolver.getBlockchain() } val derivationParams = getDerivationParams(null, scanResponse.card) return makeWalletManagerForApp( @@ -142,13 +143,14 @@ private fun selectWallet(wallets: List): CardDTO.Wallet? { fun WalletManagerFactory.makeSaltPayWalletManager( scanResponse: ScanResponse, ): EthereumWalletManager { - val blockchain = scanResponse.getBlockchain() - if (blockchain != Blockchain.SaltPay) - throw IllegalArgumentException() + val blockchain = scanResponse.cardTypesResolver.getBlockchain() + if (blockchain != Blockchain.SaltPay) { + error("WalletManager for the SaltPay can be created based only on Blockchain.SaltPay") + } val token = SaltPayWorkaround.tokenFrom(blockchain) val cardWallet = scanResponse.card.wallets.firstOrNull().guard { - throw NullPointerException("SaltPay card must have one wallet at least") + error("SaltPay card must have one wallet at least") } return makeWalletManager( diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 22cc834b2a..a2c699667a 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -1,7 +1,5 @@ package com.tangem.tap.domain.model.builders -import com.tangem.common.card.EllipticCurve -import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO @@ -32,28 +30,11 @@ class UserWalletBuilder( ProductType.Start2Coin -> "Start2Coin" ProductType.Wallet -> when { card.isBackupNotAllowed -> "Tangem card" + card.isStart2Coin -> "Start2Coin" else -> "Wallet" } } - private val ScanResponse.isMultiCurrency: Boolean - get() = when (productType) { - ProductType.Note -> false - ProductType.Twins -> false - ProductType.SaltPay -> false - ProductType.Start2Coin -> false - ProductType.Wallet -> when { - card.isStart2Coin -> false - card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable -> true - else -> { - val cardWallets = card.wallets - require(cardWallets.isNotEmpty()) { "Card wallets must not be empty" } - - cardWallets.first().curve == EllipticCurve.Secp256k1 - } - } - } - fun backupCardsIds(backupCardsIds: Set?) = this.apply { if (backupCardsIds != null) { this.backupCardsIds = backupCardsIds @@ -71,7 +52,7 @@ class UserWalletBuilder( artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), cardsInWallet = backupCardsIds.plus(card.cardId), scanResponse = this, - isMultiCurrency = isMultiCurrency, + isMultiCurrency = cardTypesResolver.isMultiwalletAllowed(), ) } } @@ -106,6 +87,6 @@ class UserWalletBuilder( private fun getUrlForArtwork(cardId: String, cardPublicKeyHex: String, artworkId: String): String { return TangemApi.Companion.BaseUrl.VERIFY.url + TangemApi.ARTWORK + - "?artworkId=${artworkId}&CID=${cardId}&publicKey=$cardPublicKeyHex" + "?artworkId=$artworkId&CID=$cardId&publicKey=$cardPublicKeyHex" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt index 651ab25018..b48ae715a2 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -57,7 +57,7 @@ private class BlockchainNetworkWalletStoreBuilderImpl( userWalletId = userWalletId, blockchain = blockchainNetwork.blockchain, derivationPath = blockchainNetwork.derivationPath?.let { DerivationPath(it) }, - walletsData = (listOf(blockchainWalletData) + tokensWalletsData), + walletsData = listOf(blockchainWalletData) + tokensWalletsData, walletRent = null, walletManager = walletManager, blockchainNetwork = blockchainNetwork, @@ -73,13 +73,13 @@ private class WalletMangerWalletStoreBuilderImpl( override fun build(): WalletStoreModel { val wallet = walletManager.wallet val blockchainWalletData = wallet.blockchain.toBlockchainWalletData(walletManager) - val tokenWalletsData = walletManager.cardTokens.map { it.toTokenWalletData(walletManager) } + val tokenWalletsData = wallet.getTokens().firstOrNull()?.toTokenWalletData(walletManager) return WalletStoreModel( userWalletId = userWalletId, blockchain = wallet.blockchain, derivationPath = wallet.publicKey.derivationPath, - walletsData = (listOf(blockchainWalletData) + tokenWalletsData), + walletsData = listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData), walletRent = null, walletManager = walletManager, blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager), diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt index 66e41dc48e..daa0901b14 100644 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt @@ -15,8 +15,9 @@ internal class AddCustomTokenErrorConverter( private val context: Context, ) : ModuleMessageConverter { + @Suppress("MagicNumber") override fun convert(message: DomainModuleError): ConvertedMessage? { - val customTokenError = (message as? AddCustomTokenError) ?: throw UnsupportedOperationException() + val customTokenError = message as? AddCustomTokenError ?: throw UnsupportedOperationException() val rawMessage = when (customTokenError) { AddCustomTokenError.Warning.PotentialScamToken -> R.string.custom_token_validation_error_not_found diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt index ac8c8cd359..9db1c78996 100644 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt @@ -31,4 +31,4 @@ class SaltPayErrorConverter( is SaltPayActivationError -> SaltPayActivationErrorConverter(context).convert(message) else -> null } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt index 65de384d44..1771a2db47 100644 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt @@ -14,7 +14,7 @@ internal class SaltPayActivationErrorConverter( ) : ModuleMessageConverter { override fun convert(message: SaltPayActivationError): ConvertedDialogMessage? { - val saltPayError = (message as? SaltPayActivationError) ?: throw UnsupportedOperationException() + val saltPayError = message as? SaltPayActivationError ?: throw UnsupportedOperationException() val dialogMessage = when (saltPayError) { SaltPayActivationError.NoGas -> { diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 6caafef969..36f4635c4a 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -129,7 +129,7 @@ object ScanCardProcessor { ?.let { it == scanResponse.card.cardId } ?: false - if (scanResponse.isSaltPayWallet() || !isTheSamePrimaryCard) { + if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { val error = SaltPayActivationError.PutVisaCard SaltPayExceptionHandler.handle(error) onFailure(TangemSdkError.ExceptionError(error)) @@ -148,30 +148,33 @@ object ScanCardProcessor { store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) if (disclaimer.isAccepted()) { - nextHandler((scanResponse)) - } else scope.launch { - delay(DELAY_SDK_DIALOG_CLOSE) - disclaimerWillShow() - dispatchOnMain( - DisclaimerAction.Show( - fromScreen = AppScreen.Home, - callback = DisclaimerCallback( - onAccept = { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, - onDismiss = { - scope.launch(Dispatchers.Main) { - onFailure(TangemSdkError.UserCancelled()) - } - }, + nextHandler(scanResponse) + } else { + scope.launch { + delay(DELAY_SDK_DIALOG_CLOSE) + disclaimerWillShow() + dispatchOnMain( + DisclaimerAction.Show( + fromScreen = AppScreen.Home, + callback = DisclaimerCallback( + onAccept = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismiss = { + scope.launch(Dispatchers.Main) { + onFailure(TangemSdkError.UserCancelled()) + } + }, + ), ), - ), - ) + ) + } } } + @Suppress("LongMethod", "MagicNumber") private suspend inline fun onScanSuccess( scanResponse: ScanResponse, crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, @@ -185,8 +188,8 @@ object ScanCardProcessor { store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) - if (scanResponse.isSaltPay()) { - if (scanResponse.isSaltPayVisa()) { + if (scanResponse.cardTypesResolver.isSaltPay()) { + if (scanResponse.cardTypesResolver.isSaltPayVisa()) { val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) delay(500) diff --git a/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt b/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt index 858dc8b5d1..3e98a50996 100644 --- a/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt @@ -24,7 +24,7 @@ class OnboardingWalletStateConverter : StringStateConverter { BackupStepAdapter(), BigIntegerAdapter(), - ) + ) } override fun convert(stateHolder: AppState): String { @@ -56,6 +56,7 @@ class BackupStepAdapter { } } + @Suppress("MagicNumber") @FromJson fun fromJson(json: String): BackupStep { return when (json) { diff --git a/app/src/main/java/com/tangem/tap/domain/statePrinter/ScanResponseConverter.kt b/app/src/main/java/com/tangem/tap/domain/statePrinter/ScanResponseConverter.kt index f8597dc48b..e59536ab1e 100644 --- a/app/src/main/java/com/tangem/tap/domain/statePrinter/ScanResponseConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/statePrinter/ScanResponseConverter.kt @@ -29,14 +29,12 @@ class ScanResponseConverter : StringStateConverter { ) } derivedKeysMap[keyWalletPubKey.bytes.toHexString()] = exPubKeysMap - } scanResponseMap["derivedKeys"] = derivedKeysMap val json = converter.prettyPrint(scanResponseMap) return json - } } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt index 140a0ef72d..5c39928f88 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt @@ -13,9 +13,9 @@ import com.tangem.operations.wallet.CreateWalletTask class CreateWalletsTask(curves: List? = null) : CardSessionRunnable { private val curves = curves ?: listOf( - EllipticCurve.Secp256k1, - EllipticCurve.Ed25519, - EllipticCurve.Secp256r1, + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Secp256r1, ) private var index = 0 @@ -26,10 +26,10 @@ class CreateWalletsTask(curves: List? = null) : CardSessionRunnab } private fun createWallet( - curve: EllipticCurve, session: CardSession, + curve: EllipticCurve, + session: CardSession, callback: (result: CompletionResult) -> Unit ) { - CreateWalletTask(curve).run(session) { result -> when (result) { is CompletionResult.Success -> { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt deleted file mode 100644 index 3c471f4bc4..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt +++ /dev/null @@ -1,55 +0,0 @@ -//package com.tangem.tap.domain.tasks -// -//import com.tangem.common.CompletionResult -//import com.tangem.common.core.CardSession -//import com.tangem.common.core.CardSessionRunnable -//import com.tangem.common.core.CompletionCallback -//import com.tangem.common.hdWallet.DerivationPath -//import com.tangem.common.hdWallet.ExtendedPublicKey -//import com.tangem.operations.CommandResponse -//import com.tangem.operations.derivation.DeriveWalletPublicKeysTask -//import com.tangem.tap.common.extensions.ByteArrayKey -// -//class ExtendedPublicKeyList( -// items: Collection -//): ArrayList(items), CommandResponse -// -// -//class DerivationTaskResponse( -// val entries: Map -//): CommandResponse -// -//class DerivationTask( -// private val derivations: Map> -//) : CardSessionRunnable { -// -// val response: MutableMap = mutableMapOf() -// -// override fun run(session: CardSession, callback: CompletionCallback) { -// derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback) -// } -// -// private fun derive( -// keys: List, -// index: Int, -// session: CardSession, -// callback: CompletionCallback -// ) { -// if (index == keys.count()) { -// callback(CompletionResult.Success(DerivationTaskResponse(response.toMap()))) -// return -// } -// -// val key = keys[index] -// val paths = derivations[key]!! -// DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result -> -// when (result) { -// is CompletionResult.Success -> { -// response[key] = result.data[key] -// derive(keys = keys, index = index + 1, session = session, callback = callback) -// } -// is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) -// } -// } -// } -//} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt index 58520125ce..3e5d37ac4b 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt @@ -34,4 +34,4 @@ class SignHashTask( } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 98ce39259e..7bbd4d1811 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -13,10 +13,9 @@ import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath import com.tangem.common.map import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.KeyWalletPublicKey -import com.tangem.domain.common.ProductType import com.tangem.domain.common.TapWorkarounds.derivationStyle -import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.operations.CommandResponse import com.tangem.operations.backup.PrimaryCard @@ -56,7 +55,7 @@ private data class CreateWalletResponse( } class CreateProductWalletTask( - private val type: ProductType, + private val cardTypesResolver: CardTypesResolver, ) : CardSessionRunnable { override val allowsRequestAccessCodeFromRepository: Boolean = false @@ -71,9 +70,10 @@ class CreateProductWalletTask( } val cardDto = CardDTO(card) - val commandProcessor = when (type) { - ProductType.Note -> CreateWalletTangemNote() - ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") + val commandProcessor = when { + cardTypesResolver.isTangemNote() -> CreateWalletTangemNote(cardTypesResolver) + cardTypesResolver.isTangemTwins() -> + throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") else -> CreateWalletTangemWallet() } commandProcessor.proceed(cardDto, session) { @@ -95,7 +95,8 @@ class CreateProductWalletTask( } } -private class CreateWalletTangemNote : ProductCommandProcessor { +private class CreateWalletTangemNote(private val cardTypesResolver: CardTypesResolver) : + ProductCommandProcessor { override fun proceed( card: CardDTO, session: CardSession, @@ -106,8 +107,8 @@ private class CreateWalletTangemNote : ProductCommandProcessor { callback( CompletionResult.Success( - CreateProductWalletTaskResponse(card = session.environment.card!!) - ) + CreateProductWalletTaskResponse(card = session.environment.card!!), + ), ) } } @@ -209,9 +210,10 @@ private class CreateWalletTangemWallet : ProductCommandProcessor() override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - if (curves.isEmpty()){ + if (curves.isEmpty()) { callback(CompletionResult.Failure(TangemSdkError.WalletIsNotCreated())) return } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt index 444a7b3971..398f369a54 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt @@ -8,7 +8,7 @@ import com.tangem.common.extensions.guard import com.tangem.operations.backup.ResetBackupCommand import com.tangem.operations.wallet.PurgeWalletCommand -class ResetToFactorySettingsTask() : CardSessionRunnable { +class ResetToFactorySettingsTask : CardSessionRunnable { override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { deleteWallets(session, callback) @@ -18,7 +18,6 @@ class ResetToFactorySettingsTask() : CardSessionRunnable { session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - val wallet = session.environment.card?.wallets?.lastOrNull().guard { resetBackup(session, callback) return @@ -38,7 +37,6 @@ class ResetToFactorySettingsTask() : CardSessionRunnable { session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - val backupStatus = session.environment.card?.backupStatus if (backupStatus == null || backupStatus == Card.BackupStatus.NoBackup) { callback(CompletionResult.Success(session.environment.card!!)) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index b04226d1d6..6cbd5923e6 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -8,19 +8,23 @@ import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError +import com.tangem.common.deserialization.WalletDataDeserializer import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.guard +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toByteArray import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.tlv.Tlv +import com.tangem.common.tlv.TlvDecoder +import com.tangem.crypto.CryptoUtils import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isExcluded import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease import com.tangem.domain.common.TapWorkarounds.isSaltPay -import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.TwinsHelper @@ -30,10 +34,11 @@ import com.tangem.operations.ScanTask import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.backup.StartPrimaryCardLinkingTask import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask +import com.tangem.operations.files.ReadFilesTask import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand import com.tangem.tap.domain.TapSdkError import com.tangem.tap.domain.extensions.getPrimaryCurve -import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.extensions.isFirmwareMultiwalletAllowed import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.preferencesStorage @@ -66,7 +71,6 @@ class ScanProductTask( } val commandProcessor = when { - cardDto.isTangemNote -> ScanNoteProcessor() cardDto.isTangemTwins -> ScanTwinProcessor() else -> ScanWalletProcessor(userTokensRepository, additionalBlockchainsToDerive) } @@ -97,24 +101,6 @@ class ScanProductTask( } } -private class ScanNoteProcessor : ProductCommandProcessor { - override fun proceed( - card: CardDTO, - session: CardSession, - callback: (result: CompletionResult) -> Unit, - ) { - callback( - CompletionResult.Success( - ScanResponse( - card = card, - productType = ProductType.Note, - walletData = session.environment.walletData, - ), - ), - ) - } -} - private class ScanWalletProcessor( private val userTokensRepository: UserTokensRepository?, private val additionalBlockchainsToDerive: Collection? = null, @@ -126,15 +112,81 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit, ) { + @Suppress("MagicNumber") + if (card.firmwareVersion.doubleValue >= 4.39 && card.settings.maxWalletsCount == 1) { + readFile(card, session, callback) + return + } + createMissingWalletsIfNeeded(card, session, callback) } + private fun readFile( + card: CardDTO, + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) { + val funToContinue = { createMissingWalletsIfNeeded(card, session, callback) } + + ReadFilesTask(fileName = "blockchainInfo", walletPublicKey = null).run(session) { result -> + + when (result) { + is CompletionResult.Success -> { + val file = result.data.firstOrNull() + val counter = file?.counter + val signature = file?.signature + + val tlv = file?.data?.let { Tlv.deserialize(it) } + val walletData = tlv?.let { WalletDataDeserializer.deserialize(TlvDecoder(tlv)) } + + if (walletData == null || counter == null || signature == null) { + funToContinue() + return@run + } + + val dataToVerify = card.cardId.hexToBytes() + file.data + counter.toByteArray() + val isVerified = CryptoUtils.verify( + publicKey = card.issuer.publicKey, + message = dataToVerify, + signature = signature, + ) + if (!isVerified) { + funToContinue() + return@run + } + + if (walletData.blockchain != "ANY") { + callback( + CompletionResult.Success( + ScanResponse( + card = card, + productType = ProductType.Note, + walletData = walletData, + ), + ), + ) + } else { + funToContinue() + } + } + is CompletionResult.Failure -> { + when (result.error) { + is TangemSdkError.FileNotFound, is TangemSdkError.InsNotSupported -> { + funToContinue() + } + else -> callback(CompletionResult.Failure(result.error)) + } + } + } + } + } + private fun createMissingWalletsIfNeeded( card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - if (card.wallets.isEmpty() || !card.isMultiwalletAllowed) { + if (card.wallets.isEmpty() || !card.isFirmwareMultiwalletAllowed) { startLinkingForBackupIfNeeded(card, session, callback) return } @@ -170,8 +222,9 @@ private class ScanWalletProcessor( ) { val activationInProgress = preferencesStorage.usedCardsPrefStorage.isActivationInProgress(card.cardId) - if ((card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty()) - && (activationInProgress || card.isSaltPay) + @Suppress("ComplexCondition") + if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty() && + (activationInProgress || card.isSaltPay) ) { StartPrimaryCardLinkingTask().run(session) { linkingResult -> when (linkingResult) { @@ -194,9 +247,8 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - val productType = when { - card.isSaltPay -> ProductType.SaltPay - card.isStart2Coin -> ProductType.Start2Coin + val productType = when (card.isSaltPay) { + true -> ProductType.SaltPay else -> ProductType.Wallet } scope.launch { @@ -266,7 +318,12 @@ private class ScanWalletProcessor( } if (additionalBlockchainsToDerive != null) { blockchainsToDerive.addAll( - additionalBlockchainsToDerive.map { BlockchainNetwork(it, card) } + additionalBlockchainsToDerive.map { + BlockchainNetwork( + blockchain = it, + card = card, + ) + }, ) } if (!card.useOldStyleDerivation) { @@ -277,7 +334,12 @@ private class ScanWalletProcessor( Blockchain.RSK, Blockchain.Fantom, Blockchain.FantomTestnet, Blockchain.Avalanche, Blockchain.AvalancheTestnet, - ).map { BlockchainNetwork(it, card) }, + ).map { + BlockchainNetwork( + blockchain = it, + card = card, + ) + }, ) } return blockchainsToDerive.distinct() @@ -307,6 +369,7 @@ private class ScanWalletProcessor( } } +@Suppress("MagicNumber") private class ScanTwinProcessor : ProductCommandProcessor { override fun proceed( card: CardDTO, diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt b/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt index 0e18c4018d..c70127f7e2 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt @@ -2,8 +2,8 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonClass import com.tangem.blockchain.common.Blockchain +import com.tangem.datasource.api.tangemTech.models.CoinsResponse import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.datasource.api.tangemTech.CoinsResponse @JsonClass(generateAdapter = true) data class CurrencyFromJson( @@ -26,7 +26,6 @@ data class CurrenciesFromJson( val coins: List ) - fun List.toContracts(): List { return mapNotNull { Contract.fromJsonObject(it) } } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt index fbbaad9022..bc1f1dccc5 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt @@ -1,23 +1,23 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain import com.tangem.common.services.Result -import com.tangem.domain.common.extensions.getListOfCoins -import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.datasource.api.tangemTech.CoinsResponse -import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.datasource.api.common.MoshiConverter -import com.tangem.tap.common.AssetReader +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.CoinsResponse +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.datasource.utils.AssetReader +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext class LoadAvailableCoinsService( - private val networkService: TangemTechService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, private val assetReader: AssetReader, ) { - private val moshi: Moshi by lazy { MoshiConverter.defaultMoshi() } private val currenciesAdapter: JsonAdapter = - moshi.adapter(CurrenciesFromJson::class.java) + MoshiConverter.networkMoshi.adapter(CurrenciesFromJson::class.java) suspend fun getSupportedTokens( isTestNet: Boolean = false, @@ -43,7 +43,7 @@ class LoadAvailableCoinsService( LoadedCoins( currencies = data.coins.map { Currency.fromCoinResponse(it, data.imageHost) }, moreAvailable = data.total > offset + LOAD_PER_PAGE, - ) + ), ) } is Result.Failure -> { @@ -55,16 +55,23 @@ class LoadAvailableCoinsService( private suspend fun loadCoins( supportedBlockchains: List, offset: Int, - searchInput: String? = null + searchInput: String? = null, ): Result { - val networkIds = supportedBlockchains.toSet().map { it.toNetworkId() } - return networkService.getListOfCoins( - networkIds = networkIds, - active = true, - offset = offset, - limit = LOAD_PER_PAGE, - searchText = searchInput, - ) + return withContext(dispatchers.io) { + runCatching { + tangemTechApi.getCoins( + networkIds = supportedBlockchains.toSet().map(Blockchain::toNetworkId).joinToString(","), + active = true, + searchText = searchInput, + offset = offset, + limit = LOAD_PER_PAGE, + ) + } + .onSuccess { return@withContext Result.Success(it) } + .onFailure { return@withContext Result.Failure(it) } + + error("Unreachable code because runCatching must return result") + } } fun getTestnetCoins(): List { @@ -88,7 +95,6 @@ class LoadAvailableCoinsService( } } - data class LoadedCoins( val currencies: List, val moreAvailable: Boolean, diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt index 2aa1dce1a7..b9338e42a3 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt @@ -4,23 +4,23 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Types import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle -import com.tangem.common.services.Result -import com.tangem.domain.common.extensions.getTokens -import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.domain.common.extensions.toNetworkId import com.tangem.tap.common.FileReader import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.tokens.models.TokenDao +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext @Deprecated("Use this only for migration") class OldUserTokensRepository( private val fileReader: FileReader, - private val tangemNetworkService: TangemTechService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) { - private val moshi = MoshiConverter.defaultMoshi() + private val moshi = MoshiConverter.networkMoshi private val blockchainsAdapter: JsonAdapter> = moshi.adapter( Types.newParameterizedType(List::class.java, Blockchain::class.java), ) @@ -83,7 +83,8 @@ class OldUserTokensRepository( } private suspend fun loadSavedCurrenciesOldWay( - cardId: String, isHdWalletSupported: Boolean = false, + cardId: String, + isHdWalletSupported: Boolean = false, ): List { val blockchains = loadSavedBlockchains(cardId) val tokens = loadSavedTokens(cardId) @@ -104,20 +105,27 @@ class OldUserTokensRepository( return blockchainNetworks } - private suspend fun getTokensIds(tokens: List): Map = coroutineScope { + private suspend fun getTokensIds(tokens: List): Map = withContext(dispatchers.io) { tokens.map { async { - tangemNetworkService.getTokens( + tangemTechApi.getCoins( contractAddress = it.contractAddress, - networkId = it.blockchainDao.toBlockchain().toNetworkId(), + networkIds = it.blockchainDao.toBlockchain().toNetworkId(), active = true, ) } - }.map { it.await() } - .map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id } + } + .map { + runCatching { it.await() } + .onSuccess { return@map it.coins.firstOrNull()?.id } + .onFailure { return@map null } + + error("Unreachable code because runCatching must return result") + } .mapIndexedNotNull { index, id -> if (id == null) null else tokens[index].contractAddress to id - }.toMap() + } + .toMap() } companion object { diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt deleted file mode 100644 index 821a8046ac..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.tap.domain.tokens - -import com.tangem.common.core.TangemSdkError -import com.tangem.common.services.Result -import com.tangem.datasource.api.tangemTech.TangemTechService -import com.tangem.datasource.api.tangemTech.UserTokensResponse -import com.tangem.tap.domain.NoDataError - -class UserTokensNetworkService(private val tangemTechService: TangemTechService) { - suspend fun getUserTokens(userId: String): Result { - return when (val result = tangemTechService.getUserTokens(userId)) { - is Result.Success -> result - is Result.Failure -> { - val error = result.error - if (error is TangemSdkError.NetworkError && error.customMessage.contains("404")) { - return Result.Failure(NoDataError(error.customMessage)) - } else { - return result - } - } - } - } - - suspend fun saveUserTokens(userId: String, tokens: UserTokensResponse): Result { - return tangemTechService.putUserTokens(userId, tokens) - } -} diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index b3efb6ce5b..7d01846702 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -2,108 +2,103 @@ package com.tangem.tap.domain.tokens import android.content.Context import com.tangem.blockchain.common.DerivationStyle -import com.tangem.common.services.Result +import com.tangem.common.core.TangemSdkError +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechService -import com.tangem.datasource.api.tangemTech.UserTokensResponse +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.CardDTO import com.tangem.tap.common.AndroidFileReader -import com.tangem.tap.domain.NoDataError import com.tangem.tap.domain.model.builders.UserWalletIdBuilder +import com.tangem.tap.domain.tokens.converters.CurrencyConverter import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.network.NetworkConnectivity -import com.tangem.tap.store -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext class UserTokensRepository( private val storageService: UserTokensStorageService, - private val networkService: UserTokensNetworkService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) { - suspend fun getUserTokens(card: CardDTO): List { - val userId = getUserWalletId(card) ?: return emptyList() + + // TODO("After adding DI") replace with CoroutineDispatcherProvider + suspend fun getUserTokens(card: CardDTO): List = withContext(dispatchers.io) { + val userId = getUserWalletId(card) ?: return@withContext emptyList() if (DemoHelper.isDemoCardId(card.cardId)) { - return loadTokensOffline(card, userId).ifEmpty { loadDemoCurrencies() } + return@withContext loadTokensOffline(card, userId).ifEmpty(::loadDemoCurrencies) } if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - return loadTokensOffline(card, userId) + return@withContext loadTokensOffline(card, userId) } - return when (val networkResult = networkService.getUserTokens(userId)) { - is Result.Success -> { - val tokens = networkResult.data.tokens.mapNotNull { Currency.fromTokenResponse(it) } - storageService.saveUserTokens(userId, tokens.toUserTokensResponse()) - tokens.distinct() + runCatching { tangemTechApi.getUserTokens(userId) } + .onSuccess { response -> + return@withContext response.tokens + .mapNotNull(Currency.Companion::fromTokenResponse).also { + storageService.saveUserTokens(userId, it.toUserTokensResponse()) + } + .distinct() + } + .onFailure { + return@withContext handleGetUserTokensFailure(card = card, userId = userId, error = it) } - is Result.Failure -> { - handleGetUserTokensFailure(card = card, userId = userId, error = networkResult.error) - } - } + error("Unreachable code because runCatching must return result") } - suspend fun saveUserTokens(card: CardDTO, tokens: List) { - val userId = getUserWalletId(card) ?: return + // TODO("After adding DI") replace with CoroutineDispatcherProvider + suspend fun saveUserTokens(card: CardDTO, tokens: List) = withContext(dispatchers.io) { + val userId = getUserWalletId(card) ?: return@withContext val userTokens = tokens.toUserTokensResponse() - networkService.saveUserTokens(userId, userTokens) + tangemTechApi.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) } - suspend fun removeUserTokens(card: CardDTO) { - val userId = getUserWalletId(card) ?: return - val userTokens = emptyList().toUserTokensResponse() - networkService.saveUserTokens(userId, userTokens) - storageService.saveUserTokens(userId, userTokens) - } - - private fun List.toUserTokensResponse(): UserTokensResponse { - val tokensResponse = this.map { it.toTokenResponse() } - return UserTokensResponse( - tokens = tokensResponse, - group = GROUP_DEFAULT_VALUE, - sort = SORT_DEFAULT_VALUE, - ) - } - - suspend fun loadBlockchainsToDerive(card: CardDTO): List { - val userId = getUserWalletId(card) ?: return emptyList() - val blockchainNetworks = loadTokensOffline(card, userId).toBlockchainNetworks() + suspend fun loadBlockchainsToDerive(card: CardDTO): List = withContext(dispatchers.io) { + val userId = getUserWalletId(card) ?: return@withContext emptyList() + val blockchainNetworks = loadTokensOffline(card = card, userId = userId).toBlockchainNetworks() if (DemoHelper.isDemoCardId(card.cardId)) { - return blockchainNetworks - .ifEmpty { loadDemoCurrencies().toBlockchainNetworks() } + return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks) } - return blockchainNetworks + return@withContext blockchainNetworks + } + + private suspend fun loadTokensOffline(card: CardDTO, userId: String): List { + return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) } private fun loadDemoCurrencies(): List { - return DemoHelper.config.demoBlockchains.map { - BlockchainNetwork( - blockchain = it, - derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath, - tokens = emptyList(), - ) - }.flatMap { it.toCurrencies() } + return DemoHelper.config.demoBlockchains + .map { blockchain -> + BlockchainNetwork( + blockchain = blockchain, + derivationPath = blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath, + tokens = emptyList(), + ) + } + .flatMap(BlockchainNetwork::toCurrencies) } - private suspend fun handleGetUserTokensFailure( - card: CardDTO, - userId: String, - error: Throwable, - ): List { - return when (error) { - is NoDataError -> { - val tokens = storageService.getUserTokens(card) - val userTokens = tokens.toUserTokensResponse() - coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = userTokens) } } - tokens - } + private fun List.toUserTokensResponse() = UserTokensResponse( + tokens = CurrencyConverter.convertList(this), + group = GROUP_DEFAULT_VALUE, + sort = SORT_DEFAULT_VALUE, + ) + private suspend fun handleGetUserTokensFailure(card: CardDTO, userId: String, error: Throwable): List { + return when { + error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) -> + storageService.getUserTokens(card).also { + tangemTechApi.saveUserTokens(userId = userId, userTokens = it.toUserTokensResponse()) + } else -> { val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) tokens.distinct() @@ -111,26 +106,36 @@ class UserTokensRepository( } } - private suspend fun loadTokensOffline(card: CardDTO, userId: String): List { - return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) - } - private fun getUserWalletId(card: CardDTO): String? { return UserWalletIdBuilder.card(card).build() ?.stringValue } companion object { - const val SORT_DEFAULT_VALUE = "manual" - const val GROUP_DEFAULT_VALUE = "none" + private const val GROUP_DEFAULT_VALUE = "none" + private const val SORT_DEFAULT_VALUE = "manual" + private const val NOT_FOUND_HTTP_CODE = "404" + + // TODO("After adding DI") get dependencies by DI fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository { val fileReader = AndroidFileReader(context) + val dispatchers = AppCoroutineDispatcherProvider() + val oldUserTokensRepository = OldUserTokensRepository( - fileReader, store.state.domainNetworks.tangemTechService, + fileReader = fileReader, + tangemTechApi = tangemTechService.api, + dispatchers = dispatchers, + ) + val storageService = UserTokensStorageService( + oldUserTokensRepository = oldUserTokensRepository, + fileReader = fileReader, + ) + + return UserTokensRepository( + storageService = storageService, + tangemTechApi = tangemTechService.api, + dispatchers = dispatchers, ) - val storageService = UserTokensStorageService(oldUserTokensRepository, fileReader) - val networkService = UserTokensNetworkService(tangemTechService) - return UserTokensRepository(storageService, networkService) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt index 9e5dac0b68..9df51fb6f4 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -2,8 +2,8 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonAdapter import com.tangem.Log -import com.tangem.datasource.api.tangemTech.UserTokensResponse import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.CardDTO import com.tangem.tap.common.FileReader import com.tangem.tap.features.wallet.models.Currency @@ -13,9 +13,8 @@ class UserTokensStorageService( private val oldUserTokensRepository: OldUserTokensRepository, private val fileReader: FileReader, ) { - private val moshi = MoshiConverter.defaultMoshi() private val userTokensAdapter: JsonAdapter = - moshi.adapter(UserTokensResponse::class.java) + MoshiConverter.networkMoshi.adapter(UserTokensResponse::class.java) fun getUserTokens(userId: String): List? { return try { diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt b/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt new file mode 100644 index 0000000000..3ac6babc72 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tokens/converters/CurrencyConverter.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.domain.tokens.converters + +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.utils.converter.Converter + +/** Converter from domain model [Currency] to data model [UserTokensResponse.Token] */ +object CurrencyConverter : Converter { + + override fun convert(value: Currency) = UserTokensResponse.Token( + id = value.coinId, + networkId = value.blockchain.toNetworkId(), + derivationPath = value.derivationPath, + name = value.currencyName, + symbol = value.currencySymbol, + decimals = value.decimals, + contractAddress = if (value is Currency.Token) value.token.contractAddress else null + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainDao.kt b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainDao.kt index 73eef228ad..4417004531 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainDao.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainDao.kt @@ -10,14 +10,13 @@ data class BlockchainDao( @Json(name = "key") val name: String, @Json(name = "testnet") - val isTestNet: Boolean + val isTestNet: Boolean, ) { @Deprecated("The method is used only for migration from older versions of the app") fun toBlockchain(): Blockchain { val blockchain = Blockchain.values().find { it.name.lowercase() == name.lowercase() } - ?: throw Exception("Invalid BlockchainDao") - return if (!isTestNet) blockchain else blockchain.getTestnetVersion() - ?: throw Exception("Invalid BlockchainDao") + ?: error("Blockchain is null") + return if (!isTestNet) blockchain else blockchain.getTestnetVersion() ?: error("Invalid BlockchainDao") } companion object { diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt index 3b9558c303..8abb99b14d 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt @@ -12,15 +12,16 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle data class BlockchainNetwork( val blockchain: Blockchain, val derivationPath: String?, - val tokens: List + val tokens: List, ) { - constructor( - blockchain: Blockchain, - card: CardDTO, - ) : this( + constructor(blockchain: Blockchain, card: CardDTO) : this( blockchain = blockchain, - derivationPath = if (card.settings.isHDWalletAllowed) blockchain.derivationPath(card.derivationStyle)?.rawPath else null, + derivationPath = if (card.settings.isHDWalletAllowed) { + blockchain.derivationPath(card.derivationStyle)?.rawPath + } else { + null + }, tokens = emptyList(), ) @@ -43,7 +44,8 @@ data class BlockchainNetwork( } override fun hashCode(): Int = calculateHashCode( - blockchain.hashCode(), derivationPath?.hashCode() ?: 0 + blockchain.hashCode(), + derivationPath?.hashCode() ?: 0, ) companion object { @@ -51,7 +53,7 @@ data class BlockchainNetwork( return BlockchainNetwork( walletManager.wallet.blockchain, walletManager.wallet.publicKey.derivationPath?.rawPath, - walletManager.cardTokens.toList() + walletManager.cardTokens.toList(), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/models/TokenDao.kt b/app/src/main/java/com/tangem/tap/domain/tokens/models/TokenDao.kt index a8a717b1ce..ef6f541f6e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/models/TokenDao.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/models/TokenDao.kt @@ -26,5 +26,4 @@ data class TokenDao( customIconUrl?.let { this.setCustomIconUrl(it) } } } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt index faa4628b9b..b0224afdac 100644 --- a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt @@ -47,10 +47,10 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { is WalletDataModel.VerifiedOnline, is WalletDataModel.SameCurrencyTransactionInProgress, is WalletDataModel.TransactionInProgress, - is WalletDataModel.NoAccount, - -> { - if (walletData.fiatRate == null) TotalFiatBalanceStatus.Error - else TotalFiatBalanceStatus.Loaded + is WalletDataModel.NoAccount -> if (walletData.fiatRate == null) { + TotalFiatBalanceStatus.Error + } else { + TotalFiatBalanceStatus.Loaded } is WalletDataModel.Unreachable, is WalletDataModel.MissedDerivation, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt index d4e00cb3fe..b173602e3d 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -10,7 +10,8 @@ import com.tangem.operations.PreflightReadTask import com.tangem.tap.domain.tasks.product.ScanProductTask class FinalizeTwinTask( - private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair, + private val twinPublicKey: ByteArray, + private val issuerKeys: KeyPair, ) : CardSessionRunnable { override val allowsRequestAccessCodeFromRepository: Boolean = false diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index b910fcb589..8e2cea35fa 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -12,7 +12,7 @@ import com.tangem.datasource.api.common.MoshiConverter import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.operations.wallet.CreateWalletResponse -import com.tangem.tap.common.AssetReader +import com.tangem.datasource.utils.AssetReader import com.tangem.tap.tangemSdkManager class TwinCardsManager( @@ -83,7 +83,7 @@ class TwinCardsManager( } private fun getAdapter(): JsonAdapter> { - return MoshiConverter.defaultMoshi().adapter( + return MoshiConverter.sdkMoshi.adapter( Types.newParameterizedType(List::class.java, Issuer::class.java), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt index 36d3b037ff..95d1d76fdd 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt @@ -11,14 +11,13 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget - /** [REDACTED_AUTHOR] */ class TwinsCardWidget( val leapfrogWidget: LeapfrogWidget, private val deviceScaleFactor: Float = 1f, - val getTopOfAnchorViewForActivateState: () -> Float + val getTopOfAnchorViewForActivateState: () -> Float, ) { var state: TwinCardsWidgetState? = null @@ -37,7 +36,7 @@ class TwinsCardWidget( val animator = createAnimator(animate) animator.playTogether( createAnimator(TwinCardNumber.First, createWelcomeProperties(TwinCardNumber.First)), - createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second)) + createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second)), ) animator.doOnEnd { onEnd() } leapfrogWidget.fold(animate) { animator.start() } @@ -50,7 +49,7 @@ class TwinsCardWidget( val animator = createAnimator(animate) animator.playTogether( createAnimator(TwinCardNumber.First, createLeapfrogProperties(TwinCardNumber.First)), - createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second)) + createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second)), ) animator.doOnEnd { leapfrogWidget.initViews() @@ -66,7 +65,7 @@ class TwinsCardWidget( val animator = createAnimator(animate) animator.playTogether( createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)), - createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)) + createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)), ) animator.doOnEnd { onEnd() } animator.start() @@ -85,6 +84,7 @@ class TwinsCardWidget( return animator } + @Suppress("MagicNumber") private fun createWelcomeProperties(cardNumber: TwinCardNumber): TwinsCardProperties { return when (cardNumber) { TwinCardNumber.First -> { @@ -118,6 +118,7 @@ class TwinsCardWidget( ) } + @Suppress("MagicNumber") private fun createActivateProperties(cardNumber: TwinCardNumber): TwinsCardProperties { val topOfAnchorView = getTopOfAnchorViewForActivateState() val twinProperties = TwinsCardProperties.from(getLeapViewByCardNumber(cardNumber).state) diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt index 31d35c4a06..2a415c6eda 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt @@ -15,7 +15,8 @@ import com.tangem.operations.issuerAndUserData.WriteIssuerDataCommand import com.tangem.operations.sign.SignHashCommand class WriteProtectedIssuerDataTask( - private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair, + private val twinPublicKey: ByteArray, + private val issuerKeys: KeyPair, ) : CardSessionRunnable { override fun run( @@ -33,12 +34,19 @@ class WriteProtectedIssuerDataTask( when (readResult) { is CompletionResult.Success -> { writeIssuerData( - twinPublicKey, issuerKeys, signResult.data.signature, - readResult.data, session, callback + twinPublicKey, + issuerKeys, + signResult.data.signature, + readResult.data, + session, + callback, ) } - is CompletionResult.Failure -> callback(CompletionResult.Failure( - readResult.error)) + is CompletionResult.Failure -> callback( + CompletionResult.Failure( + readResult.error, + ), + ) } } } @@ -47,8 +55,11 @@ class WriteProtectedIssuerDataTask( } } + @Suppress("LongParameterList") private fun writeIssuerData( - twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray, + twinPublicKey: ByteArray, + issuerKeys: KeyPair, + cardSignature: ByteArray, readResponse: ReadIssuerDataResponse, session: CardSession, callback: ( diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index 9e7619e7c5..91d3b8dd91 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -6,25 +6,71 @@ import com.tangem.tap.domain.model.UserWallet import kotlinx.coroutines.flow.Flow interface UserWalletsListManager { + /** + * [Flow] with all saved [UserWallet]s updates + * */ val userWallets: Flow> + + /** + * [Flow] with selected [UserWallet] updates + * */ val selectedUserWallet: Flow + + /** + * Selected [UserWallet] + * */ val selectedUserWalletSync: UserWallet? + + /** + * Indicates that all [UserWallet]s is unlocked + * + * @see [isLockedSync] + * @see [UserWallet.isLocked] + * */ val isLocked: Flow + + /** + * Indicates that all [UserWallet]s is unlocked + * + * Sync version + * + * @see [isLocked] + * @see [UserWallet.isLocked] + * */ val isLockedSync: Boolean + val hasSavedUserWallets: Boolean + /** + * Receive saved [UserWallet]s, populate [userWallets] flow with it and set [isLocked] as false. + * Require biometric user authorization + * + * @return [CompletionResult] of operation, with selected [UserWallet] + * or null if there is no selected [UserWallet] + * */ suspend fun unlockWithBiometry(): CompletionResult + + /** + * Remove [UserWallet]s from [userWallets] and set [isLocked] as true + * */ fun lock() + /** + * Set [UserWallet] with provided [UserWalletId] as selected + * + * @param userWalletId [UserWalletId] of [UserWallet] which must be selected + * + * @return [CompletionResult] of operation with selected [UserWallet] + * */ suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** * Save provided user wallet and set it as selected * @param userWallet [UserWallet] to save - * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an - * already saved card + * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries + * to save an already saved card * @return [CompletionResult] of operation - * */ + */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult /** @@ -38,10 +84,30 @@ interface UserWalletsListManager { * */ suspend fun update(userWalletId: UserWalletId, update: (UserWallet) -> UserWallet): CompletionResult + /** + * Delete saved [UserWallet]s with provided [UserWalletId]s + * + * @param userWalletIds [UserWalletId]s of [UserWallet]s which must be deleted + * + * @return [CompletionResult] of operation + * */ suspend fun delete(userWalletIds: List): CompletionResult + + /** + * Clear all saved [UserWallet]s and set [isLocked] as true + * + * @return [CompletionResult] of operation + * */ suspend fun clear(): CompletionResult + /** + * Get [UserWallet] with provided [UserWalletId] + * May terminate with [NoSuchElementException] if [UserWallet] is not found + * + * @return [CompletionResult] of operation with found [UserWallet] + * */ suspend fun get(userWalletId: UserWalletId): CompletionResult + // For provider companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index db1db2ebbd..748665ccea 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -59,12 +59,7 @@ internal class BiometricUserWalletsListManager( } override fun lock() { - state.update { prevState -> - prevState.copy( - encryptionKeys = emptyList(), - isLocked = true, - ) - } + state.update { State() } } override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult = catching { @@ -143,7 +138,7 @@ internal class BiometricUserWalletsListManager( .flatMap { keysRepository.clear() } .map { selectedUserWalletRepository.set(null) - state.update { State() } + lock() } } @@ -219,13 +214,18 @@ internal class BiometricUserWalletsListManager( private suspend fun loadModels(): CompletionResult { return getSavedUserWallets() .map { userWallets -> - if (userWallets.isNotEmpty()) state.update { prevState -> - val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId } + if (userWallets.isNotEmpty()) { + state.update { prevState -> + val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId } - prevState.copy( - userWallets = wallets, - selectedUserWalletId = findOrSetSelectedUserWalletId(prevState.selectedUserWalletId, wallets), - ) + prevState.copy( + userWallets = wallets, + selectedUserWalletId = findOrSetSelectedUserWalletId( + prevSelectedWalletId = prevState.selectedUserWalletId, + userWallets = wallets, + ), + ) + } } } .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 11e558535d..5f72d40ab7 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -134,7 +134,7 @@ internal class BiometricUserWalletsKeysRepository( } private suspend fun deleteUserWalletsIds(userWalletsIds: List) { - val remainingIds = (getUserWalletsIds() - userWalletsIds.toSet()) + val remainingIds = getUserWalletsIds() - userWalletsIds.toSet() withContext(Dispatchers.IO) { secureStorage.store(remainingIds.encode(), StorageKey.UserWalletIds.name) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 673ec51a10..8d1fdddb9f 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -15,7 +15,6 @@ import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository -import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.sensitiveInformation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -180,7 +179,7 @@ internal class DefaultUserWalletsSensitiveInformationRepository( } class SensitiveInformationIv(userWalletId: String) : StorageKey { - override val name: String = "user_wallet_sensitive_information_iv_${userWalletId}" + override val name: String = "user_wallet_sensitive_information_iv_$userWalletId" } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt index fb77a29584..2738438ac1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt @@ -5,25 +5,70 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.features.wallet.models.Currency interface WalletCurrenciesManager { + /** + * Update [UserWallet] currencies with same blockchain as provided [Currency] blockchain + * [UserWallet] currencies updates can be observed + * with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll] + * or [com.tangem.tap.domain.walletStores.WalletStoresManager.get] + * + * @param userWallet [UserWallet] which currencies will be updated + * @param currency [Currency] to find other currencies to update + * + * @return [CompletionResult] of operation + * */ suspend fun update( userWallet: UserWallet, currency: Currency, ): CompletionResult + /** + * Add list of [Currency] to [UserWallet]. + * [UserWallet] currencies updates can be observed + * with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll] + * or [com.tangem.tap.domain.walletStores.WalletStoresManager.get] + * + * @param userWallet [UserWallet] to add currencies + * @param currenciesToAdd list of [Currency] to add [UserWallet] + * + * @return [CompletionResult] of operation + * */ suspend fun addCurrencies( userWallet: UserWallet, currenciesToAdd: List, ): CompletionResult + /** + * Remove [Currency] from [UserWallet] + * [UserWallet] currencies updates can be observed + * with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll] + * or [com.tangem.tap.domain.walletStores.WalletStoresManager.get] + * + * @param userWallet [UserWallet] which currency will be removed + * @param currencyToRemove [Currency] to remove from [UserWallet] + * + * @return [CompletionResult] of operation + * */ suspend fun removeCurrency( userWallet: UserWallet, currencyToRemove: Currency, ): CompletionResult + /** + * Remove list of [Currency] from [UserWallet] + * [UserWallet] currencies updates can be observed + * with [com.tangem.tap.domain.walletStores.WalletStoresManager.getAll] + * or [com.tangem.tap.domain.walletStores.WalletStoresManager.get] + * + * @param userWallet [UserWallet] which currency will be removed + * @param currenciesToRemove list of [Currency] to remove from [UserWallet] + * + * @return [CompletionResult] of operation + * */ suspend fun removeCurrencies( userWallet: UserWallet, currenciesToRemove: List, ): CompletionResult + // For provider companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index 2b5a565f7e..25e54b765d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -37,8 +37,8 @@ internal class DefaultWalletCurrenciesManager( ): CompletionResult = withContext(Dispatchers.Default) { val walletStore = walletStoresRepository.getSync(userWallet.walletId) .find { - it.blockchain == currency.blockchain - && it.derivationPath?.rawPath == currency.derivationPath + it.blockchain == currency.blockchain && + it.derivationPath?.rawPath == currency.derivationPath } if (walletStore != null) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt index de8c07b6d2..826c158a8b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt @@ -11,6 +11,7 @@ sealed class WalletStoresError(code: Int) : TangemError(code) { override val message: String? get() = customMessage + @Suppress("MagicNumber") class FetchFiatRatesError( currencies: List, override val cause: Throwable?, @@ -18,18 +19,22 @@ sealed class WalletStoresError(code: Int) : TangemError(code) { override var customMessage: String = "Failed to fetch fiat rates for currencies $currencies" } + @Suppress("MagicNumber") class UnknownBlockchain : WalletStoresError(60012) { override var customMessage: String = "Unknown blockchain" } + @Suppress("MagicNumber") object NoInternetConnection : WalletStoresError(60013) { override var customMessage: String = "No internet connection" } + @Suppress("MagicNumber") class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(60014) { override var customMessage: String = "Wallet manager can not be created for $blockchain" } + @Suppress("MagicNumber") class UpdateWalletManagerError( blockchain: Blockchain, override val cause: Throwable, diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt index f1fdaff7aa..e4bebc2c07 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt @@ -7,26 +7,78 @@ import com.tangem.tap.domain.model.WalletStoreModel import kotlinx.coroutines.flow.Flow interface WalletStoresManager { + /** + * Get all [WalletStoreModel]s updates + * + * @return [Flow] with map of [WalletStoreModel] list assigned by [UserWalletId] + * */ fun getAll(): Flow>> + + /** + * Get [WalletStoreModel]s updates which associated with provided [UserWalletId] + * + * @param userWalletId [UserWalletId] of user wallet + * + * @return [Flow] with [WalletStoreModel] list + * */ fun get(userWalletId: UserWalletId): Flow> suspend fun getSync(userWalletId: UserWalletId): List + /** + * Delete [WalletStoreModel]s associated with provided [UserWalletId]s + * + * @param userWalletsIds [UserWalletId] list + * + * @return [CompletionResult] of operation + * */ suspend fun delete(userWalletsIds: List): CompletionResult + + /** + * Clear all [WalletStoreModel]s + * + * @return [CompletionResult] of operation + * */ suspend fun clear(): CompletionResult + /** + * Fetch wallet stores associated with provided [UserWallet]. Fetched [WalletStoreModel]s updates can be observed + * with [get] and [getAll] methods + * + * @param userWallet [UserWallet] to fetch [WalletStoreModel]s + + * + * @return [CompletionResult] of operation + * */ suspend fun fetch( userWallet: UserWallet, refresh: Boolean = false, ): CompletionResult + /** + * Fetch wallet stores associated with provided [UserWallet]s. Fetched [WalletStoreModel]s updates can be observed + * with [get] and [getAll] methods + * + * @param userWallets [UserWallet]s list to fetch [WalletStoreModel]s + + * + * @return [CompletionResult] of operation + * */ suspend fun fetch( userWallets: List, refresh: Boolean = false, ): CompletionResult + /** + * Update [WalletStoreModel]s amounts associated with provided [UserWallet]s + * + * @param userWallets [UserWallet]s list to update [WalletStoreModel]s amounts + * + * @return [CompletionResult] of operation + * */ suspend fun updateAmounts( userWallets: List, ): CompletionResult + // For provider companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt index 7fc7ba38e7..d89d4ea6aa 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt @@ -8,6 +8,7 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletManagersRepository import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletStoresRepository +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider fun WalletStoresRepository.Companion.provideDefaultImplementation(): WalletStoresRepository { return DefaultWalletStoresRepository() @@ -22,5 +23,9 @@ fun WalletManagersRepository.Companion.provideDefaultImplementation( fun WalletAmountsRepository.Companion.provideDefaultImplementation( tangemTechService: TangemTechService, ): WalletAmountsRepository { - return DefaultWalletAmountsRepository(tangemTechService) + // TODO("After adding DI") get dependencies by DI + return DefaultWalletAmountsRepository( + tangemTechApi = tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 93c70be9ef..a7dc0a853d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -14,8 +14,7 @@ import com.tangem.common.flatMap import com.tangem.common.flatMapOnFailure import com.tangem.common.fold import com.tangem.common.map -import com.tangem.common.services.Result -import com.tangem.datasource.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency @@ -36,10 +35,12 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage import com.tangem.tap.features.demo.DemoHelper +import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransactionType import com.tangem.tap.features.wallet.models.filterByCoin import com.tangem.tap.features.wallet.models.getPendingTransactions import com.tangem.tap.network.NetworkConnectivity +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -49,8 +50,10 @@ import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal +@Suppress("LargeClass") internal class DefaultWalletAmountsRepository( - private val tangemTechService: TangemTechService, + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, ) : WalletAmountsRepository { private val walletStoresStorage = WalletStoresStorage private val walletManagersStorage = WalletManagerStorage @@ -59,13 +62,16 @@ internal class DefaultWalletAmountsRepository( userWallets: List, fiatCurrency: FiatCurrency, ): CompletionResult { - return if (userWallets.isEmpty()) CompletionResult.Success(Unit) - else withContext(Dispatchers.Default) { - awaitAll( - async { fetchAmountsForUserWallets(userWallets) }, - async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) }, - ) - .fold() + return if (userWallets.isEmpty()) { + CompletionResult.Success(Unit) + } else { + withContext(Dispatchers.Default) { + awaitAll( + async { fetchAmountsForUserWallets(userWallets) }, + async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) }, + ) + .fold() + } } } @@ -81,16 +87,19 @@ internal class DefaultWalletAmountsRepository( userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult { - return if (walletStores.isEmpty()) CompletionResult.Success(Unit) - else withContext(Dispatchers.Default) { - val userWalletId = userWallet.walletId - val scanResponse = userWallet.scanResponse + return if (walletStores.isEmpty()) { + CompletionResult.Success(Unit) + } else { + withContext(Dispatchers.Default) { + val userWalletId = userWallet.walletId + val scanResponse = userWallet.scanResponse - awaitAll( - async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) }, - async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) }, - ) - .fold() + awaitAll( + async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) }, + async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) }, + ) + .fold() + } } } @@ -116,38 +125,30 @@ internal class DefaultWalletAmountsRepository( val coinsIds = currencies.mapNotNull { it.coinId }.distinct().toList() - val fiatRatesResult = withContext(Dispatchers.IO) { - tangemTechService.rates( - currency = fiatCurrency.code, - ids = coinsIds, - ) - } + return withContext(dispatchers.io) { + runCatching { tangemTechApi.getRates(fiatCurrency.code.lowercase(), coinsIds.joinToString(",")) } + .onSuccess { + updateWalletStoresWithFiatRates(walletStores = walletStoresInternal, fiatRates = it.rates) + return@withContext CompletionResult.Success(Unit) + } + .onFailure { + val error = WalletStoresError.FetchFiatRatesError( + currencies = currencies.map(Currency::currencySymbol).toList(), + cause = it, + ) - return when (fiatRatesResult) { - is Result.Success -> { - updateWalletStoresWithFiatRates( - walletStores = walletStoresInternal, - fiatRates = fiatRatesResult.data.rates, - ) - - CompletionResult.Success(Unit) - } - is Result.Failure -> { - val error = WalletStoresError.FetchFiatRatesError( - currencies = currencies.map { it.currencySymbol }.toList(), - cause = fiatRatesResult.error, - ) - - Timber.e( - error, - """ + Timber.e( + error, + """ Unable to fetch fiat rates |- Coins ids: $coinsIds - """.trimIndent(), - ) + """.trimIndent(), + ) - CompletionResult.Failure(error) - } + return@withContext CompletionResult.Failure(error) + } + + error("Unreachable code because runCatching must return result") } } @@ -209,7 +210,7 @@ internal class DefaultWalletAmountsRepository( updateWalletStoreWithAmounts( walletStore = walletStore, updatedWallet = walletManager.wallet, - // fixme move DemoHelper to Demo core module maybe + // FIXME: move DemoHelper to Demo core module maybe isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId), ) } @@ -423,8 +424,10 @@ internal class DefaultWalletAmountsRepository( val error = WalletStoresError.NoInternetConnection Timber.e(error) CompletionResult.Failure(error) - } else withContext(Dispatchers.IO) { - catching { block() } + } else { + withContext(Dispatchers.IO) { + catching { block() } + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index 6355f2f6d6..1758c2fbf9 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -38,7 +38,9 @@ internal class DefaultWalletManagersRepository( return findOrMakeInternal(userWallet, blockchainNetwork) } - override suspend fun findOrMakeSingleCurrencyWalletManager(userWallet: UserWallet): CompletionResult { + override suspend fun findOrMakeSingleCurrencyWalletManager( + userWallet: UserWallet, + ): CompletionResult { return findOrMakeInternal(userWallet, blockchainNetwork = null) } @@ -64,7 +66,7 @@ internal class DefaultWalletManagersRepository( ): CompletionResult { val scanResponse = userWallet.scanResponse val blockchain = blockchainNetwork?.blockchain - ?: scanResponse.getBlockchain().let { blockchain -> + ?: scanResponse.cardTypesResolver.getBlockchain().let { blockchain -> if (scanResponse.card.isTestCard) blockchain.getTestnetVersion() else blockchain } val derivationParams = getDerivationParams( @@ -154,7 +156,7 @@ internal class DefaultWalletManagersRepository( ): CompletionResult { val walletManager = this return catching { - val tokens = blockchainNetwork?.tokens ?: listOfNotNull(scanResponse.getPrimaryToken()) + val tokens = blockchainNetwork?.tokens ?: listOfNotNull(scanResponse.cardTypesResolver.getPrimaryToken()) if (tokens != cardTokens) { cardTokens.clear() @@ -180,8 +182,11 @@ internal class DefaultWalletManagersRepository( blockchain: Blockchain?, ): WalletManager? { return walletManagersStorage.getAll().first()[userWalletId]?.let { userWalletManagers -> - if (blockchain == null) userWalletManagers.firstOrNull() - else userWalletManagers.find { it.wallet.blockchain == blockchain } + if (blockchain == null) { + userWalletManagers.firstOrNull() + } else { + userWalletManagers.find { it.wallet.blockchain == blockchain } + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 0c3e47861c..47ef539e1f 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -113,9 +113,9 @@ private inline fun List.replaceWalletStores( if (currentWalletStore != updatedWalletStore) { Timber.d( """ - Update wallet store in storage - |- User wallet ID: ${updatedWalletStore.userWalletId} - |- Blockchain: ${updatedWalletStore.blockchain} + Update wallet store in storage + |- User wallet ID: ${updatedWalletStore.userWalletId} + |- Blockchain: ${updatedWalletStore.blockchain} """.trimIndent(), ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt index 3646e38f6a..866711e543 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt @@ -11,8 +11,8 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock internal object WalletManagerStorage { - private val managers = - MutableSharedFlow>>(replay = 1) + private val managers = MutableSharedFlow>>(replay = 1) + private val mutex = Mutex() init { managers.tryEmit(hashMapOf()) @@ -22,7 +22,6 @@ internal object WalletManagerStorage { return managers.asSharedFlow() } - private val mutex = Mutex() suspend fun update( f: suspend (HashMap>) -> HashMap>, ) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt index 93cdfb508f..932abce88b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt @@ -11,8 +11,8 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock internal object WalletStoresStorage { - private val stores = - MutableSharedFlow>>(replay = 1) + private val stores = MutableSharedFlow>>(replay = 1) + private val mutex = Mutex() init { stores.tryEmit(hashMapOf()) @@ -22,7 +22,6 @@ internal object WalletStoresStorage { return stores.asSharedFlow() } - private val mutex = Mutex() suspend fun update( f: suspend (HashMap>) -> HashMap>, ) { 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 d35cc7c2e0..5dec1d0850 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 @@ -12,69 +12,65 @@ import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder import com.trustwallet.walletconnect.models.binance.tradeOrderSerializer import timber.log.Timber -class BnbHelper { - companion object { +object BnbHelper { + fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer { + val input = order.msgs.first().inputs.first() + val output = order.msgs.first().inputs.first() - fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer { - val input = order.msgs.first().inputs.first() - val output = order.msgs.first().inputs.first() + val currency = + input.coins.firstOrNull()?.denom ?: Blockchain.Binance + val amount = input.coins + .mapNotNull { if (it.denom == currency) it.amount else null } + .sum() + .toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripZeroPlainString() - val currency = - input.coins.firstOrNull()?.denom ?: Blockchain.Binance - val amount = input.coins - .mapNotNull { if (it.denom == currency) it.amount else null } - .sum() - .toBigDecimal() - .movePointLeft(Blockchain.Binance.decimals()) - .stripZeroPlainString() - - val gson = GsonBuilder() - .registerTypeAdapter(tradeOrderSerializer) - .serializeNulls() - .create() - - return BinanceMessageData.Transfer( - outputAddress = output.address, - amount = amount, - address = input.address, - data = gson.toJson(order).toByteArray().calculateSha256() - ) - } - - fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade { - val address = order.msgs.first().sender - - val tradeData = order.msgs.map { - val price = it.price.toBigDecimal() - .movePointLeft(Blockchain.Binance.decimals()) - .stripTrailingZeros() - val quantity = it.quantity.toBigDecimal() - .movePointLeft(Blockchain.Binance.decimals()) - .stripTrailingZeros() - - val amount = price * quantity - val symbol = it.symbol.substringBefore("-") - - TradeData( - price = "$price ${Blockchain.Binance.currency}", - quantity = "$quantity $symbol", - amount = "$amount ${Blockchain.Binance.currency}", - symbol = symbol - ) - } - val gson = GsonBuilder() - .registerTypeAdapter(tradeOrderSerializer) - .serializeNulls() - .create() - val serialized = gson.toJson(order) - Timber.d(serialized) - - return BinanceMessageData.Trade( - tradeData = tradeData, - address = address, - data = serialized.toByteArray().calculateSha256() - ) - } + val gson = GsonBuilder() + .registerTypeAdapter(tradeOrderSerializer) + .serializeNulls() + .create() + return BinanceMessageData.Transfer( + outputAddress = output.address, + amount = amount, + address = input.address, + data = gson.toJson(order).toByteArray().calculateSha256(), + ) } -} + + fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade { + val address = order.msgs.first().sender + + val tradeData = order.msgs.map { + val price = it.price.toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripTrailingZeros() + val quantity = it.quantity.toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripTrailingZeros() + + val amount = price * quantity + val symbol = it.symbol.substringBefore("-") + + TradeData( + price = "$price ${Blockchain.Binance.currency}", + quantity = "$quantity $symbol", + amount = "$amount ${Blockchain.Binance.currency}", + symbol = symbol, + ) + } + val gson = GsonBuilder() + .registerTypeAdapter(tradeOrderSerializer) + .serializeNulls() + .create() + val serialized = gson.toJson(order) + Timber.d(serialized) + + return BinanceMessageData.Trade( + tradeData = tradeData, + address = address, + data = serialized.toByteArray().calculateSha256(), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt index b366229775..a302d44747 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt @@ -9,50 +9,28 @@ import com.google.gson.JsonParser import com.google.gson.annotations.SerializedName import com.tangem.blockchain.common.Blockchain import com.trustwallet.walletconnect.JSONRPC_VERSION -import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage -class EthSignHelper { - companion object { - private val gson: Gson by lazy { - GsonBuilder() - .setPrettyPrinting() - .serializeNulls() - .create() - } +object EthSignHelper { + private val gson: Gson by lazy { + GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .create() + } - fun parseCustomRequest(data: String): CustomJsonRpcRequest { - return gson.fromJson(data) - } - - fun tryToParseEthTypedMessage(request: CustomJsonRpcRequest): WCEthereumSignMessage? { - return if (request.method == WCMethodExtended.ETH_SIGN_TYPE_DATA_V4) { - WCEthereumSignMessage( - listOf( - request.params[0].asString, - request.params[1].asString, - ), - WCEthereumSignMessage.WCSignType.TYPED_MESSAGE - ) - } else { - null - } - } - - fun tryToParseEthTypedMessageString(message: String): String? { - return try { - val messageString = message - .replace("\\", "") - .removePrefix("\"") - .removeSuffix("\"") - val messageJson = JsonParser().parse(messageString) - val filteredMap = gson.fromJson>(messageJson) - .filterKeys { it == "domain" || it == "message"} - - gson.toJson(filteredMap) - } catch (exception: Exception) { - null - } + fun tryToParseEthTypedMessageString(message: String): String? { + return try { + val messageString = message + .replace("\\", "") + .removePrefix("\"") + .removeSuffix("\"") + val messageJson = JsonParser().parse(messageString) + val filteredMap = gson.fromJson>(messageJson) + .filterKeys { it == "domain" || it == "message" } + gson.toJson(filteredMap) + } catch (exception: Exception) { + null } } } @@ -61,7 +39,7 @@ data class CustomJsonRpcRequest( val id: Long, val jsonrpc: String = JSONRPC_VERSION, val method: WCMethodExtended?, - val params: JsonArray + val params: JsonArray, ) { fun blockchainFromChainId(): Blockchain? { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index d2b77a628c..8759dc91d4 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -3,8 +3,8 @@ package com.tangem.tap.domain.walletconnect import com.tangem.blockchain.common.Blockchain import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.guard -import com.tangem.domain.common.ScanResponse import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction @@ -28,6 +28,7 @@ import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder import com.trustwallet.walletconnect.models.binance.WCBinanceTxConfirmParam import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction +import com.trustwallet.walletconnect.models.session.WCAddNetwork import com.trustwallet.walletconnect.models.session.WCSession import com.trustwallet.walletconnect.models.session.WCSessionUpdate import kotlinx.coroutines.delay @@ -42,6 +43,7 @@ import java.util.* import java.util.concurrent.TimeUnit import kotlin.collections.set +@Suppress("LargeClass") class WalletConnectManager { private var cardId: String? = null @@ -103,7 +105,7 @@ class WalletConnectManager { fun updateSession(session: WalletConnectSession) { val updatedSession = sessions[session.session]?.copy( - wallet = session.wallet + wallet = session.wallet, ) if (updatedSession != null) { sessions[session.session] = updatedSession @@ -114,18 +116,18 @@ class WalletConnectManager { sessions[session.session]?.client?.updateSession( accounts = listOfNotNull(session.getAddress()), chainId = session.wallet.blockchain?.getChainId(), - approved = true + approved = true, ) - val updatedSession = sessions[session.session]?.copy( - wallet = session.wallet + wallet = session.wallet, ) if (updatedSession != null) { sessions[session.session] = updatedSession } } + @Suppress("MagicNumber") private fun setupConnectionTimeoutCheck(session: WCSession) { scope.launch { delay(20_000) @@ -172,7 +174,7 @@ class WalletConnectManager { val accounts = listOf(blockchain.makeAddresses(key).first().value) val approved = activeData.client.approveSession( accounts = accounts, - chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!! + chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!!, ) if (approved) { val walletConnectSession = WalletConnectSession( @@ -180,23 +182,22 @@ class WalletConnectManager { remotePeerId = activeData.remotePeerId, wallet = activeData.wallet, session = session, - peerMeta = activeData.peerMeta!! + peerMeta = activeData.peerMeta!!, ) walletConnectRepository.saveSession(walletConnectSession) store.dispatchOnMain( WalletConnectAction.ApproveSession.Success( - walletConnectSession - ) + walletConnectSession, + ), ) - } } fun removeSimilarSessions(activeData: WalletConnectActiveData) { val sessionsToRemove = sessions.filter { - it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true - && it.value.peerMeta?.url == activeData.peerMeta?.url - && it.value.session != activeData.session + it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true && + it.value.peerMeta?.url == activeData.peerMeta?.url && + it.value.session != activeData.session } Timber.d("RemoveSimilarSessions: ${sessionsToRemove.values.map { it.client.session }}") sessionsToRemove.forEach { disconnect(it.value.session) } @@ -244,14 +245,14 @@ class WalletConnectManager { transaction = transaction, session = session, id = id, - type = type + type = type, ).guard { sessions[session.session] = activeData.copy(transactionData = null) store.dispatchOnMain( WalletConnectAction.RejectRequest( session.session, - id - ) + id, + ), ) return@launch } @@ -260,9 +261,9 @@ class WalletConnectManager { store.dispatchOnMain( GlobalAction.ShowDialog( WalletConnectDialog.RequestTransaction( - data.dialogData - ) - ) + data.dialogData, + ), + ), ) } } @@ -287,7 +288,9 @@ class WalletConnectManager { } fun signBnb( - id: Long, data: ByteArray, sessionData: WCSession, + id: Long, + data: ByteArray, + sessionData: WCSession, ) { val activeData = sessions[sessionData] ?: return scope.launch { @@ -312,15 +315,17 @@ class WalletConnectManager { val activeData = sessions[session.session] ?: return scope.launch { val data = WalletConnectSdkHelper().prepareDataForPersonalSign( - message = message, session = session, id = id + message = message, + session = session, + id = id, ) sessions[session.session] = activeData.copy(personalSignData = data) store.dispatchOnMain( GlobalAction.ShowDialog( WalletConnectDialog.PersonalSign( - data.dialogData - ) - ) + data.dialogData, + ), + ), ) } } @@ -346,7 +351,7 @@ class WalletConnectManager { } } - + @Suppress("LongMethod", "ComplexMethod") fun setListeners(client: WCClient) { client.onSessionRequest = { id: Long, peer: WCPeerMeta -> Timber.d("OnSessionRequest: $peer") @@ -393,8 +398,8 @@ class WalletConnectManager { transaction = transaction, session = sessionData, id = id, - type = WcTransactionType.EthSendTransaction - ) + type = WcTransactionType.EthSendTransaction, + ), ) } } @@ -411,8 +416,8 @@ class WalletConnectManager { transaction = transaction, session = sessionData, id = id, - type = WcTransactionType.EthSignTransaction - ) + type = WcTransactionType.EthSignTransaction, + ), ) } } @@ -428,22 +433,22 @@ class WalletConnectManager { WalletConnectAction.HandlePersonalSignRequest( message, sessionData, - id - ) + id, + ), ) } } - client.onBnbCancel = { id: Long, order: WCBinanceCancelOrder -> - } client.onBnbTrade = { id: Long, order: WCBinanceTradeOrder -> sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.BinanceTransaction.Trade( - id = id, order = order, sessionData = sessionData - ) + id = id, + order = order, + sessionData = sessionData, + ), ) } } @@ -451,8 +456,10 @@ class WalletConnectManager { sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.BinanceTransaction.Transfer( - id = id, order = order, sessionData = sessionData - ) + id = id, + order = order, + sessionData = sessionData, + ), ) } } @@ -467,23 +474,36 @@ class WalletConnectManager { } } client.onWalletChangeNetwork = { id: Long, chainId: Int -> - Timber.d("On WC Switch Chain") - val blockchain = Blockchain.fromChainId(chainId) - Timber.d("WC switch chainID\nNew Blockchain: $blockchain") - val session = sessions[client.session]?.toWalletConnectSession() - if (session != null) { - store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session)) + switchChain(chainId, client) + } + client.onWalletAddNetwork = { id: Long, network: WCAddNetwork -> + // TODO + // In fact this method is used to add a EVM network. It provides RPC url and chain ID. + // Here now it just tries to switch to a EVM network with a provided chain ID. + try { + val chainId = Integer.decode(network.chainIdHex) + switchChain(chainId, client) + } catch (exception: Exception) { + Timber.d("WC add network error: chain could not be parsed") } } } + private fun switchChain(chainId: Int, client: WCClient) { + val blockchain = Blockchain.fromChainId(chainId) + Timber.d("WC switch chainID\nNew Blockchain: $blockchain") + val session = sessions[client.session]?.toWalletConnectSession() + if (session != null) { + store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session)) + } + } + companion object { - private val tangemPeerMeta = - WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com") + const val WC_SCHEME = "wc" + + private val tangemPeerMeta = WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com") fun isCorrectWcUri(string: String): Boolean = WCSession.from(string) != null - - const val WC_SCHEME = "wc" } } @@ -504,11 +524,12 @@ data class WalletConnectActiveData( remotePeerId = remotePeerId, wallet = wallet, session = session, - peerMeta = peerMeta + peerMeta = peerMeta, ) } } +@Suppress("MagicNumber") class RetryInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request: Request = chain.request() diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt index 6b2a457e43..19e2e44e42 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt @@ -3,43 +3,39 @@ package com.tangem.tap.domain.walletconnect import com.tangem.blockchain.common.Blockchain import com.trustwallet.walletconnect.models.WCPeerMeta -class WalletConnectNetworkUtils { - - companion object { - - fun parseBlockchain( - chainId: Int?, - peer: WCPeerMeta, - ): Blockchain? { - return when { - peer.url.contains("pancakeswap.finance") -> { - Blockchain.BSC - } - chainId != null -> { - Blockchain.fromChainId(chainId) - } - peer.url.contains("matic.network") || peer.name == "Polygon" -> { - Blockchain.Polygon - } - peer.url.contains("binance.org") || peer.name.contains("Binance") -> { - if (peer.icons.firstOrNull()?.contains("testnet") == true) { - Blockchain.BinanceTestnet - } else { - Blockchain.Binance - } - } - peer.name.contains("BSC") -> { - Blockchain.BSC - } - peer.url.contains("honeyswap.1hive.eth.limo") -> { - // Check if something's changed after this bug report: - // https://github.com/1Hive/honeyswap-interface/issues/83 - Blockchain.Gnosis - } - else -> { - Blockchain.Ethereum +object WalletConnectNetworkUtils { + fun parseBlockchain(chainId: Int?, peer: WCPeerMeta): Blockchain? { + return when { + peer.url.contains("pancakeswap.finance") -> { + Blockchain.BSC + } + peer.url.contains("optimism") -> { + Blockchain.Optimism + } + chainId != null -> { + Blockchain.fromChainId(chainId) + } + peer.url.contains("matic.network") || peer.name == "Polygon" -> { + Blockchain.Polygon + } + peer.url.contains("binance.org") || peer.name.contains("Binance") -> { + if (peer.icons.firstOrNull()?.contains("testnet") == true) { + Blockchain.BinanceTestnet + } else { + Blockchain.Binance } } + peer.name.contains("BSC") -> { + Blockchain.BSC + } + peer.url.contains("honeyswap.1hive.eth.limo") -> { + // Check if something's changed after this bug report: + // https://github.com/1Hive/honeyswap-interface/issues/83 + Blockchain.Gnosis + } + else -> { + Blockchain.Ethereum + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt index c796b81261..12df7aeb8e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt @@ -16,8 +16,7 @@ import timber.log.Timber import java.nio.charset.Charset class WalletConnectRepository(val context: Application) { - private val moshi = MoshiConverter.defaultMoshi() - private val walletConnectAdapter: JsonAdapter> = moshi.adapter( + private val walletConnectAdapter: JsonAdapter> = MoshiConverter.sdkMoshi.adapter( Types.newParameterizedType(List::class.java, SessionDao::class.java), ) 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 2ad236bd6c..316d44de9c 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 @@ -22,9 +22,9 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toDecompressedPublicKey import com.tangem.common.extensions.toHexString +import com.tangem.core.analytics.Analytics import com.tangem.crypto.CryptoUtils import com.tangem.operations.sign.SignHashCommand -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.toFormattedString @@ -48,6 +48,7 @@ import java.math.BigDecimal class WalletConnectSdkHelper { + @Suppress("MagicNumber") suspend fun prepareTransactionData( transaction: WCEthereumTransaction, session: WalletConnectSession, @@ -62,7 +63,7 @@ class WalletConnectSdkHelper { val gas = transaction.gas?.hexToBigDecimal() ?: transaction.gasLimit?.hexToBigDecimal() - ?: BigDecimal(300000) //Set high gasLimit if not provided + ?: BigDecimal(300000) // Set high gasLimit if not provided val decimals = wallet.blockchain.decimals() @@ -70,8 +71,7 @@ class WalletConnectSdkHelper { ?.movePointLeft(decimals) ?: return null val gasPrice = transaction.gasPrice?.hexToBigDecimal() - ?: when (val result = - (walletManager as? EthereumGasLoader)?.getGasPrice()) { + ?: when (val result = (walletManager as? EthereumGasLoader)?.getGasPrice()) { is Result.Success -> result.data.toBigDecimal() is Result.Failure -> { (result.error as? Throwable)?.let { Timber.e(it) } @@ -101,7 +101,7 @@ class WalletConnectSdkHelper { gasAmount = fee.toFormattedString(decimals), totalAmount = total.toFormattedString(decimals), balance = balance.toFormattedString(decimals), - isEnoughFundsToSend = (balance - total) >= BigDecimal.ZERO, + isEnoughFundsToSend = balance - total >= BigDecimal.ZERO, session = session.session, id = id, type = type, @@ -270,7 +270,9 @@ class WalletConnectSdkHelper { is CompletionResult.Success -> { val hash = result.data.signature return EthereumUtils.prepareSignedMessageData( - hash, hashToSign, CryptoUtils.decompressPublicKey(key!!), + signedHash = hash, + hashToSign = hashToSign, + publicKey = CryptoUtils.decompressPublicKey(key!!), ) } is CompletionResult.Failure -> { diff --git a/app/src/main/java/com/tangem/tap/features/BaseFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseFragment.kt index f0e6016021..fe5c9a3926 100644 --- a/app/src/main/java/com/tangem/tap/features/BaseFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/BaseFragment.kt @@ -54,7 +54,7 @@ abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackP } } - protected open fun loadToolbarMenu():MenuProvider? = null + protected open fun loadToolbarMenu(): MenuProvider? = null fun showRetrySnackbar(message: String, action: VoidCallback) { val snackbar = Snackbar.make(mainView, message, Snackbar.LENGTH_INDEFINITE) diff --git a/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt index b4f486a1a2..e295c9f100 100644 --- a/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt @@ -8,9 +8,10 @@ import org.rekotlin.StoreSubscriber */ abstract class BaseStoreFragment(layoutId: Int) : BaseFragment(layoutId) { - abstract fun subscribeToStore() protected val storeSubscribersList = mutableListOf>() + abstract fun subscribeToStore() + override fun onStart() { super.onStart() subscribeToStore() diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index b0d473cde0..d763e26339 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.demo -import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.TransactionData @@ -83,6 +82,7 @@ object DemoHelper { } } +@Suppress("LargeClass") class DemoConfig { val demoBlockchains = listOf( @@ -115,6 +115,7 @@ class DemoConfig { return (releaseDemoCardIds + testDemoCardIds).distinct() } + @Suppress("ClassOrdering") private val releaseDemoCardIds = mutableListOf( // === Not from the Google Sheet table === "FB10000000000196", // Note BTC @@ -477,38 +478,36 @@ class DemoConfig { "AB02000000058187", ) + @Suppress("ClassOrdering") private val testDemoCardIds = listOf( "FB20000000000186", // Note ETH "FB10000000000196", // Note BTC "FB30000000000176", // Wallet ) - private val debugTestDemoCardIds = listOf( - ) + @Suppress("ClassOrdering") + private val debugTestDemoCardIds = listOf() } -class DemoTransactionSender( - private val walletManager: WalletManager, - private val sender: TransactionSender = walletManager as TransactionSender -) : TransactionSender { +class DemoTransactionSender(private val walletManager: WalletManager) : TransactionSender { override suspend fun getFee(amount: Amount, destination: String): Result> { val blockchain = walletManager.wallet.blockchain - return when (walletManager) { - is BitcoinWalletManager -> Result.Success(listOf( + return Result.Success( + listOf( Amount(0.0001.toBigDecimal(), blockchain), + Amount(0.0002.toBigDecimal(), blockchain), Amount(0.0003.toBigDecimal(), blockchain), - Amount(0.00055.toBigDecimal(), blockchain), - )) - else -> sender.getFee(amount, destination) - } + ), + ) } + @Suppress("MagicNumber") override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult { val dataToSign = randomString(32).toByteArray() val signerResponse = signer.sign( hash = dataToSign, - publicKey = walletManager.wallet.publicKey + publicKey = walletManager.wallet.publicKey, ) return when (signerResponse) { is CompletionResult.Success -> SimpleResult.Failure(Exception(ID).toBlockchainSdkError()) @@ -529,5 +528,4 @@ class DemoTransactionSender( companion object { val ID = DemoTransactionSender::class.java.simpleName } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt rename to app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt index 87af003060..9b391b22df 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt @@ -39,7 +39,7 @@ internal class DemoOnboardingNoteMiddleware : DemoMiddleware { currency = Currency.Blockchain(walletManager.wallet.blockchain, null), state = ProgressState.Done, error = null, - criticalError = null + criticalError = null, ) walletManager.wallet.setAmount(balanceAmount) 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 5447faf5b0..6e64e29b98 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 @@ -2,6 +2,7 @@ package com.tangem.tap.features.details.redux import com.tangem.blockchain.common.Wallet import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.entities.FiatCurrency import org.rekotlin.Action @@ -27,7 +28,7 @@ sealed class DetailsAction : Action { object ScanCard : DetailsAction() - data class PrepareCardSettingsData(val card: CardDTO) : DetailsAction() + data class PrepareCardSettingsData(val card: CardDTO, val cardTypesResolver: CardTypesResolver) : DetailsAction() object ResetCardSettingsData : DetailsAction() sealed class ManageSecurity : DetailsAction() { @@ -44,11 +45,13 @@ sealed class DetailsAction : Action { sealed class AppSettings : DetailsAction() { data class SwitchPrivacySetting( val enable: Boolean, - val setting: PrivacySetting, + val setting: AppSetting, ) : AppSettings() { - data class Success( - val enable: Boolean, - val setting: PrivacySetting, + object Success : AppSettings() + + data class Failure( + val prevState: Boolean, + val setting: AppSetting, ) : AppSettings() } 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 8e9b0ccbb7..203af20870 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 @@ -5,8 +5,9 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap -import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow @@ -82,7 +83,12 @@ class DetailsMiddleware { val isSameWallet = currentUserWalletId == scannedUserWalletId if (isSameWallet) { - store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(scanResponse.card)) + store.dispatchOnMain( + DetailsAction.PrepareCardSettingsData( + scanResponse.card, + scanResponse.cardTypesResolver, + ), + ) } else { store.dispatchDialogShow( AppDialog.SimpleOkDialogRes( @@ -146,6 +152,7 @@ class DetailsMiddleware { } class ManageSecurityMiddleware { + @Suppress("ComplexMethod") fun handle(action: DetailsAction.ManageSecurity) { when (action) { is DetailsAction.ManageSecurity.OpenSecurity -> { @@ -202,8 +209,8 @@ class DetailsMiddleware { when (action) { is DetailsAction.AppSettings.SwitchPrivacySetting -> { when (action.setting) { - PrivacySetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable) - PrivacySetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable) + AppSetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable) + AppSetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable) } } is DetailsAction.AppSettings.CheckBiometricsStatus -> { @@ -213,6 +220,7 @@ class DetailsMiddleware { enrollBiometrics() } is DetailsAction.AppSettings.SwitchPrivacySetting.Success, + is DetailsAction.AppSettings.SwitchPrivacySetting.Failure, is DetailsAction.AppSettings.BiometricsStatusChanged, -> Unit } @@ -225,7 +233,7 @@ class DetailsMiddleware { private fun checkBiometricsStatus(awaitStatusChange: Boolean, state: DetailsState) { scope.launch { if (awaitStatusChange) { - while (state.needEnrollBiometrics == tangemSdkManager.needEnrollBiometrics) { + while (state.appSettingsState.needEnrollBiometrics == tangemSdkManager.needEnrollBiometrics) { delay(timeMillis = 100) } } @@ -243,47 +251,97 @@ class DetailsMiddleware { } private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch { - if (state.saveWallets == enable) return@launch - if (enable) { - saveCurrentWallet(state) - } else { - deleteSavedWallets() - if (state.saveAccessCodes) { - deleteSavedAccessCodes() + // Nothing to change + if (preferencesStorage.shouldSaveUserWallets == enable) { + store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + return@launch + } + + toggleSaveWallets(state.scanResponse, enable) + .doOnFailure { + store.dispatchOnMain( + DetailsAction.AppSettings.SwitchPrivacySetting.Failure( + prevState = !enable, + setting = AppSetting.SaveWallets, + ), + ) } + .doOnSuccess { + store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + } + } + + private suspend fun toggleSaveWallets( + scanResponse: ScanResponse?, + enable: Boolean, + ): CompletionResult { + return if (enable) { + saveCurrentWallet(scanResponse, enableAccessCodesSaving = false) + } else { + deleteSavedWalletsAndAccessCodes() } } private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch { - if (state.saveAccessCodes == enable) return@launch - if (enable) { - saveAccessCodes(state) - if (!state.saveWallets) { - saveCurrentWallet(state) + // Nothing to change + if (preferencesStorage.shouldSaveAccessCodes == enable) { + store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + return@launch + } + + toggleSaveAccessCodes(state.scanResponse, state.appSettingsState.saveWallets, enable) + .doOnFailure { + store.dispatchOnMain( + DetailsAction.AppSettings.SwitchPrivacySetting.Failure( + prevState = !enable, + setting = AppSetting.SaveAccessCode, + ), + ) + } + .doOnSuccess { + store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + } + } + + private suspend fun toggleSaveAccessCodes( + scanResponse: ScanResponse?, + isWalletsSavingEnabled: Boolean, + enable: Boolean, + ): CompletionResult { + return if (enable) { + if (!isWalletsSavingEnabled) { + saveCurrentWallet(scanResponse, enableAccessCodesSaving = true) + } else { + saveAccessCodes(scanResponse) } } else { deleteSavedAccessCodes() } } - private suspend fun saveCurrentWallet(state: DetailsState) { - val scanResponse = state.scanResponse ?: return - val userWallet = UserWalletBuilder(scanResponse).build() ?: return + private suspend fun saveCurrentWallet( + scanResponse: ScanResponse?, + enableAccessCodesSaving: Boolean, + ): CompletionResult { + val userWallet = scanResponse?.let { UserWalletBuilder(it).build() } + ?: return CompletionResult.Failure( + TangemSdkError.ExceptionError(IllegalStateException("scanResponse is null")), + ) - userWalletsListManager.save(userWallet) + return userWalletsListManager.save(userWallet) + .flatMap { + if (enableAccessCodesSaving) { + saveAccessCodes(scanResponse) + } else { + CompletionResult.Success(Unit) + } + } .doOnSuccess { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On)) preferencesStorage.shouldShowSaveUserWalletScreen = false preferencesStorage.shouldSaveUserWallets = true - store.dispatchOnMain( - DetailsAction.AppSettings.SwitchPrivacySetting.Success( - setting = PrivacySetting.SaveWallets, - enable = true, - ), - ) - store.onUserWalletSelected(userWallet) } .doOnFailure { error -> @@ -291,21 +349,14 @@ class DetailsMiddleware { } } - private suspend fun deleteSavedWallets() { - userWalletsListManager.clear() + private suspend fun deleteSavedWalletsAndAccessCodes(): CompletionResult { + return userWalletsListManager.clear() .flatMap { walletStoresManager.clear() } .doOnSuccess { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off)) - + deleteSavedAccessCodes() preferencesStorage.shouldSaveUserWallets = false - store.dispatchOnMain( - DetailsAction.AppSettings.SwitchPrivacySetting.Success( - setting = PrivacySetting.SaveWallets, - enable = false, - ), - ) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } .doOnFailure { error -> @@ -313,24 +364,19 @@ class DetailsMiddleware { } } - private fun saveAccessCodes(state: DetailsState) { + private fun saveAccessCodes(scanResponse: ScanResponse?): CompletionResult { Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On)) preferencesStorage.shouldSaveAccessCodes = true tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = state.scanResponse?.card?.isAccessCodeSet == true, + useBiometricsForAccessCode = scanResponse?.card?.isAccessCodeSet == true, ) - store.dispatchOnMain( - DetailsAction.AppSettings.SwitchPrivacySetting.Success( - setting = PrivacySetting.SaveAccessCode, - enable = true, - ), - ) + return CompletionResult.Success(Unit) } - private suspend fun deleteSavedAccessCodes() { - tangemSdkManager.clearSavedUserCodes() + private suspend fun deleteSavedAccessCodes(): CompletionResult { + return tangemSdkManager.clearSavedUserCodes() .doOnSuccess { Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off)) @@ -338,16 +384,6 @@ class DetailsMiddleware { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = false, ) - - tangemSdkManager.clearSavedUserCodes() - .doOnSuccess { - store.dispatchOnMain( - DetailsAction.AppSettings.SwitchPrivacySetting.Success( - setting = PrivacySetting.SaveAccessCode, - enable = false, - ), - ) - } } .doOnFailure { error -> Timber.e(error, "Unable to delete saved access codes") 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 142d59fbf3..cd51367536 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,10 +1,8 @@ package com.tangem.tap.features.details.redux import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.TapWorkarounds.isSaltPay -import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.domain.common.TapWorkarounds.isTangemNote -import com.tangem.domain.common.isTangemTwin import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.extensions.isWalletDataSupported import com.tangem.tap.domain.extensions.signedHashesCount @@ -14,10 +12,8 @@ import com.tangem.tap.tangemSdkManager import org.rekotlin.Action import java.util.* -class DetailsReducer { - companion object { - fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state) - } +object DetailsReducer { + fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): DetailsState { @@ -28,7 +24,11 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { handlePrepareScreen(action) } is DetailsAction.PrepareCardSettingsData -> { - handlePrepareCardSettingsScreen(action.card, detailsState) + handlePrepareCardSettingsScreen( + card = action.card, + cardTypesResolver = action.cardTypesResolver, + state = detailsState, + ) } is DetailsAction.ResetCardSettingsData -> detailsState.copy(cardSettingsState = null) is DetailsAction.ResetToFactory -> { @@ -54,23 +54,30 @@ private fun handlePrepareScreen( wallets = action.wallets, createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appCurrency = store.state.globalState.appCurrency, - isBiometricsAvailable = tangemSdkManager.canUseBiometry, - saveWallets = preferencesStorage.shouldSaveUserWallets, - saveAccessCodes = preferencesStorage.shouldSaveAccessCodes, + appSettingsState = AppSettingsState( + isBiometricsAvailable = tangemSdkManager.canUseBiometry, + saveWallets = preferencesStorage.shouldSaveUserWallets, + saveAccessCodes = preferencesStorage.shouldSaveAccessCodes, + ), ) } -private fun handlePrepareCardSettingsScreen(card: CardDTO, state: DetailsState): DetailsState { +private fun handlePrepareCardSettingsScreen( + card: CardDTO, + cardTypesResolver: CardTypesResolver, + state: DetailsState, +): + DetailsState { val cardSettingsState = CardSettingsState( - cardInfo = card.toCardInfo(), - manageSecurityState = prepareSecurityOptions(card), + cardInfo = card.toCardInfo(cardTypesResolver), + manageSecurityState = prepareSecurityOptions(card, cardTypesResolver), card = card, - resetCardAllowed = isResetToFactoryAllowedByCard(card), + resetCardAllowed = isResetToFactoryAllowedByCard(card, cardTypesResolver), ) return state.copy(cardSettingsState = cardSettingsState) } -private fun prepareSecurityOptions(card: CardDTO): ManageSecurityState { +private fun prepareSecurityOptions(card: CardDTO, cardTypesResolver: CardTypesResolver): ManageSecurityState { val securityOption = when { card.isAccessCodeSet -> { SecurityOption.AccessCode @@ -85,13 +92,14 @@ private fun prepareSecurityOptions(card: CardDTO): ManageSecurityState { } } val allowedSecurityOptions = when { - card.isStart2Coin || card.isTangemNote || card.isSaltPay -> { + cardTypesResolver.isStart2Coin() || cardTypesResolver.isTangemNote() || cardTypesResolver.isSaltPay() -> { EnumSet.of(SecurityOption.LongTap) } card.settings.isBackupAllowed -> { EnumSet.of(securityOption) } - else -> prepareAllowedSecurityOptions(card, securityOption) + else -> + prepareAllowedSecurityOptions(cardTypesResolver = cardTypesResolver, currentSecurityOption = securityOption) } return ManageSecurityState( currentOption = securityOption, @@ -100,10 +108,10 @@ private fun prepareSecurityOptions(card: CardDTO): ManageSecurityState { ) } -private fun isResetToFactoryAllowedByCard(card: CardDTO): Boolean { +private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean { val notAllowedByAnyWallet = card.wallets.any { it.settings.isPermanent } val notAllowedByCard = notAllowedByAnyWallet || - (card.isWalletDataSupported && (!card.isTangemNote && !card.settings.isBackupAllowed)) || + card.isWalletDataSupported && !cardTypesResolver.isTangemNote() && !card.settings.isBackupAllowed || card.isSaltPay return !notAllowedByCard } @@ -134,9 +142,10 @@ private fun handleSecurityAction( // Setting options to show only LongTap from now on for non-twins val manageSecurityState = state.cardSettingsState?.manageSecurityState?.copy( currentOption = state.cardSettingsState.manageSecurityState.selectedOption, - allowedOptions = state.scanResponse?.card?.let { + allowedOptions = state.scanResponse?.cardTypesResolver?.let { prepareAllowedSecurityOptions( - it, state.cardSettingsState.manageSecurityState.selectedOption, + cardTypesResolver = it, + currentSecurityOption = state.cardSettingsState.manageSecurityState.selectedOption, ) } ?: EnumSet.of(SecurityOption.LongTap), ) @@ -155,14 +164,41 @@ private fun handlePrivacyAction( state: DetailsState, ): DetailsState { return when (action) { - is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> when (action.setting) { - PrivacySetting.SaveWallets -> state.copy(saveWallets = action.enable) - PrivacySetting.SaveAccessCode -> state.copy(saveAccessCodes = action.enable) - } - is DetailsAction.AppSettings.BiometricsStatusChanged -> state.copy( - needEnrollBiometrics = action.needEnrollBiometrics, + is DetailsAction.AppSettings.SwitchPrivacySetting -> state.copy( + appSettingsState = when (action.setting) { + AppSetting.SaveWallets -> state.appSettingsState.copy( + isInProgress = true, + saveWallets = action.enable, + ) + AppSetting.SaveAccessCode -> state.appSettingsState.copy( + isInProgress = true, + saveWallets = true, // User can't enable access codes saving without wallets saving + saveAccessCodes = action.enable, + ) + }, + ) + is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> state.copy( + appSettingsState = state.appSettingsState.copy( + isInProgress = false, + ), + ) + is DetailsAction.AppSettings.SwitchPrivacySetting.Failure -> state.copy( + appSettingsState = when (action.setting) { + AppSetting.SaveWallets -> state.appSettingsState.copy( + isInProgress = false, + saveWallets = action.prevState, + ) + AppSetting.SaveAccessCode -> state.appSettingsState.copy( + isInProgress = false, + saveAccessCodes = action.prevState, + ) + }, + ) + is DetailsAction.AppSettings.BiometricsStatusChanged -> state.copy( + appSettingsState = state.appSettingsState.copy( + needEnrollBiometrics = action.needEnrollBiometrics, + ), ) - is DetailsAction.AppSettings.SwitchPrivacySetting, is DetailsAction.AppSettings.EnrollBiometrics, is DetailsAction.AppSettings.CheckBiometricsStatus, -> state @@ -170,12 +206,12 @@ private fun handlePrivacyAction( } private fun prepareAllowedSecurityOptions( - card: CardDTO?, + cardTypesResolver: CardTypesResolver, currentSecurityOption: SecurityOption?, ): EnumSet { val allowedSecurityOptions = EnumSet.of(SecurityOption.LongTap) - if (card?.isTangemTwin() == true) { + if (cardTypesResolver.isTangemTwins()) { allowedSecurityOptions.add(SecurityOption.PassCode) } if (currentSecurityOption == SecurityOption.AccessCode) { @@ -187,9 +223,17 @@ private fun prepareAllowedSecurityOptions( return allowedSecurityOptions } -private fun CardDTO.toCardInfo(): CardInfo { +@Suppress("MagicNumber") +private fun CardDTO.toCardInfo(cardTypesResolver: CardTypesResolver): CardInfo { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.issuer.name val signedHashes = this.signedHashesCount() - return CardInfo(cardId, issuer, signedHashes) + + return CardInfo( + cardId = cardId, + issuer = issuer, + signedHashes = signedHashes, + isTwin = cardTypesResolver.isTangemTwins(), + hasBackup = backupStatus?.isActive == true, + ) } \ No newline at end of file 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 a22b810f7c..dd560af176 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 @@ -18,10 +18,7 @@ data class DetailsState( val privacyPolicyUrl: String? = null, val createBackupAllowed: Boolean = false, val appCurrency: FiatCurrency = FiatCurrency.Default, - val saveWallets: Boolean = false, - val saveAccessCodes: Boolean = false, - val isBiometricsAvailable: Boolean = false, - val needEnrollBiometrics: Boolean = false, + val appSettingsState: AppSettingsState = AppSettingsState(), ) : StateType { // if you do not delegate - the application crashes on startup, @@ -29,15 +26,14 @@ data class DetailsState( val twinCardsState: TwinCardsState by ReadOnlyProperty { _, _ -> store.state.twinCardsState } - - val isTangemTwins: Boolean - get() = store.state.globalState.scanResponse?.isTangemTwins() == true } data class CardInfo( val cardId: String, val issuer: String, val signedHashes: Int, + val isTwin: Boolean, + val hasBackup: Boolean, ) data class CardSettingsState( @@ -55,8 +51,16 @@ data class ManageSecurityState( val buttonProceed: Button = Button(true), ) +data class AppSettingsState( + val saveWallets: Boolean = false, + val saveAccessCodes: Boolean = false, + val isBiometricsAvailable: Boolean = false, + val needEnrollBiometrics: Boolean = false, + val isInProgress: Boolean = false, +) + enum class SecurityOption { LongTap, PassCode, AccessCode } -enum class PrivacySetting { +enum class AppSetting { SaveWallets, SaveAccessCode } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 67b215dd51..a383235538 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -33,7 +33,8 @@ sealed class WalletConnectAction : Action { object RefuseOpeningSession : WalletConnectAction() data class OpeningSessionTimeout(val session: WCSession) : WalletConnectAction() data class ScanCard( - val session: WalletConnectSession, val chainId: Int?, + val session: WalletConnectSession, + val chainId: Int?, ) : WalletConnectAction() data class ApproveSession( @@ -93,17 +94,21 @@ sealed class WalletConnectAction : Action { object BinanceTransaction : WalletConnectAction() { data class Trade( - val id: Long, val order: WCBinanceTradeOrder, + val id: Long, + val order: WCBinanceTradeOrder, val sessionData: WalletConnectSession, ) : WalletConnectAction() data class Transfer( - val id: Long, val order: WCBinanceTransferOrder, + val id: Long, + val order: WCBinanceTransferOrder, val sessionData: WalletConnectSession, ) : WalletConnectAction() data class Sign( - val id: Long, val data: ByteArray, val sessionData: WCSession, + val id: Long, + val data: ByteArray, + val sessionData: WCSession, ) : WalletConnectAction() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 7818f367b3..0957c8f2dc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -14,7 +14,6 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.walletconnect.BnbHelper import com.tangem.tap.domain.walletconnect.WalletConnectManager @@ -40,6 +39,7 @@ class WalletConnectMiddleware { } } + @Suppress("ComplexMethod", "LongMethod") private fun handle(state: () -> AppState?, action: Action) { if (DemoHelper.tryHandle(state, action)) return @@ -49,10 +49,8 @@ class WalletConnectMiddleware { walletConnectManager.restoreSessions(action.scanResponse) } is WalletConnectAction.HandleDeepLink -> { - if (!action.wcUri.isNullOrBlank()) { - if (WalletConnectManager.isCorrectWcUri(action.wcUri)) { - store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri)) - } + if (!action.wcUri.isNullOrBlank() && WalletConnectManager.isCorrectWcUri(action.wcUri)) { + store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri)) } } is WalletConnectAction.StartWalletConnect -> { @@ -189,7 +187,9 @@ class WalletConnectMiddleware { } is WalletConnectAction.BinanceTransaction.Sign -> { walletConnectManager.signBnb( - action.id, action.data, action.sessionData, + id = action.id, + data = action.data, + sessionData = action.sessionData, ) } is WalletConnectAction.SwitchBlockchain -> { @@ -291,7 +291,7 @@ class WalletConnectMiddleware { blockchain: Blockchain, ) { val card = scanResponse.card - if (!card.isMultiwalletAllowed) { + if (!scanResponse.cardTypesResolver.isMultiwalletAllowed()) { store.dispatchOnMain(WalletConnectAction.UnsupportedCard) return } @@ -311,7 +311,9 @@ class WalletConnectMiddleware { } private fun getWalletManager( - wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState, + wallet: WalletForSession, + blockchain: Blockchain, + walletState: WalletState, ): WalletManager? { val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) { Blockchain.EthereumTestnet diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt index e4cc76254c..a4990f5d8b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt @@ -2,47 +2,44 @@ package com.tangem.tap.features.details.redux.walletconnect import org.rekotlin.Action -class WalletConnectReducer { - companion object { - fun reduce( - action: Action, state: WalletConnectState, - ): WalletConnectState { - if (action !is WalletConnectAction) return state +object WalletConnectReducer { + fun reduce( + action: Action, + state: WalletConnectState, + ): WalletConnectState { + if (action !is WalletConnectAction) return state - return when (action) { - is WalletConnectAction.ResetState -> return WalletConnectState() - is WalletConnectAction.ApproveSession.Success -> { - state.copy( - loading = false, - sessions = state.sessions + action.session, - ) - } - is WalletConnectAction.OpenSession -> { - state.copy(loading = true) - } - is WalletConnectAction.SetNewSessionData -> { - state.copy(newSessionData = action.newSession) - } - is WalletConnectAction.SetSessionsRestored -> - WalletConnectState(sessions = action.sessions) - is WalletConnectAction.RemoveSession -> { - val sessions = - state.sessions.filterNot { it.session.toUri() == action.session.toUri() } - state.copy(sessions = sessions) - } - is WalletConnectAction.UnsupportedCard -> state.copy(loading = false) - is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false) - is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false) - is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false) - is WalletConnectAction.UpdateBlockchain -> state.copy( - sessions = state.sessions - .filterNot { it.peerId == action.updatedSession.peerId } - + action.updatedSession + return when (action) { + is WalletConnectAction.ResetState -> return WalletConnectState() + is WalletConnectAction.ApproveSession.Success -> { + state.copy( + loading = false, + sessions = state.sessions + action.session, ) - - else -> state } - } + is WalletConnectAction.OpenSession -> { + state.copy(loading = true) + } + is WalletConnectAction.SetNewSessionData -> { + state.copy(newSessionData = action.newSession) + } + is WalletConnectAction.SetSessionsRestored -> + WalletConnectState(sessions = action.sessions) + is WalletConnectAction.RemoveSession -> { + val sessions = + state.sessions.filterNot { it.session.toUri() == action.session.toUri() } + state.copy(sessions = sessions) + } + is WalletConnectAction.UnsupportedCard -> state.copy(loading = false) + is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false) + is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false) + is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false) + is WalletConnectAction.UpdateBlockchain -> state.copy( + sessions = state.sessions + .filterNot { it.peerId == action.updatedSession.peerId } + action.updatedSession, + ) + else -> state + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 97afb372a2..70e41f4f0c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -74,7 +74,7 @@ data class WalletForSession( } override fun hashCode(): Int { - var result = (walletPublicKey?.contentHashCode() ?: 0) + var result = walletPublicKey?.contentHashCode() ?: 0 result = 31 * result + (derivedPublicKey?.contentHashCode() ?: 0) result = 31 * result + (derivationPath?.hashCode() ?: 0) result = 31 * result + isTestNet.hashCode() @@ -83,7 +83,6 @@ data class WalletForSession( } } - sealed class WalletConnectDialog : StateDialog { data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog() object UnsupportedCard : WalletConnectDialog() @@ -133,7 +132,7 @@ data class WcPersonalSignData( val id: Long, val dialogData: PersonalSignDialogData, - ) +) sealed class BinanceMessageData( val address: String, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt index 25a6cd0902..3302facf0b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt @@ -41,7 +41,7 @@ class AppSettingsFragment : Fragment(), StoreSubscriber { TangemTheme { AppSettingsScreen( state = screenState.value, - onBackPressed = { + onBackClick = { store.dispatch(DetailsAction.ResetCardSettingsData) store.dispatch(NavigationAction.PopBackTo()) }, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index b072be7b74..84ff3c3dba 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -23,7 +23,7 @@ import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.SpacerH4 import com.tangem.core.ui.components.SpacerW32 import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.features.details.redux.PrivacySetting +import com.tangem.tap.features.details.redux.AppSetting import com.tangem.tap.features.details.ui.appsettings.components.EnrollBiometricsCard import com.tangem.tap.features.details.ui.appsettings.components.SettingsAlertDialog import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -31,78 +31,61 @@ import com.tangem.tap.features.details.ui.common.TangemSwitch import com.tangem.wallet.R @Composable -fun AppSettingsScreen( - state: AppSettingsScreenState, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { +fun AppSettingsScreen(state: AppSettingsScreenState, onBackClick: () -> Unit) { SettingsScreensScaffold( - content = { - AppSettings(state = state, modifier = modifier) - }, + content = { AppSettings(state = state) }, titleRes = R.string.app_settings_title, - onBackClick = onBackPressed, + onBackClick = onBackClick, ) } @Composable -private fun AppSettings( - state: AppSettingsScreenState, - modifier: Modifier = Modifier, -) { - var dialogType by remember { mutableStateOf(null) } - val onDialogStateChange: (PrivacySetting?) -> Unit = { dialogType = it } +private fun AppSettings(state: AppSettingsScreenState) { + var dialogType by remember { mutableStateOf(null) } + val onDialogStateChange: (AppSetting?) -> Unit = { dialogType = it } dialogType?.let { SettingsAlertDialog( element = it, onDialogStateChange = onDialogStateChange, - onSettingToggled = state.onSettingToggled, + onSettingToggle = { state.onSettingToggled(it, false) }, ) } - Column( - modifier = modifier - .fillMaxSize(), - ) { + Column(modifier = Modifier.fillMaxSize()) { if (state.showEnrollBiometricsCard) { - EnrollBiometricsCard( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing8) - .fillMaxWidth(), - onClick = state.onEnrollBiometrics, - ) + EnrollBiometricsCard(onClick = state.onEnrollBiometrics) SpacerH24() } AppSettingsElement( state = state, - setting = PrivacySetting.SaveWallets, + setting = AppSetting.SaveWallets, onDialogStateChange = onDialogStateChange, ) SpacerH32() AppSettingsElement( state = state, - setting = PrivacySetting.SaveAccessCode, + setting = AppSetting.SaveAccessCode, onDialogStateChange = onDialogStateChange, ) } } +@Suppress("LongMethod") @Composable private fun AppSettingsElement( - modifier: Modifier = Modifier, state: AppSettingsScreenState, - setting: PrivacySetting, - onDialogStateChange: (PrivacySetting?) -> Unit, + setting: AppSetting, + onDialogStateChange: (AppSetting?) -> Unit, ) { val titleRes = when (setting) { - PrivacySetting.SaveWallets -> R.string.app_settings_saved_wallet - PrivacySetting.SaveAccessCode -> R.string.app_settings_saved_access_codes + AppSetting.SaveWallets -> R.string.app_settings_saved_wallet + AppSetting.SaveAccessCode -> R.string.app_settings_saved_access_codes } val subtitleRes = when (setting) { - PrivacySetting.SaveWallets -> R.string.app_settings_saved_wallet_footer - PrivacySetting.SaveAccessCode -> R.string.app_settings_saved_access_codes_footer + AppSetting.SaveWallets -> R.string.app_settings_saved_wallet_footer + AppSetting.SaveAccessCode -> R.string.app_settings_saved_access_codes_footer } val checked = state.settings[setting] ?: false @@ -122,7 +105,7 @@ private fun AppSettingsElement( ) Row( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(horizontal = TangemTheme.dimens.spacing20), verticalAlignment = Alignment.CenterVertically, @@ -161,12 +144,12 @@ private fun AppSettingsElement( } private fun onCheckedChange( - element: PrivacySetting, + element: AppSetting, enabled: Boolean, - onSettingToggled: (PrivacySetting, Boolean) -> Unit, - onDialogStateChange: (PrivacySetting?) -> Unit, + onSettingToggled: (AppSetting, Boolean) -> Unit, + onDialogStateChange: (AppSetting?) -> Unit, ) { - //Show warning if user wants to disable the switch + // Show warning if user wants to disable the switch if (!enabled) { onDialogStateChange(element) } else { @@ -186,15 +169,15 @@ private fun AppSettingsScreenSample( AppSettingsScreen( state = AppSettingsScreenState( settings = mapOf( - PrivacySetting.SaveWallets to true, - PrivacySetting.SaveAccessCode to false, + AppSetting.SaveWallets to true, + AppSetting.SaveAccessCode to false, ), showEnrollBiometricsCard = false, isTogglesEnabled = true, onSettingToggled = { _, _ -> }, onEnrollBiometrics = {}, ), - onBackPressed = { }, + onBackClick = { }, ) } } @@ -219,22 +202,19 @@ private fun AppSettingsScreenPreview_Dark() { private fun AppSettingsScreen_EnrollBiometrics_Sample( modifier: Modifier = Modifier, ) { - Column( - modifier = modifier - .background(TangemTheme.colors.background.primary), - ) { + Column(modifier = modifier.background(TangemTheme.colors.background.primary)) { AppSettingsScreen( state = AppSettingsScreenState( settings = mapOf( - PrivacySetting.SaveWallets to true, - PrivacySetting.SaveAccessCode to false, + AppSetting.SaveWallets to true, + AppSetting.SaveAccessCode to false, ), showEnrollBiometricsCard = true, isTogglesEnabled = false, onSettingToggled = { _, _ -> }, onEnrollBiometrics = {}, ), - onBackPressed = { }, + onBackClick = { }, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt index 615a0a167d..fbc4640e8f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt @@ -1,11 +1,11 @@ package com.tangem.tap.features.details.ui.appsettings -import com.tangem.tap.features.details.redux.PrivacySetting +import com.tangem.tap.features.details.redux.AppSetting data class AppSettingsScreenState( - val settings: Map = emptyMap(), + val settings: Map = emptyMap(), val showEnrollBiometricsCard: Boolean = false, - val isTogglesEnabled: Boolean = true, - val onSettingToggled: (PrivacySetting, Boolean) -> Unit = { _, _ -> /* no-op */ }, + val isTogglesEnabled: Boolean = false, + val onSettingToggled: (AppSetting, Boolean) -> Unit = { _, _ -> /* no-op */ }, val onEnrollBiometrics: () -> Unit = { /* no-op */ }, ) \ No newline at end of file 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 802435f578..38323dcc3e 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 @@ -2,35 +2,33 @@ package com.tangem.tap.features.details.ui.appsettings import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.details.redux.AppSetting import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState -import com.tangem.tap.features.details.redux.PrivacySetting -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import org.rekotlin.Store class AppSettingsViewModel(private val store: Store) { - private val _uiState = MutableStateFlow(updateState(store.state.detailsState)) - val uiState: StateFlow = _uiState fun updateState(state: DetailsState): AppSettingsScreenState { - return AppSettingsScreenState( - settings = mapOf( - PrivacySetting.SaveWallets to state.saveWallets, - PrivacySetting.SaveAccessCode to state.saveAccessCodes, - ), - showEnrollBiometricsCard = state.needEnrollBiometrics, - isTogglesEnabled = !state.needEnrollBiometrics, - onSettingToggled = { privacySetting, enabled -> - onSettingsToggled(privacySetting, enabled) - }, - onEnrollBiometrics = { - store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics) - }, - ) + return with(state.appSettingsState) { + AppSettingsScreenState( + settings = mapOf( + AppSetting.SaveWallets to saveWallets, + AppSetting.SaveAccessCode to saveAccessCodes, + ), + showEnrollBiometricsCard = needEnrollBiometrics, + isTogglesEnabled = !needEnrollBiometrics && !isInProgress, + onSettingToggled = { privacySetting, enabled -> + onSettingsToggled(privacySetting, enabled) + }, + onEnrollBiometrics = { + store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics) + }, + ) + } } - private fun onSettingsToggled(setting: PrivacySetting, enable: Boolean) { + private fun onSettingsToggled(setting: AppSetting, enable: Boolean) { store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting)) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/EnrollBiometricsCard.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/EnrollBiometricsCard.kt index fddc2b0e7b..b541b88894 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/EnrollBiometricsCard.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/EnrollBiometricsCard.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon @@ -23,12 +24,11 @@ import com.tangem.wallet.R @OptIn(ExperimentalMaterialApi::class) @Composable -internal fun EnrollBiometricsCard( - modifier: Modifier = Modifier, - onClick: () -> Unit, -) { +internal fun EnrollBiometricsCard(onClick: () -> Unit) { Surface( - modifier = modifier, + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing8) + .fillMaxWidth(), color = TangemTheme.colors.background.primary, shape = TangemTheme.shapes.roundedCornersLarge, onClick = onClick, @@ -67,8 +67,7 @@ private fun EnrollBiometricsCardSample( modifier: Modifier = Modifier, ) { Column( - modifier = modifier - .background(TangemTheme.colors.background.secondary), + modifier = modifier.background(TangemTheme.colors.background.secondary), ) { EnrollBiometricsCard(onClick = {}) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt index 65514a2f86..0ed7409abd 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt @@ -12,19 +12,18 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.TextButton import com.tangem.core.ui.components.WarningTextButton import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.features.details.redux.PrivacySetting +import com.tangem.tap.features.details.redux.AppSetting import com.tangem.wallet.R @Composable internal fun SettingsAlertDialog( - element: PrivacySetting, - onDialogStateChange: (PrivacySetting?) -> Unit, - onSettingToggled: (PrivacySetting, Boolean) -> Unit, - modifier: Modifier = Modifier, + element: AppSetting, + onDialogStateChange: (AppSetting?) -> Unit, + onSettingToggle: () -> Unit, ) { val text = when (element) { - PrivacySetting.SaveWallets -> R.string.app_settings_off_saved_wallet_alert_message - PrivacySetting.SaveAccessCode -> R.string.app_settings_off_saved_access_code_alert_message + AppSetting.SaveWallets -> R.string.app_settings_off_saved_wallet_alert_message + AppSetting.SaveAccessCode -> R.string.app_settings_off_saved_access_code_alert_message } AlertDialog( @@ -43,7 +42,7 @@ internal fun SettingsAlertDialog( text = stringResource(id = R.string.common_delete), onClick = { onDialogStateChange(null) - onSettingToggled(element, false) + onSettingToggle() }, ) }, @@ -62,7 +61,6 @@ internal fun SettingsAlertDialog( ) }, shape = TangemTheme.shapes.roundedCornersLarge, - modifier = modifier, ) } @@ -76,9 +74,9 @@ private fun SettingsAlertDialogSample( .background(TangemTheme.colors.background.primary), ) { SettingsAlertDialog( - element = PrivacySetting.SaveAccessCode, + element = AppSetting.SaveAccessCode, onDialogStateChange = {}, - onSettingToggled = { _, _ -> }, + onSettingToggle = { }, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt index a42f4a1d38..15a943d590 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt @@ -42,7 +42,7 @@ class CardSettingsFragment : Fragment(), StoreSubscriber { TangemTheme { CardSettingsScreen( state = screenState.value, - onBackPressed = { + onBackClick = { store.dispatch(DetailsAction.ResetCardSettingsData) store.dispatch(NavigationAction.PopBackTo()) }, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index 5a4d1bdc53..be54f4a52f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -30,43 +30,36 @@ import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R @Composable -fun CardSettingsScreen( - state: CardSettingsScreenState, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { +fun CardSettingsScreen(state: CardSettingsScreenState, onBackClick: () -> Unit) { val needReadCard = state.cardDetails == null SettingsScreensScaffold( content = { if (needReadCard) { - CardSettingsReadCard(state.onScanCardClick, modifier = modifier) + CardSettingsReadCard(state.onScanCardClick) } else { - CardSettings(state = state, modifier = modifier) + CardSettings(state = state) } }, titleRes = R.string.card_settings_title, backgroundColor = TangemTheme.colors.background.secondary, - onBackClick = onBackPressed, + onBackClick = onBackClick, ) } +@Suppress("MagicNumber") @Composable -fun CardSettingsReadCard( - onScanCardClick: () -> Unit, - modifier: Modifier = Modifier, -) { +fun CardSettingsReadCard(onScanCardClick: () -> Unit) { Column( - modifier = modifier - .fillMaxSize(), + modifier = Modifier.fillMaxSize(), ) { Box( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(bottom = 40.dp), ) { Image( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(start = 80.dp, end = 80.dp, top = 70.dp) .rotate(-15f), @@ -75,7 +68,7 @@ fun CardSettingsReadCard( contentScale = ContentScale.FillWidth, ) Image( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(start = 60.dp, end = 60.dp) .rotate(-1f), @@ -86,7 +79,7 @@ fun CardSettingsReadCard( } Spacer(modifier = Modifier.weight(1f)) Column( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(start = 16.dp, end = 16.dp, bottom = 32.dp), ) { @@ -95,35 +88,31 @@ fun CardSettingsReadCard( color = colorResource(id = R.color.text_primary_1), style = TangemTypography.headline3, ) - Spacer(modifier = modifier.size(20.dp)) + Spacer(modifier = Modifier.size(20.dp)) Text( text = stringResource(id = R.string.scan_card_settings_message), color = colorResource(id = R.color.text_secondary), style = TangemTypography.body1, - modifier = modifier + modifier = Modifier .verticalScroll(rememberScrollState()) .weight(weight = 1f, fill = false), ) - Spacer(modifier = modifier.size(29.dp)) + Spacer(modifier = Modifier.size(29.dp)) DetailsMainButton( title = stringResource(id = R.string.scan_card_settings_button), onClick = onScanCardClick, - modifier = modifier, ) } } } +@Suppress("ComplexMethod") @Composable -fun CardSettings( - state: CardSettingsScreenState, - modifier: Modifier = Modifier, -) { +fun CardSettings(state: CardSettingsScreenState) { if (state.cardDetails == null) return LazyColumn( - modifier = modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), ) { items(state.cardDetails) { val paddingBottom = when (it) { @@ -142,7 +131,7 @@ fun CardSettings( is CardInfo.ResetToFactorySettings -> 16.dp } Column( - modifier = modifier + modifier = Modifier .fillMaxWidth() .clickable( enabled = it.clickable, @@ -150,18 +139,26 @@ fun CardSettings( ) .padding(start = 20.dp, end = 20.dp, bottom = paddingBottom, top = paddingTop), ) { - val titleColor = if (it.clickable) R.color.text_primary_1 else R.color.text_tertiary - val subtitleColor = if (it.clickable) R.color.text_secondary else R.color.text_tertiary + val titleColor = if (it.clickable) { + TangemTheme.colors.text.primary1 + } else { + TangemTheme.colors.text.tertiary + } + val subtitleColor = if (it.clickable) { + TangemTheme.colors.text.secondary + } else { + TangemTheme.colors.text.tertiary + } Text( text = it.titleRes.resolveReference(), - color = colorResource(id = titleColor), - style = TangemTypography.subtitle1, + color = titleColor, + style = TangemTheme.typography.subtitle1, ) - Spacer(modifier = modifier.size(4.dp)) + Spacer(modifier = Modifier.size(4.dp)) Text( text = it.subtitle.resolveReference(), - color = colorResource(id = subtitleColor), - style = TangemTypography.body2, + color = subtitleColor, + style = TangemTheme.typography.body2, ) } } @@ -170,6 +167,6 @@ fun CardSettings( @Composable @Preview -fun CardSettingsPreview() { +private fun CardSettingsPreview() { CardSettingsScreen(state = CardSettingsScreenState(onScanCardClick = {}) {}, {}) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt index 927780750e..5ae42a55a9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -6,7 +6,9 @@ import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.res.stringResource import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.details.ui.securitymode.toTitleRes +import com.tangem.tap.features.details.ui.utils.toResetCardDescriptionText import com.tangem.wallet.R +import com.tangem.tap.features.details.redux.CardInfo as ReduxCardInfo data class CardSettingsScreenState( val cardDetails: List? = null, @@ -46,9 +48,9 @@ sealed class CardInfo( clickable = true, ) - class ResetToFactorySettings(subtitle: TextReference.Res) : CardInfo( + class ResetToFactorySettings(cardInfo: ReduxCardInfo) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory), - subtitle = subtitle, + subtitle = cardInfo.toResetCardDescriptionText(), clickable = true, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index 6920e51129..16b9836764 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -1,20 +1,18 @@ package com.tangem.tap.features.details.ui.cardsettings +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.getTwinCardIdForUser -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.wallet.R import org.rekotlin.Store class CardSettingsViewModel(private val store: Store) { fun updateState(state: CardSettingsState?): CardSettingsScreenState { - return if (state?.manageSecurityState == null) { CardSettingsScreenState( cardDetails = null, @@ -47,17 +45,7 @@ class CardSettingsViewModel(private val store: Store) { cardDetails.add(CardInfo.ChangeAccessCode) } if (state.resetCardAllowed) { - cardDetails.add( - CardInfo.ResetToFactorySettings( - subtitle = TextReference.Res( - if (state.card.backupStatus?.isActive == true) { - R.string.reset_card_with_backup_to_factory_message - } else { - R.string.reset_card_without_backup_to_factory_message - }, - ), - ), - ) + cardDetails.add(CardInfo.ResetToFactorySettings(state.cardInfo)) } CardSettingsScreenState( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index dd6f290ebe..34ed64cceb 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBarsPadding @@ -26,7 +26,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.common.compose.TangemTypography import com.tangem.wallet.R @Composable @@ -84,25 +83,23 @@ fun ScreenTitle( Text( text = stringResource(id = titleRes), modifier = modifier.padding(start = 20.dp, end = 20.dp), - style = TangemTypography.headline1, - color = colorResource(id = R.color.text_primary_1), + style = TangemTheme.typography.h1, + color = TangemTheme.colors.text.primary1, ) } @Composable fun EmptyTopBarWithNavigation( - modifier: Modifier = Modifier, onBackClick: () -> Unit, backgroundColor: Color = TangemTheme.colors.background.primary, ) { TopAppBar( - modifier = modifier, title = { }, navigationIcon = { IconButton(onClick = onBackClick) { Icon( - painter = painterResource(id = R.drawable.ic_back), + painter = painterResource(id = R.drawable.ic_back_24), contentDescription = null, tint = TangemTheme.colors.icon.primary1, ) @@ -115,16 +112,16 @@ fun EmptyTopBarWithNavigation( @Composable fun DetailsMainButton( - modifier: Modifier = Modifier, title: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, enabled: Boolean = true, - onClick: (() -> Unit), ) { Button( onClick = onClick, modifier = modifier .fillMaxWidth() - .height(48.dp), + .heightIn(48.dp), shape = RoundedCornerShape(12.dp), enabled = enabled, colors = ButtonDefaults.buttonColors( @@ -135,7 +132,11 @@ fun DetailsMainButton( ), ) { Text(text = title) - Spacer(modifier = modifier.size(8.dp)) - Icon(painter = painterResource(id = R.drawable.ic_tangem), contentDescription = "") + Spacer( + modifier = Modifier + .padding(start = 20.dp, end = 20.dp) + .size(8.dp), + ) + Icon(painter = painterResource(id = R.drawable.ic_tangem_24), contentDescription = "") } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt index d511348af9..c6c8e82cf4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt @@ -30,15 +30,15 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.wallet.R +@Suppress("MagicNumber") @Composable fun TangemSwitch( - modifier: Modifier = Modifier, + onCheckedChange: (Boolean) -> Unit, checkedColor: Color = colorResource(id = R.color.control_checked), uncheckedColor: Color = colorResource(id = R.color.icon_informative), size: Dp = 48.dp, checked: Boolean = false, enabled: Boolean = true, - onCheckedChange: (Boolean) -> Unit, ) { val transition = updateTransition(checked, label = "SwitchState") val color by transition.animateColor( @@ -47,12 +47,13 @@ fun TangemSwitch( }, label = "", ) { isChecked -> - if (isChecked) checkedColor else uncheckedColor + (if (isChecked) checkedColor else uncheckedColor) + .copy(alpha = if (enabled) 1f else .4f) } val interactionSource = remember { MutableInteractionSource() } Box( - modifier = modifier + modifier = Modifier .clickable( interactionSource = interactionSource, indication = null, @@ -69,7 +70,7 @@ fun TangemSwitch( ), ) { BoxWithConstraints( - modifier = modifier + modifier = Modifier .width(size) .height(size / 2) .indication(interactionSource, null) 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 index 56023ab1f8..c89c19485a 100644 --- 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 @@ -9,8 +9,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.tangem.core.ui.res.TangemTheme import com.tangem.core.analytics.Analytics +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsState @@ -43,7 +43,7 @@ class DetailsFragment : Fragment(), StoreSubscriber { TangemTheme { DetailsScreen( state = detailsScreenState.value, - onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) }, + onBackClick = { store.dispatch(NavigationAction.PopBackTo()) }, ) } } 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 index 1346fa4f24..c80a04b785 100644 --- 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 @@ -33,66 +33,52 @@ import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R @Composable -fun DetailsScreen( - state: DetailsScreenState, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { +fun DetailsScreen(state: DetailsScreenState, onBackClick: () -> Unit) { SystemBarsEffect { setSystemBarsColor(color = TangemColorPalette.Light1) } SettingsScreensScaffold( - content = { Content(state = state, modifier = modifier) }, - onBackClick = onBackPressed, + content = { Content(state = state) }, + onBackClick = onBackClick, ) } @Composable -fun Content( - state: DetailsScreenState, - modifier: Modifier = Modifier, -) { +fun Content(state: DetailsScreenState) { Column( - modifier = modifier + modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()), ) { - ScreenTitle(titleRes = R.string.details_title, modifier.padding(bottom = 52.dp)) - state.elements.map { - if (it == SettingsElement.WalletConnect) { - WalletConnectDetailsItem( - onItemsClick = state.onItemsClick, - modifier = modifier, - ) + ScreenTitle(titleRes = R.string.details_title, Modifier.padding(bottom = 52.dp)) + state.elements.map { element -> + if (element == SettingsElement.WalletConnect) { + WalletConnectDetailsItem(onItemsClick = state.onItemsClick) } else { DetailsItem( - item = it, + item = element, appCurrency = state.appCurrency, - onItemsClick = state.onItemsClick, - modifier = modifier, + onItemsClick = { state.onItemsClick(element) }, ) } } - Spacer(modifier = modifier.weight(1f)) + Spacer(modifier = Modifier.weight(1f)) TangemSocialAccounts(state.tangemLinks, state.onSocialNetworkClick) Spacer(modifier = Modifier.size(12.dp)) Text( text = "${stringResource(id = state.appNameRes)} ${state.tangemVersion}", style = TangemTypography.caption, color = colorResource(id = R.color.text_tertiary), - modifier = modifier.padding(start = 16.dp, end = 16.dp, bottom = 40.dp), + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 40.dp), ) } } @Composable -fun WalletConnectDetailsItem( - onItemsClick: (SettingsElement) -> Unit, - modifier: Modifier = Modifier, -) { +fun WalletConnectDetailsItem(onItemsClick: (SettingsElement) -> Unit) { Row( - modifier = modifier + modifier = Modifier .defaultMinSize(minHeight = 84.dp) .fillMaxWidth() .clickable { onItemsClick(SettingsElement.WalletConnect) }, @@ -102,23 +88,23 @@ fun WalletConnectDetailsItem( Icon( painter = painterResource(id = R.drawable.ic_walletconnect), contentDescription = stringResource(id = R.string.wallet_connect_title), - modifier = modifier.padding(start = 20.dp, end = 20.dp), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), tint = colorResource(id = R.color.all_colors_azure), ) Column( - modifier = modifier.defaultMinSize(minHeight = 56.dp), + modifier = Modifier.defaultMinSize(minHeight = 56.dp), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Center, ) { Text( text = stringResource(id = R.string.wallet_connect_title), - modifier = modifier.padding(end = 20.dp, bottom = 4.dp), + modifier = Modifier.padding(end = 20.dp, bottom = 4.dp), style = TangemTypography.headline3, color = colorResource(id = R.color.text_primary_1), ) Text( text = stringResource(id = R.string.wallet_connect_subtitle), - modifier = modifier.padding(end = 20.dp, bottom = 4.dp), + modifier = Modifier.padding(end = 20.dp, bottom = 4.dp), style = TangemTypography.body1, color = colorResource(id = R.color.text_secondary), ) @@ -127,37 +113,31 @@ fun WalletConnectDetailsItem( } @Composable -fun DetailsItem( - item: SettingsElement, - appCurrency: String, - onItemsClick: (SettingsElement) -> Unit, - modifier: Modifier = Modifier, -) { +fun DetailsItem(item: SettingsElement, appCurrency: String, onItemsClick: () -> Unit) { Row( - modifier = modifier + modifier = Modifier .height(56.dp) .fillMaxWidth() - .clickable { onItemsClick(item) }, + .clickable(onClick = onItemsClick), horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically, ) { Icon( painter = painterResource(id = item.iconRes), contentDescription = stringResource(id = item.titleRes), - modifier = modifier.padding(start = 20.dp, end = 20.dp), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), tint = colorResource(id = R.color.icon_secondary), ) - Column(modifier = modifier.padding(end = 20.dp)) { + Column(modifier = Modifier.padding(end = 20.dp)) { Text( text = stringResource(id = item.titleRes), - modifier = modifier, + modifier = Modifier, style = TangemTypography.subtitle1, color = colorResource(id = R.color.text_primary_1), ) if (item == SettingsElement.AppCurrency) { Text( text = appCurrency, - modifier = modifier, style = TangemTypography.body2, color = colorResource(id = R.color.text_secondary), ) @@ -170,16 +150,13 @@ fun DetailsItem( fun TangemSocialAccounts( links: List, onSocialNetworkClick: (SocialNetworkLink) -> Unit, - modifier: Modifier = Modifier, ) { - LazyRow( - modifier = modifier.padding(start = 8.dp, end = 8.dp), - ) { + LazyRow(modifier = Modifier.padding(start = 8.dp, end = 8.dp)) { items(links) { Icon( painter = painterResource(id = it.network.iconRes), contentDescription = "", - modifier = modifier + modifier = Modifier .padding(8.dp) .clickable { onSocialNetworkClick(it) }, tint = colorResource(id = R.color.icon_informative), @@ -190,15 +167,16 @@ fun TangemSocialAccounts( @Composable @Preview -fun Preview() { +private fun Preview() { DetailsScreen( state = DetailsScreenState( elements = SettingsElement.values().toList(), tangemLinks = TangemSocialAccounts.accountsEn, tangemVersion = "Tangem 2.14.12 (343)", appCurrency = "Dollar", - onItemsClick = {}, onSocialNetworkClick = {}, + onItemsClick = {}, + onSocialNetworkClick = {}, ), - onBackPressed = {}, + onBackClick = {}, ) } \ 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 index fcdc6689cf..88d83b897c 100644 --- 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 @@ -23,7 +23,7 @@ enum class SettingsElement( WalletConnect(R.drawable.ic_walletconnect, R.string.wallet_connect_title), Chat(R.drawable.ic_chat, R.string.details_chat), SendFeedback(R.drawable.ic_comment, R.string.details_row_title_send_feedback), - // ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), + ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), CardSettings(R.drawable.ic_card_settings, R.string.card_settings_title), AppCurrency(R.drawable.ic_currency, R.string.details_row_title_currency), AppSettings(R.drawable.ic_settings, R.string.app_settings_title), 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 index 4c75feefe3..2a3c6aea7d 100644 --- 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 @@ -1,7 +1,5 @@ package com.tangem.tap.features.details.ui.details -import com.tangem.domain.common.TapWorkarounds.isSaltPay -import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.feedback.FeedbackEmail @@ -10,7 +8,6 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.disclaimer.redux.DisclaimerAction @@ -21,24 +18,28 @@ import com.tangem.wallet.BuildConfig import org.rekotlin.Store class DetailsViewModel(private val store: Store) { + + @Suppress("ComplexMethod") fun updateState(state: DetailsState): DetailsScreenState { + val cardTypesResolver = state.scanResponse?.cardTypesResolver val settings = SettingsElement.values().mapNotNull { when (it) { SettingsElement.WalletConnect -> { - if (state.scanResponse?.card?.isMultiwalletAllowed == true) it else null + if (cardTypesResolver?.isMultiwalletAllowed() == true) it else null } - SettingsElement.SendFeedback -> if (state.scanResponse?.card?.isSaltPay != true) it else null + SettingsElement.SendFeedback -> + if (cardTypesResolver?.isSaltPay() != true) it else null SettingsElement.LinkMoreCards -> { // if (state.createBackupAllowed) it else null // TODO: SaltPay: temporary excluding backup process for Visa cards - if (state.createBackupAllowed && state.scanResponse?.card?.isSaltPay != true) it else null + if (state.createBackupAllowed && cardTypesResolver?.isSaltPay() != true) it else null } SettingsElement.PrivacyPolicy -> { if (state.privacyPolicyUrl != null) it else null } - SettingsElement.AppSettings -> if (state.isBiometricsAvailable) it else null - SettingsElement.AppCurrency -> if (state.scanResponse?.card?.isMultiwalletAllowed != true) it else null - // SettingsElement.ReferralProgram -> if (state.scanResponse?.card?.isTangemWallet == true) it else null + SettingsElement.AppSettings -> if (state.appSettingsState.isBiometricsAvailable) it else null + SettingsElement.AppCurrency -> if (cardTypesResolver?.isMultiwalletAllowed() != true) it else null + SettingsElement.ReferralProgram -> if (cardTypesResolver?.isTangemWallet() == true) it else null else -> it } } @@ -93,9 +94,9 @@ class DetailsViewModel(private val store: Store) { SettingsElement.PrivacyPolicy -> { // TODO: To be available later } - // SettingsElement.ReferralProgram -> { - // store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) - // } + SettingsElement.ReferralProgram -> { + store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt index 10270c8ecb..8ea18c0604 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt @@ -41,7 +41,7 @@ class ResetCardFragment : Fragment(), StoreSubscriber { TangemTheme { ResetCardScreen( state = screenState.value, - onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) }, + onBackClick = { store.dispatch(NavigationAction.PopBackTo()) }, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 944e4c715f..c73f6bf836 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.details.ui.resetcard import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -20,26 +21,28 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.tap.common.compose.TangemTypography +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.details.ui.common.DetailsMainButton import com.tangem.tap.features.details.ui.common.ScreenTitle import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R @Composable -fun ResetCardScreen(state: ResetCardScreenState, onBackPressed: () -> Unit) { +fun ResetCardScreen(state: ResetCardScreenState, onBackClick: () -> Unit) { SettingsScreensScaffold( content = { ResetCardView(state = state) }, - onBackClick = onBackPressed, + onBackClick = onBackClick, backgroundColor = Color.Transparent, ) } +@Suppress("LongMethod", "MagicNumber") @Composable fun ResetCardView(state: ResetCardScreenState) { Column( @@ -50,7 +53,7 @@ fun ResetCardView(state: ResetCardScreenState) { ) { Box { Image( - painter = painterResource(id = R.drawable.ic_reset_background), + painter = painterResource(id = R.drawable.ill_reset_background), contentDescription = null, modifier = Modifier.offset(y = (-82).dp), ) @@ -64,17 +67,17 @@ fun ResetCardView(state: ResetCardScreenState) { Text( text = stringResource(id = R.string.common_attention), modifier = Modifier.padding(start = 20.dp, end = 20.dp), - style = TangemTypography.headline3, - color = colorResource(id = R.color.text_primary_1), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, ) Spacer(modifier = Modifier.size(24.dp)) Text( - text = stringResource(id = state.descriptionResId), + text = state.descriptionText.resolveReference(), modifier = Modifier.padding(start = 20.dp, end = 20.dp), - style = TangemTypography.body1, - color = colorResource(id = R.color.text_secondary), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, ) Spacer(modifier = Modifier.size(28.dp)) @@ -101,16 +104,16 @@ fun ResetCardView(state: ResetCardScreenState) { ), contentDescription = null, tint = if (state.accepted) { - colorResource(id = R.color.icon_accent) + TangemTheme.colors.icon.accent } else { - colorResource(id = R.color.icon_secondary) + TangemTheme.colors.icon.secondary }, ) } Text( text = stringResource(id = R.string.reset_card_to_factory_warning_message), - style = TangemTypography.body2, - color = colorResource(id = R.color.text_secondary), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, modifier = Modifier.padding(end = 20.dp), ) } @@ -129,16 +132,40 @@ fun ResetCardView(state: ResetCardScreenState) { } } +// region Preview @Composable -@Preview -fun ResetCardScreenPreview() { - ResetCardScreen( - state = ResetCardScreenState( - descriptionResId = R.string.reset_card_without_backup_to_factory_message, - accepted = false, - onAcceptWarningToggleClick = {}, - onResetButtonClick = {}, - ), - onBackPressed = {}, - ) -} \ No newline at end of file +private fun ResetCardScreenSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.secondary), + ) { + ResetCardScreen( + state = ResetCardScreenState( + accepted = true, + descriptionText = TextReference.Res(R.string.reset_card_with_backup_to_factory_message), + onAcceptWarningToggleClick = {}, + onResetButtonClick = {}, + ), + onBackClick = {}, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun ResetCardScreenPreview_Light() { + TangemTheme { + ResetCardScreenSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun ResetCardScreenPreview_Dark() { + TangemTheme(isDark = true) { + ResetCardScreenSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt index 54693e7557..fee3e41a5b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -1,10 +1,10 @@ package com.tangem.tap.features.details.ui.resetcard -import androidx.annotation.StringRes +import com.tangem.tap.features.details.ui.cardsettings.TextReference data class ResetCardScreenState( - @StringRes val descriptionResId: Int, val accepted: Boolean = false, + val descriptionText: TextReference, val onAcceptWarningToggleClick: (Boolean) -> Unit, val onResetButtonClick: () -> Unit, ) { 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 1606631d68..9ba899bc7d 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 @@ -3,19 +3,20 @@ package com.tangem.tap.features.details.ui.resetcard import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.wallet.R +import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.tap.features.details.ui.utils.toResetCardDescriptionText import org.rekotlin.Store class ResetCardViewModel(private val store: Store) { fun updateState(state: CardSettingsState?): ResetCardScreenState { + val descriptionText = state?.cardInfo + ?.toResetCardDescriptionText() + ?: TextReference.Str(value = "") + return ResetCardScreenState( - descriptionResId = if (state?.card?.backupStatus?.isActive == true) { - R.string.reset_card_with_backup_to_factory_message - } else { - R.string.reset_card_without_backup_to_factory_message - }, accepted = state?.resetConfirmed ?: false, + descriptionText = descriptionText, onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) }, onResetButtonClick = { store.dispatch(DetailsAction.ResetToFactory.Proceed) }, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt index 567c74c007..dcddb62a2f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt @@ -41,7 +41,7 @@ class SecurityModeFragment : Fragment(), StoreSubscriber { TangemTheme { SecurityModeScreen( state = screenState.value, - onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) }, + onBackClick = { store.dispatch(NavigationAction.PopBackTo()) }, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt index 66f6adcab7..383fa7b498 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt @@ -28,36 +28,27 @@ import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R @Composable -fun SecurityModeScreen( - state: SecurityModeScreenState, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { +fun SecurityModeScreen(state: SecurityModeScreenState, onBackClick: () -> Unit) { SettingsScreensScaffold( - content = { SecurityModeOptions(state = state, modifier = modifier) }, + content = { SecurityModeOptions(state = state) }, // titleRes = R.string.card_settings_security_mode, - onBackClick = onBackPressed, + onBackClick = onBackClick, ) } @Composable -fun SecurityModeOptions( - state: SecurityModeScreenState, - modifier: Modifier = Modifier, -) { - +fun SecurityModeOptions(state: SecurityModeScreenState) { Column( - modifier = modifier + modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) .padding(bottom = 28.dp), verticalArrangement = Arrangement.SpaceBetween, ) { - ScreenTitle(titleRes = R.string.card_settings_security_mode, modifier.padding(bottom = 36.dp)) - + ScreenTitle(titleRes = R.string.card_settings_security_mode, Modifier.padding(bottom = 36.dp)) state.availableOptions.map { - SecurityOption(option = it, state = state, modifier = modifier) + SecurityOption(option = it, state = state) } Spacer(modifier = Modifier.weight(1f)) @@ -66,17 +57,13 @@ fun SecurityModeOptions( title = stringResource(id = R.string.common_save_changes), enabled = state.isSaveChangesEnabled, onClick = state.onSaveChangesClicked, - modifier = modifier - .padding(start = 20.dp, end = 20.dp), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), ) } } @Composable -fun SecurityOption( - option: SecurityOption, state: SecurityModeScreenState, - modifier: Modifier, -) { +fun SecurityOption(option: SecurityOption, state: SecurityModeScreenState) { val selected = option == state.selectedSecurityMode val title = option.toTitleRes() @@ -88,17 +75,18 @@ fun SecurityOption( } Row( - modifier = modifier + modifier = Modifier .fillMaxWidth() .selectable( - selected = selected, onClick = { state.onNewModeSelected(option) }, + selected = selected, + onClick = { state.onNewModeSelected(option) }, ) .padding(start = 20.dp, end = 20.dp, top = 16.dp, bottom = 16.dp), ) { - RadioButton( - selected = selected, onClick = null, - modifier = modifier.padding(end = 20.dp), + selected = selected, + onClick = null, + modifier = Modifier.padding(end = 20.dp), colors = RadioButtonDefaults.colors( unselectedColor = colorResource(id = R.color.icon_secondary), selectedColor = colorResource(id = R.color.icon_accent), @@ -111,7 +99,7 @@ fun SecurityOption( style = TangemTypography.subtitle1, color = colorResource(id = R.color.text_primary_1), ) - Spacer(modifier = modifier.size(4.dp)) + Spacer(modifier = Modifier.size(4.dp)) Text( text = stringResource(id = subtitle), style = TangemTypography.body2, @@ -123,7 +111,7 @@ fun SecurityOption( @Preview @Composable -fun SecurityModeScreenPreview() { +private fun SecurityModeScreenPreview() { SecurityModeScreen( state = SecurityModeScreenState( availableOptions = SecurityOption.values().toList(), @@ -132,6 +120,6 @@ fun SecurityModeScreenPreview() { onNewModeSelected = {}, onSaveChangesClicked = {}, ), - onBackPressed = {}, + onBackClick = {}, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt index b25277864d..a0666ff19d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt @@ -9,13 +9,15 @@ import org.rekotlin.Store class SecurityModeViewModel(val store: Store) { fun updateState(state: ManageSecurityState?): SecurityModeScreenState { - if (state == null) return SecurityModeScreenState( - availableOptions = emptyList(), - selectedSecurityMode = SecurityOption.LongTap, - isSaveChangesEnabled = false, - onNewModeSelected = {}, - onSaveChangesClicked = {}, - ) + if (state == null) { + return SecurityModeScreenState( + availableOptions = emptyList(), + selectedSecurityMode = SecurityOption.LongTap, + isSaveChangesEnabled = false, + onNewModeSelected = {}, + onSaveChangesClicked = {}, + ) + } return SecurityModeScreenState( availableOptions = state.allowedOptions.toList(), selectedSecurityMode = state.selectedOption, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt b/app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt new file mode 100644 index 0000000000..459e5ba49a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt @@ -0,0 +1,13 @@ +package com.tangem.tap.features.details.ui.utils + +import com.tangem.tap.features.details.redux.CardInfo +import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.wallet.R + +internal fun CardInfo.toResetCardDescriptionText(): TextReference { + return if (!this.hasBackup || this.isTwin) { + TextReference.Res(R.string.reset_card_without_backup_to_factory_message) + } else { + TextReference.Res(R.string.reset_card_with_backup_to_factory_message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt index 0ee7263879..927adfe845 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt @@ -88,5 +88,4 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler { private fun requestPermission() { requestPermissions(arrayOf(Manifest.permission.CAMERA), CameraView.PERMISSION_REQUEST_CODE) } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt index 8b29784828..3ed76fdee2 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt @@ -42,7 +42,7 @@ class WalletConnectFragment : Fragment(), StoreSubscriber { TangemTheme { WalletConnectScreen( state = screenState.value, - onBackPressed = { + onBackClick = { if (screenState.value.isLoading) { store.dispatch( WalletConnectAction.FailureEstablishingSession( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt index a08153c345..caefb4546a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -38,19 +38,15 @@ import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R @Composable -fun WalletConnectScreen( - state: WalletConnectScreenState, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { +fun WalletConnectScreen(state: WalletConnectScreenState, onBackClick: () -> Unit) { val context = LocalContext.current SettingsScreensScaffold( content = { if (state.sessions.isEmpty()) { - EmptyScreen(state, modifier) + EmptyScreen(state) } else { - WalletConnectSessions(state, modifier) + WalletConnectSessions(state) } }, fab = { @@ -64,7 +60,7 @@ fun WalletConnectScreen( } }, titleRes = R.string.wallet_connect_title, - onBackClick = onBackPressed, + onBackClick = onBackClick, ) } @@ -81,22 +77,22 @@ private fun AddSessionFab( modifier = modifier, ) { Icon( - painter = painterResource(id = R.drawable.ic_plus), + painter = painterResource(id = R.drawable.ic_plus_24), contentDescription = "", ) } } @Composable -private fun EmptyScreen(state: WalletConnectScreenState, modifier: Modifier = Modifier) { +private fun EmptyScreen(state: WalletConnectScreenState) { if (state.isLoading) { LinearProgressIndicator( - modifier = modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), color = colorResource(id = R.color.icon_accent), ) } Column( - modifier = modifier + modifier = Modifier .fillMaxSize() .padding(bottom = 64.dp), verticalArrangement = Arrangement.Center, @@ -107,10 +103,9 @@ private fun EmptyScreen(state: WalletConnectScreenState, modifier: Modifier = Mo contentDescription = "", colorFilter = ColorFilter.tint(colorResource(id = R.color.icon_inactive)), contentScale = ContentScale.FillWidth, - modifier = modifier - .width(width = 100.dp), + modifier = Modifier.width(width = 100.dp), ) - Spacer(modifier = modifier.size(24.dp)) + Spacer(modifier = Modifier.size(24.dp)) Text( text = stringResource(id = R.string.wallet_connect_subtitle), style = TangemTypography.body2, @@ -120,28 +115,25 @@ private fun EmptyScreen(state: WalletConnectScreenState, modifier: Modifier = Mo } @Composable -private fun WalletConnectSessions( - state: WalletConnectScreenState, - modifier: Modifier = Modifier, -) { +private fun WalletConnectSessions(state: WalletConnectScreenState) { if (state.isLoading) { LinearProgressIndicator( - modifier = modifier + modifier = Modifier .fillMaxWidth() .height(2.dp), color = colorResource(id = R.color.icon_accent), ) } else { - Spacer(modifier = modifier.height(2.dp)) + Spacer(modifier = Modifier.height(2.dp)) } LazyColumn( - modifier = modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { items(state.sessions) { session -> Row( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(horizontal = 20.dp, vertical = 16.dp), horizontalArrangement = Arrangement.SpaceBetween, @@ -151,7 +143,7 @@ private fun WalletConnectSessions( text = session.description, style = TangemTypography.subtitle1, color = colorResource(id = R.color.text_primary_1), - modifier = modifier.weight(1f), + modifier = Modifier.weight(1f), ) IconButton( onClick = { @@ -172,7 +164,7 @@ private fun WalletConnectSessions( @Composable @Preview -fun WalletConnectScreenPreview() { +private fun WalletConnectScreenPreview() { WalletConnectScreen( state = WalletConnectScreenState( sessions = listOf( @@ -185,4 +177,4 @@ fun WalletConnectScreenPreview() { ), {}, ) -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt index e1d915dc5b..e61b4c22e6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt @@ -21,5 +21,4 @@ data class WcSessionForScreen( ) } } -} - +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt index da8e03c3b8..aba68641df 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt @@ -9,38 +9,36 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession import com.tangem.tap.store import com.tangem.wallet.R -class ApproveWcSessionDialog { - companion object { - fun create(session: WalletConnectSession, networks: List, context: Context): AlertDialog { - val sessionBlockchain = requireNotNull(session.wallet.blockchain) { "session network is null" } - val message = context.getString( - R.string.wallet_connect_request_session_start, - session.peerMeta.name, - sessionBlockchain.fullName, - session.peerMeta.url, - ) - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(message) - setPositiveButton(context.getText(R.string.common_start)) { _, _ -> +object ApproveWcSessionDialog { + fun create(session: WalletConnectSession, networks: List, context: Context): AlertDialog { + val sessionBlockchain = requireNotNull(session.wallet.blockchain) { "session network is null" } + val message = context.getString( + R.string.wallet_connect_request_session_start, + session.peerMeta.name, + sessionBlockchain.fullName, + session.peerMeta.url, + ) + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(message) + setPositiveButton(context.getText(R.string.common_start)) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(WalletConnectAction.ChooseNetwork(sessionBlockchain)) + } + if (networks.size > 1) { + setNeutralButton(context.getText(R.string.wallet_connect_select_network)) { _, _ -> store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.ChooseNetwork(sessionBlockchain)) + store.dispatch(WalletConnectAction.SelectNetwork(session = session, networks = networks)) } - if (networks.size > 1) { - setNeutralButton(context.getText(R.string.wallet_connect_select_network)) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.SelectNetwork(session = session, networks = networks)) - } - } - setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) - } - setOnCancelListener { - store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) - } - }.create() - } + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) + } + setOnCancelListener { + store.dispatch(GlobalAction.HideDialog) + store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session)) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt index 8de826231f..3d8c70a937 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt @@ -9,59 +9,57 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.trustwallet.walletconnect.models.session.WCSession -class BnbTransactionDialog { - companion object { - fun create( - data: BinanceMessageData, - session: WCSession, - sessionId: Long, - dAppName: String, - context: Context, - ): AlertDialog { +object BnbTransactionDialog { + fun create( + data: BinanceMessageData, + session: WCSession, + sessionId: Long, + dAppName: String, + context: Context, + ): AlertDialog { + val message = when (data) { + is BinanceMessageData.Trade -> data.tradeData.map { + context.getString( + R.string.wallet_connect_bnb_trade_order_message, + it.symbol, + it.price, + it.quantity, + it.amount, + ) + }.joinToString(separator = "\n\n") + is BinanceMessageData.Transfer -> context.getString( + R.string.wallet_connect_bnb_transaction_message, + data.address, + data.outputAddress, + data.amount, + ) + } - val message = when (data) { - is BinanceMessageData.Trade -> data.tradeData.map { - context.getString( - R.string.wallet_connect_bnb_trade_order_message, - it.symbol, - it.price, - it.quantity, - it.amount, - ) - }.joinToString(separator = "\n\n") - is BinanceMessageData.Transfer -> context.getString( - R.string.wallet_connect_bnb_transaction_message, - data.address, - data.outputAddress, - data.amount, + val fullMessage = context.getString( + R.string.wallet_connect_bnb_sign_message, + dAppName, + message, + ) + val positiveButtonTitle = context.getText(R.string.common_sign) + + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(fullMessage) + setPositiveButton(positiveButtonTitle) { _, _ -> + store.dispatch( + WalletConnectAction.BinanceTransaction.Sign( + id = sessionId, + data = data.data, + sessionData = session, + ), ) } - - val fullMessage = context.getString( - R.string.wallet_connect_bnb_sign_message, - dAppName, message, - ) - val positiveButtonTitle = context.getText(R.string.common_sign) - - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(fullMessage) - setPositiveButton(positiveButtonTitle) { _, _ -> - store.dispatch( - WalletConnectAction.BinanceTransaction.Sign( - id = sessionId, - data = data.data, - sessionData = session, - ), - ) - } - setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(WalletConnectAction.RejectRequest(session, sessionId)) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(WalletConnectAction.RejectRequest(session, sessionId)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt index 0213971713..d1962be5e4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt @@ -9,22 +9,20 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.store import com.tangem.wallet.R -class ClipboardOrScanQrDialog { - companion object { - fun create(wcUri: String, context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(context.getText(R.string.wallet_connect_clipboard_alert)) - setPositiveButton(context.getText(R.string.wallet_connect_paste_from_clipboard)) { _, _ -> - store.dispatch(WalletConnectAction.OpenSession(wcUri)) - } - setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ -> - store.dispatch(NavigationAction.NavigateTo(AppScreen.QrScan)) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } +object ClipboardOrScanQrDialog { + fun create(wcUri: String, context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(context.getText(R.string.wallet_connect_clipboard_alert)) + setPositiveButton(context.getText(R.string.wallet_connect_paste_from_clipboard)) { _, _ -> + store.dispatch(WalletConnectAction.OpenSession(wcUri)) + } + setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ -> + store.dispatch(NavigationAction.NavigateTo(AppScreen.QrScan)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt index c78549f273..5f6bf384ed 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt @@ -8,24 +8,22 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.trustwallet.walletconnect.models.session.WCSession -class PersonalSignDialog { - companion object { - fun create(data: PersonalSignDialogData, context: Context): AlertDialog { - val message = context.getString(R.string.wallet_connect_alert_sign_message, data.message) - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(message) - setPositiveButton(context.getText(R.string.common_sign)) { _, _ -> - store.dispatch(WalletConnectAction.SignMessage(data.session)) - } - setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } +object PersonalSignDialog { + fun create(data: PersonalSignDialogData, context: Context): AlertDialog { + val message = context.getString(R.string.wallet_connect_alert_sign_message, data.message) + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(message) + setPositiveButton(context.getText(R.string.common_sign)) { _, _ -> + store.dispatch(WalletConnectAction.SignMessage(data.session)) + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt index cd45c1c9be..4d502f7275 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt @@ -9,45 +9,40 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.trustwallet.walletconnect.models.session.WCSession -class TransactionDialog { - companion object { - fun create( - data: TransactionRequestDialogData, - context: Context, - ): AlertDialog { - val message = context.getString( - R.string.wallet_connect_create_tx_message, - data.dAppName, - data.dAppUrl, - data.amount, - data.gasAmount, - data.totalAmount, - data.balance, - ) +object TransactionDialog { + fun create(data: TransactionRequestDialogData, context: Context): AlertDialog { + val message = context.getString( + R.string.wallet_connect_create_tx_message, + data.dAppName, + data.dAppUrl, + data.amount, + data.gasAmount, + data.totalAmount, + data.balance, + ) - val positiveButtonTitle = when (data.type) { - WcTransactionType.EthSignTransaction -> context.getText(R.string.common_sign) - WcTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send) - } - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect_title)) - setMessage(message) - setPositiveButton(positiveButtonTitle) { _, _ -> - if (data.isEnoughFundsToSend) { - store.dispatch(WalletConnectAction.SendTransaction(data.session)) - } else { - store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) - store.dispatch(WalletConnectAction.NotEnoughFunds) - } - } - setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() + val positiveButtonTitle = when (data.type) { + WcTransactionType.EthSignTransaction -> context.getText(R.string.common_sign) + WcTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send) } + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(message) + setPositiveButton(positiveButtonTitle) { _, _ -> + if (data.isEnoughFundsToSend) { + store.dispatch(WalletConnectAction.SendTransaction(data.session)) + } else { + store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) + store.dispatch(WalletConnectAction.NotEnoughFunds) + } + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt deleted file mode 100644 index f71c14a348..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.tap.features.details.ui.walletconnect.dialogs - -import android.content.Context -import androidx.appcompat.app.AlertDialog - -class WcDialog { - companion object { - fun create(dialogData: DialogData, context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(dialogData.title) - setMessage(dialogData.message) - setPositiveButton(dialogData.positiveButton.title) { _, _ -> - dialogData.positiveButton.action() - } - setNegativeButton(dialogData.negativeButton.title) { _, _ -> - dialogData.negativeButton.action() - } - setOnDismissListener { dialogData.onDismissAction() } - }.create() - } - } -} - -data class DialogData( - val title: String, - val message: String, - val positiveButton: ButtonData, - val negativeButton: ButtonData, - val onDismissAction: () -> Unit, -) - -data class ButtonData( - val title: String, - val action: () -> Unit, -) - -//class WcDialogMessageBuilder(val messageRes: Int, val data: DialogMessageData? = null) { -// fun build(context: Context): String { -// when (data) { -// null -> context.getString(messageRes) -// } -// } -//} - -interface DialogMessageData - -data class WcTransactionDialogMessageData( - val dAppName: String, - val dAppUrl: String, - val amount: String, - val gasAmount: String, - val totalAmount: String, - val balance: String, - val isEnoughFundsToSend: Boolean, -) \ No newline at end of file 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 1f72592557..27dd88ee13 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 @@ -51,6 +51,7 @@ class Start2CoinDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaime override fun getUri(): Uri = Uri.parse("$baseUrl/" + filename(dataProvider.getLanguage(), getRegion())) override fun getPreferenceKey(): String = "start2Coin_tos_accepted_${getRegion()}" + @Suppress("ComplexMethod") private fun filename(languageCode: String, regionCode: String?): String { return when { languageCode == "fr" && regionCode == "ch" -> "start2coin-fr-ch-tangem.html" 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 index 456818fe70..bef592e97e 100644 --- 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 @@ -3,10 +3,8 @@ package com.tangem.tap.features.disclaimer.redux import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class DisclaimerReducer { - companion object { - fun reduce(action: Action, state: AppState): DisclaimerState = internalReduce(action, state) - } +object DisclaimerReducer { + fun reduce(action: Action, state: AppState): DisclaimerState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): DisclaimerState { 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 index 99db90b3d8..b610475974 100644 --- 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 @@ -12,8 +12,7 @@ data class DisclaimerState( val showedFromScreen: AppScreen = AppScreen.Home, val callback: DisclaimerCallback? = null, val progressState: ProgressState? = null, -) : StateType { -} +) : StateType data class DisclaimerCallback( val onAccept: VoidCallback? = null, 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 index 3c177a29e7..e19be8e926 100644 --- 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 @@ -37,6 +37,7 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs enterTransition = inflater.inflateTransition(android.R.transition.fade) exitTransition = inflater.inflateTransition(android.R.transition.fade) } + else -> {} } } 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 index c1a024fc0c..4e07832feb 100644 --- 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 @@ -59,10 +59,14 @@ class DisclaimerWebViewClient : WebViewClient() { super.onReceivedHttpError(view, resourceRequest, errorResponse) ifNotNull(resourceRequest, errorResponse) { request, response -> - if (request.url?.toString() != loadingUrl || response.statusCode < 400) return + 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/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index 60dc234e4f..f01fdaeb7c 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -76,6 +76,7 @@ class HomeFragment : Fragment(), StoreSubscriber { homeState.value = state } + @Suppress("TopLevelComposableFunctions") @Composable private fun ScreenContent() { StoriesScreen( @@ -98,9 +99,9 @@ class HomeFragment : Fragment(), StoreSubscriber { } /* - * !!! Workaround !!! - * Used to roll back the color of icons in the system bars after the stories screen - * */ + * !!! Workaround !!! + * Used to roll back the color of icons in the system bars after the stories screen + */ private fun rollbackStatusBarIconsColor() { val windowInsetsController = WindowInsetsControllerCompat( activity?.window ?: return, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt index 58f9356349..76ef1a643e 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt @@ -22,6 +22,9 @@ import androidx.compose.ui.unit.dp import com.tangem.tap.common.compose.extensions.AnimatedValue import com.tangem.tap.common.compose.extensions.toAnimatable +private const val SCALE_SWITCH_BARRIER = 1.15f + +@Suppress("LongParameterList") @Composable fun HorizontalSlidingImage( painter: Painter, @@ -39,7 +42,7 @@ fun HorizontalSlidingImage( .requiredWidth(itemSize.width) .requiredHeight(itemSize.height) .graphicsLayer( - translationX = translateX.toAnimatable(isPaused = paused, duration = duration).value + translationX = translateX.toAnimatable(isPaused = paused, duration = duration).value, ), alignment = Alignment.TopStart, contentScale = ContentScale.FillBounds, @@ -54,7 +57,7 @@ fun StoriesTextAnimation( slideInDelay: Int = 200, slideDistance: Dp = 60.dp, label: String = "", - content: @Composable (Modifier) -> Unit + content: @Composable (Modifier) -> Unit, ) { val isLaunched = remember { mutableStateOf(false) } val transition = updateTransition(targetState = isLaunched.value, label = label) @@ -64,10 +67,10 @@ fun StoriesTextAnimation( tween( durationMillis = slideInDuration, delayMillis = slideInDelay, - easing = FastOutSlowInEasing + easing = FastOutSlowInEasing, ) }, - label = "Slide in" + label = "Slide in", ) { value -> if (value) 0.dp else slideDistance } val alpha = transition.animateFloat( @@ -75,16 +78,16 @@ fun StoriesTextAnimation( tween( durationMillis = slideInDuration * 2, delayMillis = slideInDelay, - easing = FastOutSlowInEasing + easing = FastOutSlowInEasing, ) }, - label = "Visibility" + label = "Visibility", ) { value -> if (value) 1f else 0f } content( Modifier .absoluteOffset(y = offsetY.value) - .alpha(alpha.value) + .alpha(alpha.value), ) LaunchedEffect(Unit) { isLaunched.value = true } @@ -95,9 +98,8 @@ fun StoriesBottomImageAnimation( initialScale: Float = 2.5f, firstStepDuration: Int, totalDuration: Int, - content: @Composable (Modifier) -> Unit + content: @Composable (Modifier) -> Unit, ) { - val scaleSwitchBarrier = 1.15f val secondStepDuration = totalDuration - firstStepDuration val isFirstStepLaunched = remember { mutableStateOf(false) } @@ -105,7 +107,7 @@ fun StoriesBottomImageAnimation( val firstTransition = updateTransition( targetState = isFirstStepLaunched.value, - label = "Image appearing" + label = "Image appearing", ) val firstScaleStep = firstTransition.animateFloat( transitionSpec = { @@ -114,12 +116,12 @@ fun StoriesBottomImageAnimation( easing = FastOutLinearInEasing, ) }, - label = "Appearing scale" - ) { value -> if (value) scaleSwitchBarrier else initialScale } + label = "Appearing scale", + ) { value -> if (value) SCALE_SWITCH_BARRIER else initialScale } val secondTransition = updateTransition( targetState = isSecondStepLaunched.value, - label = "Image slow outgoing" + label = "Image slow outgoing", ) val secondScaleStep = secondTransition.animateFloat( transitionSpec = { @@ -128,15 +130,15 @@ fun StoriesBottomImageAnimation( easing = LinearEasing, ) }, - label = "Outgoing scale" - ) { value -> if (value) 1f else scaleSwitchBarrier } + label = "Outgoing scale", + ) { value -> if (value) 1f else SCALE_SWITCH_BARRIER } val fadeIn = firstTransition.animateFloat( transitionSpec = { tween(durationMillis = 400) }, - label = "Fade in on start" + label = "Fade in on start", ) { value -> if (value) 1f else 0f } - if (firstScaleStep.value == scaleSwitchBarrier) { + if (firstScaleStep.value == SCALE_SWITCH_BARRIER) { isSecondStepLaunched.value = true } 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 a459a4d701..ef40d850f6 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 @@ -1,3 +1,5 @@ +@file:Suppress("MagicNumber") + package com.tangem.tap.features.home.compose import androidx.compose.foundation.Image @@ -12,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars @@ -52,14 +55,16 @@ import com.tangem.tap.features.home.redux.HomeState import com.tangem.wallet.R import kotlin.math.max +private const val STEPS = 6 + +@Suppress("LongMethod", "ComplexMethod") @Composable fun StoriesScreen( - homeState: MutableState = mutableStateOf(HomeState()), + homeState: MutableState, onScanButtonClick: () -> Unit, onShopButtonClick: () -> Unit, onSearchTokensClick: () -> Unit, ) { - val steps = 6 val currentStep = remember { mutableStateOf(1) } val systemUiController = rememberSystemUiController() @@ -69,7 +74,7 @@ fun StoriesScreen( currentStep.value = max(1, currentStep.value - 1) } val goToNextScreen = { - currentStep.value = if (currentStep.value < steps) currentStep.value + 1 else 1 + currentStep.value = if (currentStep.value < STEPS) currentStep.value + 1 else 1 } val isPressed = remember { mutableStateOf(false) } @@ -149,11 +154,11 @@ fun StoriesScreen( verticalArrangement = Arrangement.Center, ) { StoriesProgressBar( - steps = steps, + steps = STEPS, currentStep = currentStep.value, stepDuration = currentStep.duration(), paused = isPaused, - onStepFinished = goToNextScreen, + onStepFinish = goToNextScreen, ) Image( painter = painterResource(id = R.drawable.ic_tangem_logo), @@ -181,27 +186,29 @@ fun StoriesScreen( .align(Alignment.BottomCenter) .fillMaxWidth(), ) { - if (currentStep.value == 4) Button( - onClick = onSearchTokensClick, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - .height(48.dp), - colors = ButtonDefaults.textButtonColors( - backgroundColor = Color.White, - contentColor = Color(0xFF080C10), - ), - ) { - Image( - painter = painterResource(id = R.drawable.ic_search), - contentDescription = null, - ) - Text( - text = stringResource(id = R.string.search_tokens_title), - fontWeight = FontWeight.Medium, - fontSize = 16.sp, - textAlign = TextAlign.Center, - ) + if (currentStep.value == 4) { + Button( + onClick = onSearchTokensClick, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .heightIn(48.dp), + colors = ButtonDefaults.textButtonColors( + backgroundColor = Color.White, + contentColor = Color(0xFF080C10), + ), + ) { + Image( + painter = painterResource(id = R.drawable.ic_search), + contentDescription = null, + ) + Text( + text = stringResource(id = R.string.search_tokens_title), + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + textAlign = TextAlign.Center, + ) + } } HomeButtons( modifier = Modifier @@ -216,6 +223,7 @@ fun StoriesScreen( } } +@Suppress("MagicNumber") private fun MutableState.duration(): Int = when (this.value) { 1 -> 8000 else -> 6000 @@ -223,6 +231,11 @@ private fun MutableState.duration(): Int = when (this.value) { @Preview @Composable -fun StoriesScreenPreview() { - StoriesScreen(onScanButtonClick = {}, onShopButtonClick = {}, onSearchTokensClick = {}) +private fun StoriesScreenPreview() { + StoriesScreen( + onScanButtonClick = {}, + onShopButtonClick = {}, + onSearchTokensClick = {}, + homeState = remember { mutableStateOf(HomeState()) }, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt index e8a6b447f7..2a545e623b 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt @@ -51,7 +51,7 @@ fun StoriesRevolutionaryWallet(stepDuration: Int) { isDarkBackground = true, ) } - } + }, ) } @@ -76,7 +76,7 @@ fun StoriesUltraSecureBackup(isPaused: Boolean, stepDuration: Int) { }, bottomContent = { FloatingCardsContent(isPaused, stepDuration) - } + }, ) } @@ -92,7 +92,7 @@ fun StoriesCurrencies(isPaused: Boolean, stepDuration: Int) { }, bottomContent = { StoriesCurrenciesContent(paused = isPaused, duration = stepDuration) - } + }, ) } @@ -108,7 +108,7 @@ fun StoriesWeb3(isPaused: Boolean, stepDuration: Int) { }, bottomContent = { StoriesWeb3Content(paused = isPaused, duration = stepDuration) - } + }, ) } @@ -133,7 +133,7 @@ fun StoriesWalletForEveryone(stepDuration: Int) { isDarkBackground = true, ) } - } + }, ) } @@ -146,7 +146,7 @@ private fun SplitContent( modifier = Modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top + verticalArrangement = Arrangement.Top, ) { topContent() bottomContent() @@ -171,6 +171,7 @@ private fun TopContent( SpacerH32() } +@Suppress("MagicNumber") @Composable private fun StoriesTitleText( text: String, @@ -187,15 +188,14 @@ private fun StoriesTitleText( fontSize = 32.sp, fontWeight = FontWeight.SemiBold, color = if (isDarkBackground) Color.White else Color(0xFF090E13), - textAlign = TextAlign.Center + textAlign = TextAlign.Center, ) } } +@Suppress("MagicNumber") @Composable -private fun StoriesSubtitleText( - subtitleText: AnnotatedString, -) { +private fun StoriesSubtitleText(subtitleText: AnnotatedString) { val color = Color(0xFFA6AAAD) StoriesTextAnimation( @@ -209,22 +209,22 @@ private fun StoriesSubtitleText( text = subtitleText, fontSize = 20.sp, color = color, - textAlign = TextAlign.Center + textAlign = TextAlign.Center, ) } } @Composable private fun StoriesImage( - modifier: Modifier = Modifier, @DrawableRes drawableResId: Int, isDarkBackground: Boolean, + modifier: Modifier = Modifier, ) { Image( painter = painterResource(id = drawableResId), contentDescription = null, contentScale = if (isDarkBackground) ContentScale.Inside else ContentScale.FillWidth, - modifier = modifier.fillMaxWidth() + modifier = modifier.fillMaxWidth(), ) } @@ -233,7 +233,6 @@ private fun String.annotated(): AnnotatedString { return buildAnnotatedString { append(source) } } - @Preview @Composable private fun RevolutionaryWalletPreview() { @@ -252,14 +251,12 @@ private fun CurrenciesPreview() { StoriesCurrencies(false, 6000) } - @Preview @Composable private fun Web3Preview() { StoriesWeb3(false, 6000) } - @Preview @Composable private fun WalletForEveryonePreview() { diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt index 85ec581b07..87cee39eb4 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt @@ -53,7 +53,7 @@ fun StoriesCurrenciesContent( val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.halfHeight() val animateFrom = chessOffset - moveItemToStartOfScreen - val animateTo = 50.dp - (50.dp * index * decreaseRate) + val animateTo = 50.dp - 50.dp * index * decreaseRate HorizontalSlidingImage( paused = paused, @@ -70,6 +70,7 @@ fun StoriesCurrenciesContent( } } +@Suppress("MagicNumber") @Composable fun StoriesWeb3Content( paused: Boolean, @@ -99,7 +100,7 @@ fun StoriesWeb3Content( val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.width / 3 val animateFrom = chessOffset - moveItemToStartOfScreen - val animateTo = 70.dp - (70.dp * index * decreaseRate) + val animateTo = 70.dp - 70.dp * index * decreaseRate HorizontalSlidingImage( paused = paused, @@ -112,7 +113,6 @@ fun StoriesWeb3Content( ) } } - } } diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt index 393642f976..5dcfbb5f1f 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt @@ -5,8 +5,17 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.Box +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.foundation.layout.wrapContentSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -25,10 +34,12 @@ import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation import com.tangem.tap.features.home.compose.StoriesTextAnimation import com.tangem.wallet.R +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") @Composable fun FirstStoriesContent( - isPaused: Boolean, duration: Int = 8_000, - hideContent: (Boolean) -> Unit + isPaused: Boolean, + duration: Int, + onHideContent: (Boolean) -> Unit, ) { val screenState = remember { mutableStateOf(StartingScreenState.INIT) } val progress = remember { Animatable(0f) } @@ -41,8 +52,8 @@ fun FirstStoriesContent( targetValue = 2f, animationSpec = tween( durationMillis = duration, - easing = LinearEasing - ) + easing = LinearEasing, + ), ) } } @@ -60,8 +71,8 @@ fun FirstStoriesContent( in 1.2f..2f -> screenState.value = StartingScreenState.MEET_TANGEM } - if (screenState.value == StartingScreenState.INIT) hideContent(true) - if (screenState.value == StartingScreenState.BUY) hideContent(false) + if (screenState.value == StartingScreenState.INIT) onHideContent(true) + if (screenState.value == StartingScreenState.BUY) onHideContent(false) val style = TextStyle( fontSize = 60.sp, @@ -72,7 +83,6 @@ fun FirstStoriesContent( val textId = screenState.textId() Box(modifier = Modifier.fillMaxSize()) { - if (screenState.isSplashingTextDisplaying()) { TextAutoSize( modifier = Modifier @@ -80,7 +90,7 @@ fun FirstStoriesContent( .padding(start = 20.dp, end = 20.dp, bottom = 100.dp), text = textId?.let { stringResource(textId) } ?: "", textStyle = style, - fontSizeRange = FontSizeRange(20.sp, 60.sp) + fontSizeRange = FontSizeRange(20.sp, 60.sp), ) } else { Column( @@ -88,7 +98,7 @@ fun FirstStoriesContent( ) { Box( modifier = Modifier - .weight(0.7f) + .weight(0.7f), ) { if (screenState.isMeetTangemDisplaying()) { StoriesTextAnimation( @@ -100,10 +110,9 @@ fun FirstStoriesContent( .alpha(if (screenState.value == StartingScreenState.SHOW_CARD) 0f else 1f), text = textId?.let { stringResource(textId) } ?: "", textStyle = style, - fontSizeRange = FontSizeRange(30.sp, 50.sp) + fontSizeRange = FontSizeRange(30.sp, 50.sp), ) } - } } Box( @@ -155,9 +164,8 @@ private fun MutableState.isMeetTangemDisplaying(): Boolean return this.value == StartingScreenState.MEET_TANGEM } - @Preview @Composable -fun FirstStoriesPreview() { +private fun FirstStoriesPreview() { FirstStoriesContent(false, 8000) {} } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt index 8528982857..fcb0fce154 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt @@ -19,14 +19,13 @@ fun FloatingCardsContent( isPaused: Boolean, stepDuration: Int, ) { - val imageBitmap = asImageBitmap(R.drawable.card_placeholder_wallet) val cards = listOf( FloatingCard.first(), FloatingCard.second(), FloatingCard.third(), ) - Box() { + Box { cards.forEach { floatingCard -> FloatingCard.Item( isPaused = isPaused, @@ -47,57 +46,59 @@ private data class CardValues( val scale: AnimatedValue = AnimatedValue(1f, 1f), ) -private class FloatingCard { - companion object { +private object FloatingCard { - @Composable - fun Item( - isPaused: Boolean, - stepDuration: Int, - imageBitmap: ImageBitmap, - cardValues: CardValues, - ) { - Image( - bitmap = imageBitmap, - contentDescription = "Floating Tangem card", - modifier = Modifier - .graphicsLayer( - translationX = cardValues.translateX.toAnimatable(isPaused, stepDuration).value, - translationY = cardValues.translateY.toAnimatable(isPaused, stepDuration).value, - rotationX = cardValues.rotationX.toAnimatable(isPaused, stepDuration).value, - rotationY = cardValues.rotationY.toAnimatable(isPaused, stepDuration).value, - rotationZ = cardValues.rotationZ.toAnimatable(isPaused, stepDuration).value, - scaleX = cardValues.scale.toAnimatable(isPaused, stepDuration).value, - scaleY = cardValues.scale.toAnimatable(isPaused, stepDuration).value, - ), - ) - } - - fun first(): CardValues = CardValues( - translateX = -400f to -350f, - translateY = 30f to 32f, - rotationX = 10f to 15f, - rotationY = 15f to 15f, - rotationZ = 40f to 27f, - scale = 0.6f to 0.6f, - ) - - fun second(): CardValues = CardValues( - translateX = 350f to 300f, - translateY = -70f to 0f, - rotationX = 30f to 48f, - rotationY = 0f to 5f, - rotationZ = -34f to -42f, - scale = 0.47f to 0.35f, - ) - - fun third(): CardValues = CardValues( - translateX = 320f to 250f, - translateY = 500f to 500f, - rotationX = 0f to 3f, - rotationY = 10f to 10f, - rotationZ = -45f to -30f, - scale = 0.6f to 0.75f, + @Suppress("TopLevelComposableFunctions") + @Composable + fun Item( + isPaused: Boolean, + stepDuration: Int, + imageBitmap: ImageBitmap, + cardValues: CardValues, + ) { + Image( + bitmap = imageBitmap, + contentDescription = "Floating Tangem card", + modifier = Modifier + .graphicsLayer( + translationX = cardValues.translateX.toAnimatable(isPaused, stepDuration).value, + translationY = cardValues.translateY.toAnimatable(isPaused, stepDuration).value, + rotationX = cardValues.rotationX.toAnimatable(isPaused, stepDuration).value, + rotationY = cardValues.rotationY.toAnimatable(isPaused, stepDuration).value, + rotationZ = cardValues.rotationZ.toAnimatable(isPaused, stepDuration).value, + scaleX = cardValues.scale.toAnimatable(isPaused, stepDuration).value, + scaleY = cardValues.scale.toAnimatable(isPaused, stepDuration).value, + ), ) } + + @Suppress("MagicNumber") + fun first(): CardValues = CardValues( + translateX = -400f to -350f, + translateY = 30f to 32f, + rotationX = 10f to 15f, + rotationY = 15f to 15f, + rotationZ = 40f to 27f, + scale = 0.6f to 0.6f, + ) + + @Suppress("MagicNumber") + fun second(): CardValues = CardValues( + translateX = 350f to 300f, + translateY = -70f to 0f, + rotationX = 30f to 48f, + rotationY = 0f to 5f, + rotationZ = -34f to -42f, + scale = 0.47f to 0.35f, + ) + + @Suppress("MagicNumber") + fun third(): CardValues = CardValues( + translateX = 320f to 250f, + translateY = 500f to 500f, + rotationX = 0f to 3f, + rotationY = 10f to 10f, + rotationZ = -45f to -30f, + scale = 0.6f to 0.75f, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index b5ca216aeb..95049be464 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -2,7 +2,9 @@ package com.tangem.tap.features.home.compose.views import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.size import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults @@ -19,13 +21,14 @@ import androidx.compose.ui.unit.sp import com.tangem.core.ui.components.SpacerS8 import com.tangem.wallet.R +@Suppress("MagicNumber") @Composable fun HomeButtons( - modifier: Modifier = Modifier, isDarkBackground: Boolean, btnScanStateInProgress: Boolean, onScanButtonClick: () -> Unit, onShopButtonClick: () -> Unit, + modifier: Modifier = Modifier, ) { val darkColorBackground = Color(0xFF26292E) val darkColorButton = Color(0xFF080C10) @@ -35,9 +38,6 @@ fun HomeButtons( modifier = modifier, ) { ProgressButton( - modifier = Modifier - .weight(1f) - .height(48.dp), inProgress = btnScanStateInProgress, backgroundColor = if (isDarkBackground) darkColorBackground else Color.White, contentColor = if (isDarkBackground) Color.White else darkColorButton, @@ -63,7 +63,7 @@ fun HomeButtons( Button( modifier = Modifier .weight(1f) - .height(48.dp), + .heightIn(48.dp), onClick = onShopButtonClick, enabled = !btnScanStateInProgress, colors = ButtonDefaults.textButtonColors( @@ -82,9 +82,9 @@ fun HomeButtons( } } +@Suppress("LongParameterList") @Composable -fun ProgressButton( - modifier: Modifier, +fun RowScope.ProgressButton( backgroundColor: Color, contentColor: Color, onClick: () -> Unit, @@ -93,7 +93,9 @@ fun ProgressButton( content: @Composable () -> Unit, ) { Button( - modifier = modifier, + modifier = Modifier + .weight(1f) + .height(48.dp), onClick = onClick, enabled = !inProgress, colors = ButtonDefaults.textButtonColors( diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt index 3779f57358..4692fa91aa 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt @@ -26,7 +26,7 @@ fun StoriesProgressBar( currentStep: Int, paused: Boolean = false, stepDuration: Int = 8_000, - onStepFinished: () -> Unit = {}, + onStepFinish: () -> Unit = {}, ) { val progress = remember(currentStep) { Animatable(0f) } @@ -42,9 +42,8 @@ fun StoriesProgressBar( ) ) progress.snapTo(0f) - onStepFinished() + onStepFinish() } - } Row( @@ -81,6 +80,6 @@ fun StoriesProgressBar( @Preview @Composable -fun StoriesProgressBarPreview() { +private fun StoriesProgressBarPreview() { StoriesProgressBar(steps = 3, currentStep = 2, paused = false) { } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt new file mode 100644 index 0000000000..fed3f2e394 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt @@ -0,0 +1,23 @@ +package com.tangem.tap.features.home.data + +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.GeoResponse +import com.tangem.tap.features.home.domain.HomeRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +/** + * Implementation of repository for Home feature + * + * @property tangemTechApi API for server requests + * @property dispatchers coroutine dispatcher provider + */ +class HomeRepositoryImpl( + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, +) : HomeRepository { + + override suspend fun getUserCountryCode(): GeoResponse = withContext(dispatchers.io) { + tangemTechApi.getUserCountryCode() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt b/app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt new file mode 100644 index 0000000000..ad489ae0fe --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.features.home.domain + +import com.tangem.datasource.api.tangemTech.models.GeoResponse + +/** Repository for Home feature */ +interface HomeRepository { + + /** Get user's country code */ + suspend fun getUserCountryCode(): GeoResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 76048ed0f4..902bb0ef47 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -19,7 +19,7 @@ import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.features.home.BELARUS_COUNTRY_CODE import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE -import com.tangem.tap.features.home.redux.HomeMiddleware.Companion.BUY_WALLET_URL +import com.tangem.tap.features.home.redux.HomeMiddleware.BUY_WALLET_URL import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope @@ -32,12 +32,10 @@ import org.rekotlin.Action import org.rekotlin.Middleware import timber.log.Timber -class HomeMiddleware { - companion object { - val handler = homeMiddleware +object HomeMiddleware { + val handler = homeMiddleware - const val BUY_WALLET_URL = "https://tangem.com/ru/resellers/" - } + const val BUY_WALLET_URL = "https://tangem.com/ru/resellers/" } private val homeMiddleware: Middleware = { _, _ -> @@ -115,7 +113,7 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { private suspend fun navigateTo(appScreen: AppScreen) { store.dispatchOnMain(NavigationAction.NavigateTo(appScreen)) - delay(200) + delay(timeMillis = 200) changeButtonState(ButtonState.ENABLED) } 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 8e1583baa5..f9f2911c32 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 @@ -3,14 +3,11 @@ package com.tangem.tap.features.home.redux import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class HomeReducer { - companion object { - fun reduce(action: Action, state: AppState): HomeState = internalReduce(action, state) - } +object HomeReducer { + fun reduce(action: Action, state: AppState): HomeState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): HomeState { - if (action !is HomeAction) return state.homeState var state = state.homeState @@ -21,6 +18,7 @@ private fun internalReduce(action: Action, state: AppState): HomeState { is HomeAction.ChangeScanCardButtonState -> { state = state.copy(btnScanState = action.state) } + else -> {} } return state diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt new file mode 100644 index 0000000000..7773489a2f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.features.onboarding + +import com.tangem.common.extensions.VoidCallback +import com.tangem.tap.common.redux.StateDialog + +/** +[REDACTED_AUTHOR] + */ +sealed class OnboardingDialog : StateDialog { + object TwinningProcessNotCompleted : OnboardingDialog() + data class InterruptOnboarding(val onOk: VoidCallback) : OnboardingDialog() +} \ No newline at end of file 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 cb01879840..5ed844549d 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 @@ -17,82 +17,79 @@ import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] */ -class OnboardingHelper { - companion object { - - fun isOnboardingCase(response: ScanResponse): Boolean { - val cardInfoStorage = preferencesStorage.usedCardsPrefStorage - val cardId = response.card.cardId - return when { - response.isTangemTwins() -> { - if (!response.twinsIsTwinned()) { - true - } else { - cardInfoStorage.isActivationInProgress(cardId) - } - } - response.card.wallets.isNotEmpty() -> cardInfoStorage.isActivationInProgress(cardId) - else -> true - } - } - - fun whereToNavigate(scanResponse: ScanResponse): AppScreen { - return when (scanResponse.productType) { - ProductType.Note -> AppScreen.OnboardingNote - ProductType.Wallet -> if (scanResponse.card.settings.isBackupAllowed) { - AppScreen.OnboardingWallet +object OnboardingHelper { + fun isOnboardingCase(response: ScanResponse): Boolean { + val cardInfoStorage = preferencesStorage.usedCardsPrefStorage + val cardId = response.card.cardId + return when { + response.cardTypesResolver.isTangemTwins() -> { + if (!response.twinsIsTwinned()) { + true } else { - AppScreen.OnboardingOther - } - ProductType.Twins -> AppScreen.OnboardingTwins - ProductType.SaltPay -> AppScreen.OnboardingWallet - ProductType.Start2Coin -> throw java.lang.UnsupportedOperationException( - "Onboarding for Start2Coin cards is not supported" - ) - } - } - - fun trySaveWalletAndNavigateToWalletScreen( - scanResponse: ScanResponse, - accessCode: String? = null, - backupCardsIds: List? = null, - ) { - when { - // When should save user wallets, then save card without navigate to save wallet screen - preferencesStorage.shouldSaveUserWallets -> scope.launch { - store.dispatchOnMain( - SaveWalletAction.ProvideBackupInfo( - scanResponse = scanResponse, - accessCode = accessCode, - backupCardsIds = backupCardsIds?.toSet(), - ), - ) - store.dispatchOnMain(SaveWalletAction.Save) - } - // When should not save user wallets but device has biometry and save wallet screen has not been shown, - // then open save wallet screen - tangemSdkManager.canUseBiometry && - preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { - store.onCardScanned(scanResponse) - - delay(timeMillis = 1_200) - - store.dispatchOnMain( - SaveWalletAction.ProvideBackupInfo( - scanResponse = scanResponse, - accessCode = accessCode, - backupCardsIds = backupCardsIds?.toSet(), - ), - ) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) - } - // If device has no biometry and save wallet screen has been shown, then go through old scenario - else -> scope.launch { - store.onCardScanned(scanResponse) + cardInfoStorage.isActivationInProgress(cardId) } } - - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + response.card.wallets.isNotEmpty() -> cardInfoStorage.isActivationInProgress(cardId) + else -> true } } + + fun whereToNavigate(scanResponse: ScanResponse): AppScreen { + return when (scanResponse.productType) { + ProductType.Note -> AppScreen.OnboardingNote + ProductType.Wallet -> if (scanResponse.card.settings.isBackupAllowed) { + AppScreen.OnboardingWallet + } else { + AppScreen.OnboardingOther + } + ProductType.Twins -> AppScreen.OnboardingTwins + ProductType.SaltPay -> AppScreen.OnboardingWallet + ProductType.Start2Coin -> throw java.lang.UnsupportedOperationException( + "Onboarding for Start2Coin cards is not supported", + ) + } + } + + fun trySaveWalletAndNavigateToWalletScreen( + scanResponse: ScanResponse, + accessCode: String? = null, + backupCardsIds: List? = null, + ) { + when { + // When should save user wallets, then save card without navigate to save wallet screen + preferencesStorage.shouldSaveUserWallets -> scope.launch { + store.dispatchOnMain( + SaveWalletAction.ProvideBackupInfo( + scanResponse = scanResponse, + accessCode = accessCode, + backupCardsIds = backupCardsIds?.toSet(), + ), + ) + store.dispatchOnMain(SaveWalletAction.Save) + } + // When should not save user wallets but device has biometry and save wallet screen has not been shown, + // then open save wallet screen + tangemSdkManager.canUseBiometry && + preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { + store.onCardScanned(scanResponse) + + delay(timeMillis = 1_200) + + store.dispatchOnMain( + SaveWalletAction.ProvideBackupInfo( + scanResponse = scanResponse, + accessCode = accessCode, + backupCardsIds = backupCardsIds?.toSet(), + ), + ) + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) + } + // If device has no biometry and save wallet screen has been shown, then go through old scenario + else -> scope.launch { + store.onCardScanned(scanResponse) + } + } + + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + } } \ No newline at end of file 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 853044464b..47caa1b85c 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 @@ -55,7 +55,7 @@ class OnboardingManager( } } is Result.Failure -> { - val error = (result.error as? TapError) ?: TapError.UnknownError + val error = result.error as? TapError ?: TapError.UnknownError when (error) { is TapError.WalletManager.NoAccountError -> OnboardingWalletBalance.error(error) // NoInternetConnection, WalletManager.InternalError @@ -69,7 +69,8 @@ class OnboardingManager( return balance.copy( currency = Currency.Blockchain( - walletManager.wallet.blockchain, walletManager.wallet.publicKey.derivationPath?.rawPath, + blockchain = walletManager.wallet.blockchain, + derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath, ), ) } @@ -100,11 +101,11 @@ data class OnboardingWalletBalance( val criticalError: TapError? = null, ) { - fun balanceIsToppedUp(): Boolean = value.isPositive() || hasIncomingTransaction - val amountToCreateAccount: String? get() = if (error is TapError.WalletManager.NoAccountError) error.customMessage else null + fun balanceIsToppedUp(): Boolean = value.isPositive() || hasIncomingTransaction + companion object { fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( state = ProgressState.Error, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt index 209fc789eb..b9aa35f096 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt @@ -12,39 +12,23 @@ import com.tangem.tap.preferencesStorage /** [REDACTED_AUTHOR] */ -class OnboardingSaltPayHelper { - companion object { +object OnboardingSaltPayHelper { + suspend fun isOnboardingCase(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result { + return try { + var updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } - suspend fun isOnboardingCase( - scanResponse: ScanResponse, - manager: SaltPayActivationManager, - ): Result { - return try { - var updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } - - val cardStorage = preferencesStorage.usedCardsPrefStorage - val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId) - if (updatedStep == SaltPayActivationStep.Success && activationIsFinished) { - updatedStep = SaltPayActivationStep.Finished - } - val isActivationCase = updatedStep != SaltPayActivationStep.Finished - val isBackupCase = scanResponse.card.backupStatus?.isActive == false - Result.Success(isActivationCase || isBackupCase) - } catch (ex: Exception) { - Result.Failure(ex) - } catch (error: SaltPayActivationError) { - Result.Failure(error) + val cardStorage = preferencesStorage.usedCardsPrefStorage + val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId) + if (updatedStep == SaltPayActivationStep.Success && activationIsFinished) { + updatedStep = SaltPayActivationStep.Finished } + val isActivationCase = updatedStep != SaltPayActivationStep.Finished + val isBackupCase = scanResponse.card.backupStatus?.isActive == false + Result.Success(isActivationCase || isBackupCase) + } catch (ex: Exception) { + Result.Failure(ex) + } catch (error: SaltPayActivationError) { + Result.Failure(error) } - - fun testProceedToOnboarding( - scanResponse: ScanResponse, - manager: SaltPayActivationManager, - ): Result = Result.Success(true) - - fun testProceedToMainScreen( - scanResponse: ScanResponse, - manager: SaltPayActivationManager, - ): Result = Result.Success(false) } } \ No newline at end of file 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 c4b34eba62..3549ea11ab 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 @@ -159,7 +159,8 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { state.walletBalance.amountToCreateAccount?.let { amount -> val tvBodyMessage = getString( R.string.onboarding_top_up_body_no_account_error, - amount, state.walletBalance.currency.currencySymbol, + amount, + state.walletBalance.currency.currencySymbol, ) tvBody.text = tvBodyMessage } @@ -210,4 +211,8 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition) } } + + override fun handleOnBackPressed() { + store.dispatch(OnboardingNoteAction.OnBackPressed) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt index 8e7046f114..ebdf31becb 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt @@ -15,6 +15,7 @@ sealed class OnboardingNoteAction : Action { object CreateWallet : OnboardingNoteAction() object TopUp : OnboardingNoteAction() object ShowAddressInfoDialog : OnboardingNoteAction() + object OnBackPressed : OnboardingNoteAction() // from redux class SetArtworkUrl(val artworkUrl: String) : OnboardingNoteAction() 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 c4f112770f..169e5dfb7f 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 @@ -2,9 +2,9 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -18,10 +18,12 @@ import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager 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.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.ProgressState @@ -36,10 +38,8 @@ import org.rekotlin.Action import org.rekotlin.DispatchFunction import org.rekotlin.Middleware -class OnboardingNoteMiddleware { - companion object { - val handler = onboardingNoteMiddleware - } +object OnboardingNoteMiddleware { + val handler = onboardingNoteMiddleware } private val onboardingNoteMiddleware: Middleware = { dispatch, state -> @@ -51,6 +51,7 @@ private val onboardingNoteMiddleware: Middleware = { dispatch, state - } } +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch: DispatchFunction) { if (action !is OnboardingNoteAction) return if (DemoHelper.tryHandle(appState, action)) return @@ -80,7 +81,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch noteState.walletBalance.balanceIsToppedUp() -> OnboardingNoteStep.Done else -> OnboardingNoteStep.TopUpWallet } - store.dispatch((OnboardingNoteAction.SetStepOfScreen(step))) + store.dispatch(OnboardingNoteAction.SetStepOfScreen(step)) } is OnboardingNoteAction.SetStepOfScreen -> { when (action.step) { @@ -96,6 +97,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch onboardingManager.activationFinished(card.cardId) postUi(DELAY_SDK_DIALOG_CLOSE) { store.dispatch(OnboardingNoteAction.Confetti.Show) } } + else -> {} } } is OnboardingNoteAction.CreateWallet -> { @@ -186,9 +188,19 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch store.dispatchOpenUrl(topUpUrl) } } - OnboardingNoteAction.Done -> { + is OnboardingNoteAction.Done -> { store.dispatch(GlobalAction.Onboarding.Stop) OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) } + is OnboardingNoteAction.OnBackPressed -> { + store.dispatchDialogShow( + OnboardingDialog.InterruptOnboarding( + onOk = { + store.dispatch(NavigationAction.PopBackTo()) + }, + ), + ) + } + else -> Unit } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt index 7626922560..5552fc8b50 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt @@ -4,10 +4,8 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import org.rekotlin.Action -class OnboardingNoteReducer { - companion object { - fun reduce(action: Action, state: AppState): OnboardingNoteState = internalReduce(action, state) - } +object OnboardingNoteReducer { + fun reduce(action: Action, state: AppState): OnboardingNoteState = internalReduce(action, state) } private fun internalReduce(action: Action, appState: AppState): OnboardingNoteState { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index 5439b76612..a0a6b8b76b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -65,6 +65,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment setupCreateWalletState() OnboardingOtherCardsStep.Done -> setupDoneState() + else -> {} } showConfetti(state.showConfetti) } @@ -87,7 +88,6 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment = { dispatch, _ -> @@ -35,6 +33,7 @@ private val onboardingOtherCardsMiddleware: Middleware = { dispatch, _ } } +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") private fun handleOtherCardsAction(action: Action) { if (action !is OnboardingOtherCardsAction) return val globalState = store.state.globalState @@ -59,7 +58,7 @@ private fun handleOtherCardsAction(action: Action) { card.wallets.isEmpty() -> OnboardingOtherCardsStep.CreateWallet else -> OnboardingOtherCardsStep.Done } - store.dispatch((OnboardingOtherCardsAction.SetStepOfScreen(step))) + store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(step)) } is OnboardingOtherCardsAction.SetStepOfScreen -> { when (action.step) { @@ -71,6 +70,7 @@ private fun handleOtherCardsAction(action: Action) { onboardingManager.activationFinished(card.cardId) postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) } } + else -> Unit } } is OnboardingOtherCardsAction.CreateWallet -> { @@ -87,9 +87,9 @@ private fun handleOtherCardsAction(action: Action) { onboardingManager.scanResponse = updatedResponse onboardingManager.activationStarted(updatedCard.cardId) - val primaryBlockchain = updatedResponse.getBlockchain() + val primaryBlockchain = updatedResponse.cardTypesResolver.getBlockchain() val blockchainNetworks = if (primaryBlockchain != Blockchain.Unknown) { - val primaryToken = updatedResponse.getPrimaryToken() + val primaryToken = updatedResponse.cardTypesResolver.getPrimaryToken() val blockchainNetwork = BlockchainNetwork( blockchain = primaryBlockchain, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt index e8dc2ae111..3dae886ef6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt @@ -4,10 +4,8 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import org.rekotlin.Action -class OnboardingOtherCardsReducer { - companion object { - fun reduce(action: Action, state: AppState): OnboardingOtherCardsState = internalReduce(action, state) - } +object OnboardingOtherCardsReducer { + fun reduce(action: Action, state: AppState): OnboardingOtherCardsState = internalReduce(action, state) } private fun internalReduce(action: Action, appState: AppState): OnboardingOtherCardsState { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt index 8b25bf8e00..d6d15cec4c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt @@ -4,8 +4,7 @@ import com.tangem.Message import com.tangem.blockchain.common.WalletManager import com.tangem.common.extensions.VoidCallback import com.tangem.domain.common.ScanResponse -import com.tangem.tap.common.AssetReader -import com.tangem.tap.common.redux.StateDialog +import com.tangem.datasource.utils.AssetReader import com.tangem.tap.domain.TapError import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.features.onboarding.OnboardingWalletBalance @@ -28,13 +27,6 @@ sealed class TwinCardsAction : Action { } sealed class Wallet : TwinCardsAction() { - data class HandleOnBackPressed( - // this is necessary for the correct animation of the return of cards to the Home screen - val shouldResetTwinCardsWidget: (should: Boolean, popAction: VoidCallback) -> Unit - ) : TwinCardsAction() - - data class ShowInterruptDialog(val onOk: VoidCallback) : TwinCardsAction(), StateDialog - data class LaunchFirstStep(val initialMessage: Message, val reader: AssetReader) : TwinCardsAction() data class LaunchSecondStep( val initialMessage: Message, @@ -52,6 +44,11 @@ sealed class TwinCardsAction : Action { data class SetWalletManager(val walletManager: WalletManager) : TwinCardsAction() object Done : TwinCardsAction() + data class OnBackPressed( + // this is necessary for the correct animation of the return of cards to the Home screen + val shouldResetTwinCardsWidget: (should: Boolean, popAction: VoidCallback) -> Unit, + ) : TwinCardsAction() + data class SaveScannedTwinCardAndNavigateToWallet( val scanResponse: ScanResponse, ) : TwinCardsAction() 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 6c913f0b1e..df7d042f2b 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 @@ -3,10 +3,10 @@ package com.tangem.tap.features.onboarding.products.twins.redux import com.tangem.blockchain.extensions.Result import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -26,8 +26,9 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.twins.TwinCardsManager -import com.tangem.tap.features.onboarding.OnboardingHelper 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.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction @@ -42,10 +43,8 @@ import org.rekotlin.Action import org.rekotlin.DispatchFunction import org.rekotlin.Middleware -class TwinCardsMiddleware { - companion object { - val handler = twinsWalletMiddleware - } +object TwinCardsMiddleware { + val handler = twinsWalletMiddleware } private val twinsWalletMiddleware: Middleware = { dispatch, state -> @@ -57,6 +56,7 @@ private val twinsWalletMiddleware: Middleware = { dispatch, state -> } } +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") private fun handle(action: Action, dispatch: DispatchFunction) { val action = action as? TwinCardsAction ?: return @@ -111,7 +111,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done else -> TwinCardsStep.TopUpWallet } - store.dispatch((TwinCardsAction.SetStepOfScreen(step))) + store.dispatch(TwinCardsAction.SetStepOfScreen(step)) } else { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Welcome)) } @@ -121,27 +121,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } } } - is TwinCardsAction.Wallet.HandleOnBackPressed -> { - val shouldReturnCardBack = twinCardsState.mode == CreateTwinWalletMode.CreateWallet - && twinCardsState.currentStep != TwinCardsStep.TopUpWallet - && twinCardsState.currentStep != TwinCardsStep.Done - - if (twinCardsState.showAlert) { - val onInterruptPrompt = { - store.dispatch(TwinCardsAction.CardsManager.Release) - action.shouldResetTwinCardsWidget(shouldReturnCardBack) { - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - } - } - val stateDialog = TwinCardsAction.Wallet.ShowInterruptDialog(onInterruptPrompt) - store.dispatchDialogShow(stateDialog) - } else { - store.dispatch(TwinCardsAction.CardsManager.Release) - action.shouldResetTwinCardsWidget(shouldReturnCardBack) { - store.dispatch(NavigationAction.PopBackTo()) - } - } - } is TwinCardsAction.SetStepOfScreen -> { when (action.step) { is TwinCardsStep.WelcomeOnly, TwinCardsStep.Welcome -> { @@ -270,7 +249,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { scope.launch { val loadedBalance = onboardingManager?.updateBalance(walletManager) ?: return@launch - loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) } + loadedBalance.criticalError?.let(store::dispatchErrorNotification) delay(if (isLoadedBefore) 0 else 300) withMainContext { store.dispatch(TwinCardsAction.Balance.Set(loadedBalance)) @@ -332,5 +311,23 @@ private fun handle(action: Action, dispatch: DispatchFunction) { scanResponse = action.scanResponse, ) } + is TwinCardsAction.OnBackPressed -> { + if (twinCardsState.twinningInProgress) { + store.dispatchDialogShow(OnboardingDialog.TwinningProcessNotCompleted) + } else { + val onOkCallback = { + val shouldReturnCardBack = twinCardsState.mode == CreateTwinWalletMode.CreateWallet && + twinCardsState.currentStep != TwinCardsStep.TopUpWallet && + twinCardsState.currentStep != TwinCardsStep.Done + + store.dispatch(TwinCardsAction.CardsManager.Release) + action.shouldResetTwinCardsWidget(shouldReturnCardBack) { + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + store.dispatchDialogShow(OnboardingDialog.InterruptOnboarding(onOkCallback)) + } + } + else -> Unit } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt index d482a5a647..9902cd2ce5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt @@ -4,12 +4,11 @@ import com.tangem.domain.common.getTwinCardNumber import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class TwinCardsReducer { - companion object { - fun reduce(action: Action, state: AppState): TwinCardsState = internalReduce(action, state) - } +object TwinCardsReducer { + fun reduce(action: Action, state: AppState): TwinCardsState = internalReduce(action, state) } +@Suppress("ComplexMethod") private fun internalReduce(action: Action, state: AppState): TwinCardsState { if (action !is TwinCardsAction) return state.twinCardsState @@ -17,7 +16,7 @@ private fun internalReduce(action: Action, state: AppState): TwinCardsState { when (action) { is TwinCardsAction.IfTwinsPrepareState -> { - state = if (action.scanResponse.isTangemTwins()) { + state = if (action.scanResponse.cardTypesResolver.isTangemTwins()) { val cardNumber = action.scanResponse.card.getTwinCardNumber() ?: return state TwinCardsState( @@ -67,6 +66,7 @@ private fun internalReduce(action: Action, state: AppState): TwinCardsState { is TwinCardsAction.Confetti.Hide -> { state = state.copy(showConfetti = false) } + else -> {} } return state diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt index 0e1078584e..88f9e5d82d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt @@ -39,21 +39,21 @@ data class TwinCardsState( TwinCardsStep.CreateSecondWallet, TwinCardsStep.CreateThirdWallet, TwinCardsStep.TopUpWallet, - TwinCardsStep.Done + TwinCardsStep.Done, ) CreateTwinWalletMode.RecreateWallet -> listOf( TwinCardsStep.None, TwinCardsStep.CreateFirstWallet, TwinCardsStep.CreateSecondWallet, TwinCardsStep.CreateThirdWallet, - TwinCardsStep.Done + TwinCardsStep.Done, ) } val progress: Int get() = steps.indexOf(currentStep) - val showAlert: Boolean + val twinningInProgress: Boolean get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet val isBuyAllowed: Boolean by ReadOnlyProperty { _, _ -> diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index 9d7b99a2a6..86fb3f8c57 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -13,12 +13,12 @@ import coil.load import com.tangem.Message import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback +import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TwinCardNumber import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget -import com.tangem.tap.common.AndroidAssetReader -import com.tangem.core.analytics.Analytics +import com.tangem.datasource.utils.AndroidAssetReader import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.getDrawableCompat @@ -40,6 +40,7 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding +@Suppress("LargeClass") class TwinsCardsFragment : BaseOnboardingFragment() { private val mainBinding by lazy { binding.vMain } @@ -64,6 +65,7 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } } + @Suppress("MagicNumber") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) addBackPressHandler(this) @@ -133,13 +135,14 @@ class TwinsCardsFragment : BaseOnboardingFragment() { when (state.currentStep) { is TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state, state.currentStep.scanResponse) - TwinCardsStep.Welcome -> setupWelcomeState(state) - TwinCardsStep.Warning -> setupWarningState(state) - TwinCardsStep.CreateFirstWallet -> setupCreateFirstWalletState(state) - TwinCardsStep.CreateSecondWallet -> setupCreateSecondWalletState(state) - TwinCardsStep.CreateThirdWallet -> setupCreateThirdWalletState(state) - TwinCardsStep.TopUpWallet -> setupTopUpWalletState(state) - TwinCardsStep.Done -> setupDoneState(state) + is TwinCardsStep.Welcome -> setupWelcomeState(state) + is TwinCardsStep.Warning -> setupWarningState(state) + is TwinCardsStep.CreateFirstWallet -> setupCreateFirstWalletState(state) + is TwinCardsStep.CreateSecondWallet -> setupCreateSecondWalletState(state) + is TwinCardsStep.CreateThirdWallet -> setupCreateThirdWalletState(state) + is TwinCardsStep.TopUpWallet -> setupTopUpWalletState(state) + is TwinCardsStep.Done -> setupDoneState(state) + else -> {} } setBalance(state) @@ -231,12 +234,14 @@ class TwinsCardsFragment : BaseOnboardingFragment() { false, ) } + else -> {} } } } TwinCardsStep.Welcome, TwinCardsStep.Warning -> { twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) }) } + else -> {} } val twinIndexNumber = state.cardNumber?.indexNumber() @@ -314,11 +319,13 @@ class TwinsCardsFragment : BaseOnboardingFragment() { twinsWidget.toActivate(false) } } + else -> {} } } TwinCardsStep.CreateThirdWallet -> { twinsWidget.toActivate() } + else -> {} } if (state.isBuyAllowed) { @@ -401,11 +408,10 @@ class TwinsCardsFragment : BaseOnboardingFragment() { override fun handleOnBackPressed() { store.dispatch( - TwinCardsAction.Wallet.HandleOnBackPressed { should, popAction -> + TwinCardsAction.OnBackPressed { should, popAction -> store.dispatch(TwinCardsAction.Confetti.Hide) showConfetti(false) - if (should) switchToCard(TwinCardNumber.First, true, popAction) - else popAction() + if (should) switchToCard(TwinCardNumber.First, true, popAction) else popAction() }, ) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt deleted file mode 100644 index 62d98c1f69..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.tap.features.onboarding.products.twins.ui.dialog - -import android.content.Context -import androidx.appcompat.app.AlertDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.tangem.tap.common.extensions.dispatchDialogHide -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction -import com.tangem.tap.store -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -class CreateWalletInterruptDialog { - companion object { - fun create(state: TwinCardsAction.Wallet.ShowInterruptDialog, context: Context): AlertDialog { - return MaterialAlertDialogBuilder(context) - .setMessage(R.string.onboarding_twin_exit_warning) - .setPositiveButton(R.string.warning_button_ok) { _, _ -> } - .setOnDismissListener { store.dispatchDialogHide() } - .create() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/TwinningProcessNotCompletedDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/TwinningProcessNotCompletedDialog.kt new file mode 100644 index 0000000000..2a074debb7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/TwinningProcessNotCompletedDialog.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.features.onboarding.products.twins.ui.dialog + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.tangem.tap.common.extensions.dispatchDialogHide +import com.tangem.tap.store +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +object TwinningProcessNotCompletedDialog { + fun create(context: Context): AlertDialog { + return MaterialAlertDialogBuilder(context) + .setMessage(R.string.onboarding_twin_exit_warning) + .setPositiveButton(R.string.warning_button_ok) { _, _ -> } + .setOnDismissListener { store.dispatchDialogHide() } + .create() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index f3ff070f97..eabcd70384 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap +import android.net.Uri import org.rekotlin.Action sealed class OnboardingWalletAction : Action { @@ -13,8 +14,8 @@ sealed class OnboardingWalletAction : Action { object ResumeBackup : OnboardingWalletAction() - object LoadArtwork : OnboardingWalletAction() - class SetArtworkUrl(val artworkUrl: String?) : OnboardingWalletAction() + data class LoadArtwork(val cardArtworkUriForUnfinishedBackup: Uri? = null) : OnboardingWalletAction() + class SetArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction() object OnBackPressed : OnboardingWalletAction() } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 939236339c..e240879094 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -1,15 +1,16 @@ package com.tangem.tap.features.onboarding.products.wallet.redux +import android.net.Uri import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.extensions.ifNotNull +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService import com.tangem.tap.backupService -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -21,6 +22,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction +import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState @@ -36,10 +38,8 @@ import org.rekotlin.Action import org.rekotlin.DispatchFunction import org.rekotlin.Middleware -class OnboardingWalletMiddleware { - companion object { - val handler = onboardingWalletMiddleware - } +object OnboardingWalletMiddleware { + val handler = onboardingWalletMiddleware } private val onboardingWalletMiddleware: Middleware = { dispatch, state -> @@ -51,6 +51,7 @@ private val onboardingWalletMiddleware: Middleware = { dispatch, state } } +@Suppress("LongMethod", "ComplexMethod") private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: DispatchFunction) { if (action !is OnboardingWalletAction) return @@ -80,11 +81,11 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> { when { // check for unfinished backup for saltPay cards. See more - scanResponse.isSaltPay() && backupService.hasIncompletedBackup -> { + scanResponse.cardTypesResolver.isSaltPay() && backupService.hasIncompletedBackup -> { store.dispatch(OnboardingWalletAction.ResumeBackup) } - scanResponse.isSaltPay() -> { + scanResponse.cardTypesResolver.isSaltPay() -> { store.dispatch(OnboardingWalletAction.GetToSaltPayStep) store.dispatch(BackupAction.FinishBackup) } @@ -100,11 +101,11 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } is OnboardingWalletAction.LoadArtwork -> { scope.launch { - val artwork = onboardingManager?.loadArtworkUrl() - val cardArtwork = if (artwork == Artwork.DEFAULT_IMG_URL) { - null - } else { - artwork + val cardArtwork = when (onboardingManager) { + null -> action.cardArtworkUriForUnfinishedBackup + else -> onboardingManager.loadArtworkUrl() + .takeIf { it != Artwork.DEFAULT_IMG_URL } + ?.let { Uri.parse(it) } } store.dispatchOnMain(OnboardingWalletAction.SetArtworkUrl(cardArtwork)) } @@ -117,23 +118,22 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: when (result) { is CompletionResult.Success -> { Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) - //here we must use updated scanResponse after createWallet & derivation + // here we must use updated scanResponse after createWallet & derivation val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy( card = result.data.card, derivedKeys = result.data.derivedKeys, primaryCard = result.data.primaryCard, ) onboardingManager.scanResponse = updatedResponse - val blockchainNetworks = listOf( - BlockchainNetwork( - blockchain = Blockchain.Bitcoin, - card = result.data.card, - ), - BlockchainNetwork( - blockchain = Blockchain.Ethereum, - card = result.data.card, - ), - ) + + val blockchainNetworks = if (DemoHelper.isDemoCardId(result.data.card.cardId)) { + DemoHelper.config.demoBlockchains + } else { + listOf(Blockchain.Bitcoin, Blockchain.Ethereum) + }.map { blockchain -> + BlockchainNetwork(blockchain, result.data.card) + } + store.dispatch( WalletAction.MultiWallet.SaveCurrencies( blockchainNetworks = blockchainNetworks, @@ -188,11 +188,13 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: newAction?.let { store.dispatch(it) } } OnboardingWalletAction.OnBackPressed -> handleOnBackPressed(onboardingWalletState) + else -> {} } } private fun updateScanResponseAfterBackup( - scanResponse: ScanResponse, backupState: BackupState, + scanResponse: ScanResponse, + backupState: BackupState, ): ScanResponse { val card = if (backupState.backupCardsNumber > 0) { val cardsCount = backupState.backupCardsNumber @@ -217,6 +219,7 @@ class BackupMiddleware { } } +@Suppress("LongMethod", "ComplexMethod", "MagicNumber") private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) { if (DemoHelper.tryHandle(appState, action)) return val globalState = appState()?.globalState ?: return @@ -347,7 +350,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.ResumeFoundUnfinishedBackup -> { - store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup) + store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup(backupService.addedBackupCardsCount)) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } is BackupAction.DismissBackup -> { @@ -355,6 +358,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) if (onboardingWalletState.isSaltPay) throw UnsupportedOperationException() store.dispatch(BackupAction.FinishBackup) } + else -> {} } } @@ -394,7 +398,7 @@ private fun initSaltPayOnBackupFinishedIfNeeded( onboardingWalletState: OnboardingWalletState, ) { if (onboardingWalletState.isSaltPay && onboardingWalletState.onboardingSaltPayState == null) { - if (scanResponse == null) throw IllegalArgumentException() + if (scanResponse == null) error("scanning response is null") val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 8cca79e9dd..68099fff3f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -7,10 +7,8 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.Onboardi import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayReducer import org.rekotlin.Action -class OnboardingWalletReducer { - companion object { - fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state) - } +object OnboardingWalletReducer { + fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state) } private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState { @@ -29,110 +27,86 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet ) is OnboardingWalletAction.ResumeBackup -> state.copy(step = OnboardingWalletStep.Backup) is OnboardingWalletAction.SetArtworkUrl -> { - state.copy(cardArtworkUrl = action.artworkUrl) + state.copy(cardArtworkUri = action.artworkUri) } is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done) else -> state } } -private class ReducerForGlobalAction { - companion object { - fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState { - return when (action) { - is GlobalAction.Onboarding.Start -> { - OnboardingWalletState( - backupState = state.backupState.copy( - maxBackupCards = if (action.scanResponse.isSaltPay()) 1 else 2, - canSkipBackup = if (action.scanResponse.isSaltPay()) false else action.canSkipBackup, - ), - isSaltPay = action.scanResponse.isSaltPay(), - ) - } - is GlobalAction.Onboarding.StartForUnfinishedBackup -> { - OnboardingWalletState( - backupState = state.backupState.copy( - maxBackupCards = 2, - canSkipBackup = false, - ), - isSaltPay = false, - ) - } - else -> state +private object ReducerForGlobalAction { + fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState { + return when (action) { + is GlobalAction.Onboarding.Start -> { + OnboardingWalletState( + backupState = state.backupState.copy( + maxBackupCards = if (action.scanResponse.cardTypesResolver.isSaltPay()) 1 else 2, + canSkipBackup = + if (action.scanResponse.cardTypesResolver.isSaltPay()) false else action.canSkipBackup, + ), + isSaltPay = action.scanResponse.cardTypesResolver.isSaltPay(), + ) } + is GlobalAction.Onboarding.StartForUnfinishedBackup -> { + OnboardingWalletState( + backupState = state.backupState.copy( + maxBackupCards = action.addedBackupCardsCount, + canSkipBackup = false, + ), + isSaltPay = false, + ) + } + else -> state } } } -private class BackupReducer { - companion object { - fun reduce( - action: BackupAction, - state: BackupState, - isSaltPay: Boolean, - ): BackupState { - - return when (action) { - is BackupAction.IntroduceBackup -> BackupState( - backupStep = BackupStep.InitBackup, - maxBackupCards = if (isSaltPay) 1 else 2, - canSkipBackup = state.canSkipBackup, +private object BackupReducer { + @Suppress("ComplexMethod") + fun reduce(action: BackupAction, state: BackupState, isSaltPay: Boolean): BackupState { + return when (action) { + is BackupAction.IntroduceBackup -> BackupState( + backupStep = BackupStep.InitBackup, + maxBackupCards = if (isSaltPay) 1 else 2, + canSkipBackup = state.canSkipBackup, + ) + BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard) + BackupAction.StartAddingBackupCards -> state.copy(backupStep = BackupStep.AddBackupCards) + BackupAction.AddBackupCard.Success -> state.copy(backupCardsNumber = state.backupCardsNumber + 1) + BackupAction.ShowAccessCodeInfoScreen -> state.copy(backupStep = BackupStep.SetAccessCode) + BackupAction.ShowEnterAccessCodeScreen -> state.copy( + backupStep = BackupStep.EnterAccessCode, + accessCodeError = null, + ) + is BackupAction.SaveFirstAccessCode -> state.copy( + backupStep = BackupStep.ReenterAccessCode, + accessCode = action.accessCode, + ) + is BackupAction.SetAccessCodeError -> state.copy(accessCodeError = action.error) + is BackupAction.PrepareToWritePrimaryCard -> if (state.primaryCardId == null) { + state.copy( + primaryCardId = backupService.primaryCardId, + backupCardIds = backupService.backupCardIds, + backupCardsNumber = backupService.backupCardIds.size, + backupStep = BackupStep.WritePrimaryCard, ) - BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard) - BackupAction.StartAddingBackupCards -> { - state.copy(backupStep = BackupStep.AddBackupCards) - } - BackupAction.AddBackupCard.Success -> { - state.copy(backupCardsNumber = state.backupCardsNumber + 1) - } - BackupAction.ShowAccessCodeInfoScreen -> { - state.copy(backupStep = BackupStep.SetAccessCode) - } - BackupAction.ShowEnterAccessCodeScreen -> { - state.copy(backupStep = BackupStep.EnterAccessCode, accessCodeError = null) - } - is BackupAction.SaveFirstAccessCode -> { - state.copy( - backupStep = BackupStep.ReenterAccessCode, - accessCode = action.accessCode, - ) - } - is BackupAction.SetAccessCodeError -> { - state.copy(accessCodeError = action.error) - } - is BackupAction.PrepareToWritePrimaryCard -> { - if (state.primaryCardId == null) { - state.copy( - primaryCardId = backupService.primaryCardId, - backupCardIds = backupService.backupCardIds, - backupCardsNumber = backupService.backupCardIds.size, - backupStep = BackupStep.WritePrimaryCard, - ) - } else { - state.copy(backupStep = BackupStep.WritePrimaryCard) - } - } - is BackupAction.PrepareToWriteBackupCard -> { - if (state.primaryCardId == null) { - state.copy( - primaryCardId = backupService.primaryCardId, - backupCardIds = backupService.backupCardIds, - backupCardsNumber = backupService.backupCardIds.size, - backupStep = BackupStep.WriteBackupCard(action.cardNumber), - ) - } else { - state.copy(backupStep = BackupStep.WriteBackupCard(action.cardNumber)) - } - } - is BackupAction.FinishBackup -> { - state.copy(backupStep = BackupStep.Finished) - } - BackupAction.OnAccessCodeDialogClosed -> { - state.copy(backupStep = BackupStep.AddBackupCards) - } - BackupAction.DiscardBackup -> BackupState() - else -> state + } else { + state.copy(backupStep = BackupStep.WritePrimaryCard) } + is BackupAction.PrepareToWriteBackupCard -> if (state.primaryCardId == null) { + state.copy( + primaryCardId = backupService.primaryCardId, + backupCardIds = backupService.backupCardIds, + backupCardsNumber = backupService.backupCardIds.size, + backupStep = BackupStep.WriteBackupCard(action.cardNumber), + ) + } else { + state.copy(backupStep = BackupStep.WriteBackupCard(action.cardNumber)) + } + is BackupAction.FinishBackup -> state.copy(backupStep = BackupStep.Finished) + BackupAction.OnAccessCodeDialogClosed -> state.copy(backupStep = BackupStep.AddBackupCards) + BackupAction.DiscardBackup -> BackupState() + else -> state } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 4e3cad3657..64af52ecac 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -1,8 +1,8 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap +import android.net.Uri import com.tangem.common.CardFilter -import com.tangem.common.extensions.VoidCallback import com.tangem.domain.common.SaltPayWorkaround import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState @@ -17,7 +17,7 @@ data class OnboardingWalletState( val backupState: BackupState = BackupState(), val onboardingSaltPayState: OnboardingSaltPayState? = null, val isSaltPay: Boolean = false, - val cardArtworkUrl: String? = null, + val cardArtworkUri: Uri? = null, val showConfetti: Boolean = false, ) : StateType { @@ -30,11 +30,13 @@ data class OnboardingWalletState( else -> null } + @Suppress("MagicNumber") fun getMaxProgress(): Int = when { isSaltPay -> 12 else -> 6 } + @Suppress("ComplexMethod", "MagicNumber") fun getProgressStep(): Int { return when { step == OnboardingWalletStep.CreateWallet -> 1 @@ -105,10 +107,6 @@ sealed class BackupStep { object Finished : BackupStep() } -sealed class OnboardingDialog : StateDialog { - data class InterruptOnboarding(val onOk: VoidCallback) : BackupDialog() -} - sealed class BackupDialog : StateDialog { object AddMoreBackupCards : BackupDialog() object BackupInProgress : BackupDialog() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index ddf80bdc07..8882281ca9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -164,11 +164,15 @@ class GnosisRegistrator( return Result.Success(Unit) } + @Suppress("MagicNumber") private fun Result>.extractFeeAmount(): Result { return when (this) { is Result.Success -> { - if (this.data.size != 3) Result.Failure(BlockchainSdkError.FailedToLoadFee) - else Result.Success(this.data[1]) + if (this.data.size != 3) { + Result.Failure(BlockchainSdkError.FailedToLoadFee) + } else { + Result.Success(this.data[1]) + } } is Result.Failure -> this } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt index 15a4137ec0..fd359c25a4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt @@ -5,9 +5,9 @@ import com.tangem.common.Filter /** [REDACTED_AUTHOR] */ -abstract class PinFilter : Filter +interface PinFilter : Filter -class AllSymbolsTheSameFilter : PinFilter() { +class AllSymbolsTheSameFilter : PinFilter { override fun filter(value: String): Boolean { if (value.isEmpty()) return false diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt index ff6ec003d6..e99c85ae58 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt @@ -10,21 +10,29 @@ import com.tangem.blockchain.extensions.successOr import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.extensions.isZero +import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result -import com.tangem.datasource.api.paymentology.AttestationResponse +import com.tangem.common.services.performRequest import com.tangem.datasource.api.paymentology.PaymentologyApiService -import com.tangem.datasource.api.paymentology.RegisterKYCRequest -import com.tangem.datasource.api.paymentology.RegisterWalletRequest -import com.tangem.datasource.api.paymentology.RegisterWalletResponse -import com.tangem.datasource.api.paymentology.RegistrationResponse -import com.tangem.datasource.api.paymentology.tryExtractError +import com.tangem.datasource.api.paymentology.models.request.CheckRegistrationRequests +import com.tangem.datasource.api.paymentology.models.request.RegisterKYCRequest +import com.tangem.datasource.api.paymentology.models.request.RegisterWalletRequest +import com.tangem.datasource.api.paymentology.models.response.AttestationResponse +import com.tangem.datasource.api.paymentology.models.response.RegisterWalletResponse +import com.tangem.datasource.api.paymentology.models.response.RegistrationResponse +import com.tangem.datasource.api.paymentology.models.response.tryExtractError +import com.tangem.datasource.config.models.KYCProvider +import com.tangem.datasource.config.models.SaltPayConfig import com.tangem.domain.common.extensions.successOr import com.tangem.operations.attestation.AttestWalletKeyResponse import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.math.BigDecimal +import com.tangem.blockchain.extensions.Result as BlockchainResult /** [REDACTED_AUTHOR] @@ -47,24 +55,34 @@ class SaltPayActivationManager( suspend fun checkHasGas(): Result { return when (val hasGasResult = gnosisRegistrator.checkHasGas()) { - is com.tangem.blockchain.extensions.Result.Success -> if (hasGasResult.data) { + is BlockchainResult.Success -> if (hasGasResult.data) { Result.Success(Unit) } else { Result.Failure(SaltPayActivationError.NoGas) } - is com.tangem.blockchain.extensions.Result.Failure -> Result.Failure(hasGasResult.error as BlockchainSdkError) + is BlockchainResult.Failure -> Result.Failure(hasGasResult.error as BlockchainSdkError) } } suspend fun registerKYC(): Result { - return when (val result = paymentologyService.registerKYC(makeRegisterKYCRequest())) { + val result = withContext(Dispatchers.IO) { + performRequest { paymentologyService.api.registerKYC(makeRegisterKYCRequest()) } + } + return when (result) { is Result.Success -> Result.Success(Unit) is Result.Failure -> result } } suspend fun checkRegistration(): Result { - val response: RegistrationResponse = paymentologyService.checkRegistration(cardId, cardPublicKey) + val requestItem = CheckRegistrationRequests.Item(cardId, cardPublicKey.toHexString()) + val request = CheckRegistrationRequests(listOf(requestItem)) + + val response: RegistrationResponse = withContext(Dispatchers.IO) { + performRequest { + paymentologyService.api.checkRegistration(request) + } + } .successOr { return it } .tryExtractError() .successOr { return it } @@ -73,7 +91,7 @@ class SaltPayActivationManager( if (response.results.isEmpty()) throw SaltPayActivationError.EmptyResponse val item = response.results[0] - if (item.error != null) throw Exception(response.makeErrorMessage()) + if (item.error != null) throw IllegalStateException(response.makeErrorMessage()) Result.Success(item) } catch (ex: Exception) { @@ -82,14 +100,19 @@ class SaltPayActivationManager( } suspend fun requestAttestationChallenge(): Result { - return paymentologyService.requestAttestationChallenge(cardId, cardPublicKey) + val requestItem = CheckRegistrationRequests.Item(cardId, cardPublicKey.toHexString()) + return withContext(Dispatchers.IO) { + performRequest { + paymentologyService.api.requestAttestationChallenge(requestItem) + } + } .successOr { return it } .tryExtractError() } suspend fun sendTransactions( signedTransactions: List, - ): com.tangem.blockchain.extensions.Result> { + ): BlockchainResult> { return gnosisRegistrator.sendTransactions(signedTransactions) } @@ -107,7 +130,11 @@ class SaltPayActivationManager( cardSignature = attestResponse.cardSignature ?: byteArrayOf(), pin = pinCode, ) - return paymentologyService.registerWallet(request) + return withContext(Dispatchers.IO) { + performRequest { + paymentologyService.api.registerWallet(request) + } + } .successOr { return it } .tryExtractError() } @@ -171,7 +198,7 @@ class SaltPayActivationManager( companion object { fun stub(): SaltPayActivationManager = SaltPayActivationManager( kycProvider = SaltPayConfig.stub().kycProvider, - paymentologyService = PaymentologyApiService.stub(), + paymentologyService = PaymentologyApiService, gnosisRegistrator = GnosisRegistrator.stub(), cardId = "", cardPublicKey = byteArrayOf(), diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt index 0f0b207431..6236bf847e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -11,35 +11,33 @@ import com.tangem.tap.store /** [REDACTED_AUTHOR] */ -class SaltPayExceptionHandler { - companion object { - fun handle(throwable: Throwable) { - when (throwable) { - is SaltPayActivationError -> { - val dialog = when (throwable) { - is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas - is SaltPayActivationError.PutVisaCard -> SaltPayDialog.Activation.PutVisaCard - else -> SaltPayDialog.Activation.OnError(throwable) +object SaltPayExceptionHandler { + fun handle(throwable: Throwable) { + when (throwable) { + is SaltPayActivationError -> { + val dialog = when (throwable) { + is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas + is SaltPayActivationError.PutVisaCard -> SaltPayDialog.Activation.PutVisaCard + else -> SaltPayDialog.Activation.OnError(throwable) + } + store.dispatchDialogShow(dialog) + } + is TangemSdkError -> { + when (throwable) { + is TangemSdkError.NetworkError -> { + val message = throwable.customMessage + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) } - store.dispatchDialogShow(dialog) - } - is TangemSdkError -> { - when (throwable) { - is TangemSdkError.NetworkError -> { - val message = (throwable).customMessage - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) - } - else -> Unit - } - } - is BlockchainSdkError -> { - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(throwable.customMessage)) - } - else -> { - val message = throwable.localizedMessage ?: "SaltPay unknown error" - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + else -> Unit } } + is BlockchainSdkError -> { + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(throwable.customMessage)) + } + else -> { + val message = throwable.localizedMessage ?: "SaltPay unknown error" + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt index b773adcb70..2a33de86f4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt @@ -64,7 +64,9 @@ class UtorgWebChromeClient( if (!readyToShowFileChooser()) return false val type: String - if (fileChooserParams != null && fileChooserParams.acceptTypes != null && fileChooserParams.acceptTypes.isNotEmpty()) { + if (fileChooserParams != null && fileChooserParams.acceptTypes != null && + fileChooserParams.acceptTypes.isNotEmpty() + ) { type = if (!TextUtils.isEmpty(fileChooserParams.acceptTypes[0])) fileChooserParams.acceptTypes[0] else "*/*" this.filePathCallback = filePathCallback } else { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt index 1636675ae1..36827b96a4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt @@ -4,24 +4,22 @@ import android.app.Dialog import android.content.Context import androidx.appcompat.app.AlertDialog import com.tangem.tap.common.extensions.dispatchDialogHide -import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingDialog +import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.store import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class InterruptOnboardingDialog { - companion object { - fun create(context: Context, dialog: OnboardingDialog.InterruptOnboarding): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.onboarding_exit_alert_title)) - setMessage(context.getString(R.string.onboarding_exit_alert_message)) - setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() } - setNegativeButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatchDialogHide() } - setCancelable(false) - }.create() - } +object InterruptOnboardingDialog { + fun create(context: Context, dialog: OnboardingDialog.InterruptOnboarding): Dialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.onboarding_exit_alert_title)) + setMessage(context.getString(R.string.onboarding_exit_alert_message)) + setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() } + setNegativeButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatchDialogHide() } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt index e1c2021b61..8280d87aab 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt @@ -13,26 +13,24 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class NoFundsForActivationDialog { - companion object { - fun create(context: Context): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.saltpay_error_no_gas_title) - setMessage(R.string.saltpay_error_no_gas_message) - setPositiveButton(R.string.chat_button_title) { _, _ -> - val config = store.state.globalState.configManager?.config?.saltPayConfig?.zendesk.guard { - store.dispatchDebugErrorNotification("SaltPayConfig not initialized") - return@setPositiveButton - } - store.dispatch(GlobalAction.OpenChat(SupportInfo(), config)) +object NoFundsForActivationDialog { + fun create(context: Context): Dialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.saltpay_error_no_gas_title) + setMessage(R.string.saltpay_error_no_gas_message) + setPositiveButton(R.string.chat_button_title) { _, _ -> + val config = store.state.globalState.configManager?.config?.saltPayConfig?.zendesk.guard { + store.dispatchDebugErrorNotification("SaltPayConfig not initialized") + return@setPositiveButton } - setNegativeButton(R.string.common_cancel) { _, _ -> - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } + store.dispatch(GlobalAction.OpenChat(SupportInfo(), config)) + } + setNegativeButton(R.string.common_cancel) { _, _ -> + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt index ba689a1449..d3daf798fe 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt @@ -10,18 +10,16 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class PutVisaCardDialog { - companion object { - fun create(context: Context): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.saltpay_error_empty_backup_title) - setMessage(R.string.saltpay_error_empty_backup_message) - setPositiveButton(R.string.common_ok) { _, _ -> } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } +object PutVisaCardDialog { + fun create(context: Context): Dialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.saltpay_error_empty_backup_title) + setMessage(R.string.saltpay_error_empty_backup_message) + setPositiveButton(R.string.common_ok) { _, _ -> } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt index 831d097783..f3c184157f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt @@ -12,22 +12,20 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class RegistrationErrorDialog { - companion object { - fun create(context: Context, dialog: SaltPayDialog.Activation.OnError): Dialog { - val convertedMessage = SaltPayErrorConverter(context).convert(dialog.error) as ConvertedDialogMessage +object RegistrationErrorDialog { + fun create(context: Context, dialog: SaltPayDialog.Activation.OnError): Dialog { + val convertedMessage = SaltPayErrorConverter(context).convert(dialog.error) as ConvertedDialogMessage - return AlertDialog.Builder(context).apply { - setTitle(convertedMessage.title) - setMessage(convertedMessage.message) - setPositiveButton(R.string.common_ok) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } + return AlertDialog.Builder(context).apply { + setTitle(convertedMessage.title) + setMessage(convertedMessage.message) + setPositiveButton(R.string.common_ok) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt index 6097e52cc2..c6e8d9526e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt @@ -28,29 +28,27 @@ sealed class SaltPayActivationError( subCode = ERROR_CODE_REGISTRATION + subCode, message = message ?: EMPTY_MESSAGE, ) { - object Unknown : SaltPayActivationError(4) - object Empty : SaltPayActivationError(4) + object Unknown : SaltPayActivationError(subCode = 4) + object Empty : SaltPayActivationError(subCode = 4) // gnosis - object FailedToMakeTxData : SaltPayActivationError(1) - object FailedToSendTx : SaltPayActivationError(2) + object FailedToMakeTxData : SaltPayActivationError(subCode = 1) + object FailedToSendTx : SaltPayActivationError(subCode = 2) - object NeedPin : SaltPayActivationError(3) - object NoGas : SaltPayActivationError(5) // - object EmptyResponse : SaltPayActivationError(6) - object CardNotFound : SaltPayActivationError(8) - class CardDisabled(message: String?) : SaltPayActivationError(9, message) - class CardNotPassed(message: String?) : SaltPayActivationError(10, message) - object EmptyDynamicAttestResponse : SaltPayActivationError(11) - object EmptyBackupCardScanned : SaltPayActivationError(12) - object WeakPin : SaltPayActivationError(13) + object NeedPin : SaltPayActivationError(subCode = 3) + object NoGas : SaltPayActivationError(subCode = 5) + object EmptyResponse : SaltPayActivationError(subCode = 6) + object CardNotFound : SaltPayActivationError(subCode = 8) + class CardDisabled(message: String?) : SaltPayActivationError(subCode = 9, message = message) + class CardNotPassed(message: String?) : SaltPayActivationError(subCode = 10, message = message) + object EmptyDynamicAttestResponse : SaltPayActivationError(subCode = 11) + object EmptyBackupCardScanned : SaltPayActivationError(subCode = 12) + object WeakPin : SaltPayActivationError(subCode = 13) - object FailedToGetFundsToClaim : SaltPayActivationError(14) - object NoFundsToClaim : SaltPayActivationError(15) - object ClaimTransactionFailed : SaltPayActivationError(16) - object NotClaimed : SaltPayActivationError(17) - - object PutVisaCard : SaltPayActivationError(18) + object FailedToGetFundsToClaim : SaltPayActivationError(subCode = 14) + object NoFundsToClaim : SaltPayActivationError(subCode = 15) + object ClaimTransactionFailed : SaltPayActivationError(subCode = 16) + object PutVisaCard : SaltPayActivationError(subCode = 18) companion object { const val EMPTY_MESSAGE = "" diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt index a2194e9568..ea8b58f85c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux import com.tangem.blockchain.common.Amount import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig +import com.tangem.datasource.config.models.SaltPayConfig import org.rekotlin.Action import java.math.BigDecimal @@ -36,4 +36,4 @@ sealed class OnboardingSaltPayAction : Action { data class SetStep(val newStep: SaltPayActivationStep) : OnboardingSaltPayAction() data class SetAmountToClaim(val amount: Amount?) : OnboardingSaltPayAction() data class SetTokenBalance(val balanceValue: BigDecimal) : OnboardingSaltPayAction() -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index dfdfb619b5..90ebec0825 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -8,8 +8,8 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics -import com.tangem.datasource.api.paymentology.KYCStatus -import com.tangem.datasource.api.paymentology.RegistrationResponse +import com.tangem.datasource.api.paymentology.models.response.KYCStatus +import com.tangem.datasource.api.paymentology.models.response.RegistrationResponse import com.tangem.domain.common.extensions.successOr import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.analytics.events.Onboarding @@ -40,10 +40,8 @@ import timber.log.Timber /** [REDACTED_AUTHOR] */ -class OnboardingSaltPayMiddleware { - companion object { - val handler = onboardingSaltPayMiddleware - } +object OnboardingSaltPayMiddleware { + val handler = onboardingSaltPayMiddleware } private val onboardingSaltPayMiddleware: Middleware = { dispatch, state -> @@ -55,6 +53,7 @@ private val onboardingSaltPayMiddleware: Middleware = { dispatch, stat } } +@Suppress("LongMethod", "ComplexMethod") private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> AppState?) { if (DemoHelper.tryHandle(appState, anyAction)) return val action = anyAction as? OnboardingSaltPayAction ?: return @@ -298,6 +297,7 @@ suspend fun SaltPayActivationManager.update( return Result.Success(newStep) } +@Suppress("ComplexMethod") private fun determineStep( currentStep: SaltPayActivationStep, amountToClaim: Amount?, @@ -418,34 +418,6 @@ private suspend fun checkGasIfNeeded( return result } -@Throws(SaltPayActivationError::class) -fun RegistrationResponse.Item.toSaltPayStep(currentStep: SaltPayActivationStep): SaltPayActivationStep { - return when { - passed != true -> throw SaltPayActivationError.CardNotPassed(this.error) - disabledByAdmin == true -> throw SaltPayActivationError.CardDisabled(this.error) - - // go to claim screen - active == true -> SaltPayActivationStep.Claim - - // pinSet is false, go toPin screen - pinSet == false -> SaltPayActivationStep.NeedPin - - kycStatus != null -> { - when (kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - KYCStatus.APPROVED -> SaltPayActivationStep.Claim - null -> throw UnsupportedOperationException() - } - } - // kycDate is set, go to kyc waiting screen - kycDate != null -> SaltPayActivationStep.KycWaiting - - else -> SaltPayActivationStep.KycIntro - } -} - @Throws(SaltPayActivationError::class) private fun assertPinValid(pin: String, pinLength: Int) { val array = pin.toCharArray() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt index 71570fd52e..9d4e8774c0 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt @@ -6,14 +6,13 @@ import org.rekotlin.Action /** [REDACTED_AUTHOR] */ -class OnboardingSaltPayReducer { - companion object { - fun reduce(action: Action, state: OnboardingWalletState): OnboardingWalletState { - return internalReduce(action, state) - } +object OnboardingSaltPayReducer { + fun reduce(action: Action, state: OnboardingWalletState): OnboardingWalletState { + return internalReduce(action, state) } } +@Suppress("ComplexMethod") private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingWalletState): OnboardingWalletState { val action = anyAction as? OnboardingSaltPayAction ?: return onboardingWalletState diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt index a14ad10252..344834807f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt @@ -4,15 +4,15 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.extensions.guard +import com.tangem.datasource.api.paymentology.PaymentologyApiService import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.ScanResponse -import com.tangem.datasource.api.paymentology.PaymentologyApiService import com.tangem.tap.common.toggleWidget.WidgetState import com.tangem.tap.domain.extensions.makeSaltPayWalletManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.GnosisRegistrator -import com.tangem.tap.features.onboarding.products.wallet.saltPay.KYCProvider +import com.tangem.datasource.config.models.KYCProvider import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig +import com.tangem.datasource.config.models.SaltPayConfig import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.store import java.math.BigDecimal @@ -37,8 +37,6 @@ data class OnboardingSaltPayState( val mainButtonState: WidgetState get() = if (inProgress) ProgressState.Loading else ProgressState.Done - fun readyToClaim(): Boolean = amountToClaim != null - val pinLength: Int = 4 companion object { @@ -66,7 +64,7 @@ data class OnboardingSaltPayState( paymentologyService: PaymentologyApiService, kycProvider: KYCProvider, ): SaltPayActivationManager { - if (!scanResponse.isSaltPay()) { + if (!scanResponse.cardTypesResolver.isSaltPay()) { throw IllegalArgumentException("Can't initialize the OnboardingSaltPayMiddleware if card is not SalPay") } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt index 84e012cad9..d4549041e1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.ui +import android.net.Uri import android.view.View import android.view.ViewGroup import android.view.animation.OvershootInterpolator @@ -62,7 +63,9 @@ internal class OnboardingSaltPayView( private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar } private val claimBinding: LayoutOnboardingMainBinding by lazy { walletFragment.bindingSaltPay.claim } private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer } - private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { claimBinding.onboardingActionContainer } + private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { + claimBinding.onboardingActionContainer + } private val btnRefreshBalanceWidget by lazy { RefreshBalanceWidget(claimBinding.onboardingTopContainer.onboardingWalletContainer) @@ -90,14 +93,15 @@ internal class OnboardingSaltPayView( } private fun handleCardArtworks(state: OnboardingWalletState) = with(walletFragment.binding) { - //TODO: SaltPay: remove hardCode + // TODO: SaltPay: remove hardCode if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { imvFrontCard.load(R.drawable.img_salt_pay_visa) { scale(Scale.FILL) crossfade(enable = true) } } else { - walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) + val uri = Uri.parse(state.onboardingSaltPayState.saltPayCardArtworkUrl) + walletFragment.loadImageIntoImageView(uri, imvFrontCard) } imvFirstBackupCard.load(R.drawable.img_salt_pay_visa) @@ -145,7 +149,8 @@ internal class OnboardingSaltPayView( SaltPayActivationStep.Claim -> handleClaim(state) SaltPayActivationStep.ClaimInProgress -> handleClaim(state) SaltPayActivationStep.ClaimSuccess -> handleClaim(state) - SaltPayActivationStep.Success -> handleSuccess(state) + SaltPayActivationStep.Success -> handleSuccess() + else -> Unit } progressButton?.changeState(state.mainButtonState) } @@ -249,6 +254,7 @@ internal class OnboardingSaltPayView( progressButton = SaltPayProgressButton(root) } + @Suppress("LongMethod") private fun handleClaim(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { toolbar.title = getText(R.string.onboarding_getting_started) val btnMain = actionContainer.btnContainer.findViewById(R.id.btn_main_action) @@ -322,7 +328,7 @@ internal class OnboardingSaltPayView( } } - private fun handleSuccess(state: OnboardingSaltPayState) = with(walletFragment.binding) { + private fun handleSuccess() = with(walletFragment.binding) { walletFragment.bindingSaltPay.onboardingSaltpayContainer.hide() onboardingWalletContainer.show() walletFragment.showSuccess() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt index 66f90f159c..368cb6a3a3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt @@ -34,6 +34,8 @@ class WalletBackupAnimator( } private var currentStep = -1 + private var firstBackupCardAnimated = false + private var secondBackupCardAnimated = false override fun updateBackupState(backupState: BackupState) { } @@ -64,6 +66,7 @@ class WalletBackupAnimator( cardsWidget.toFolded() } + @Suppress("MagicNumber") override fun showAddBackupCards(state: BackupState, backupCardsCounts: Int) { when (backupCardsCounts) { 0 -> { @@ -101,6 +104,7 @@ class WalletBackupAnimator( currentStep = STEP_ADD_BACKUP_CARDS } + @Suppress("MagicNumber") override fun showWritePrimaryCard(state: BackupState) { currentStep = STEP_WRITE_PRIMARY_CARD @@ -112,9 +116,7 @@ class WalletBackupAnimator( ) } - private var firstBackupCardAnimated = false - private var secondBackupCardAnimated = false - + @Suppress("MagicNumber") override fun showWriteBackupCard(state: BackupState, backupCard: Int) { when (viewState) { State.TwoCards -> { @@ -134,6 +136,7 @@ class WalletBackupAnimator( 1 -> { if (firstBackupCardAnimated) return firstBackupCardAnimated = true + FIRST_BACKUP.alpha(1f) cardsWidget.leapfrogWidget.leap { SECOND_BACKUP.alpha(0.4f) @@ -143,6 +146,7 @@ class WalletBackupAnimator( 2 -> { if (secondBackupCardAnimated) return secondBackupCardAnimated = true + SECOND_BACKUP.alpha(1f) cardsWidget.leapfrogWidget.leap { ORIGIN.alpha(0.4f) @@ -242,6 +246,7 @@ class TestBackupAnimation( init(binding, this) } + @Suppress("MagicNumber") fun setStep(step: Int, onStepUpdate: (Int) -> Unit = {}) { steps = step when (steps) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt index 3388e72a96..5b62611410 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt @@ -15,7 +15,9 @@ class BackupInfoAdapter : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupInfoViewHolder = BackupInfoViewHolder( ItemBackupInfoAdapterBinding.inflate( - LayoutInflater.from(parent.context), parent, false + LayoutInflater.from(parent.context), + parent, + false ) ) @@ -51,7 +53,7 @@ private val backupInfoSnippets = listOf( R.string.onboarding_wallet_info_subtitle_fourth ), - ) +) class BackupInfoViewHolder(val binding: ItemBackupInfoAdapterBinding) : RecyclerView.ViewHolder(binding.root) \ No newline at end of file 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 2dfed0997d..d2202cc249 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 @@ -1,5 +1,6 @@ package com.tangem.tap.features.onboarding.products.wallet.ui +import android.net.Uri import android.os.Bundle import android.util.TypedValue import android.view.View @@ -17,12 +18,13 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.tabs.TabLayoutMediator import com.tangem.common.CardIdFormatter import com.tangem.common.core.CardIdDisplayFormat +import com.tangem.core.analytics.Analytics import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.inflate +import com.tangem.tap.common.extensions.resourceUri import com.tangem.tap.common.extensions.show import com.tangem.tap.features.BaseFragment import com.tangem.tap.features.FragmentOnBackPressedHandler @@ -44,6 +46,7 @@ import com.tangem.wallet.databinding.LayoutOnboardingSaltpayBinding import com.tangem.wallet.databinding.ViewOnboardingProgressBinding import org.rekotlin.StoreSubscriber +@Suppress("LargeClass", "MagicNumber") class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_wallet), StoreSubscriber, FragmentOnBackPressedHandler { @@ -75,7 +78,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle binding.tabLayoutBackupInfo, binding.viewPagerBackupInfo, ) { tab, position -> - //Some implementation + // Some implementation }.attach() (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar) @@ -85,12 +88,16 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle store.dispatch(OnboardingWalletAction.Init) store.dispatch(OnboardingSaltPayAction.Init) - store.dispatch(OnboardingWalletAction.LoadArtwork) + store.dispatch( + OnboardingWalletAction.LoadArtwork( + cardArtworkUriForUnfinishedBackup = requireContext().resourceUri(R.drawable.card_placeholder_wallet), + ), + ) } override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider() - private fun reInitCardsWidget(backupCardsCounts: Int) = with(binding) { + private fun reInitCardsWidgetIfNeeded(backupCardsCounts: Int) = with(binding) { val viewBackupCount = flCardsContainer.childCount - 1 if (viewBackupCount <= 0) return@with if (viewBackupCount == backupCardsCounts) return@with @@ -142,7 +149,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle override fun newState(state: OnboardingWalletState) { if (activity == null || view == null) return - if (state.isSaltPay) reInitCardsWidget(1) + if (state.isSaltPay) reInitCardsWidgetIfNeeded(1) animator.updateBackupState(state.backupState) requireActivity().invalidateOptionsMenu() @@ -153,15 +160,15 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle if (state.isSaltPay) { onboardingSaltPayView.newState(state) } else { - loadImageIntoImageView(state.cardArtworkUrl, binding.imvFrontCard) - loadImageIntoImageView(state.cardArtworkUrl, binding.imvFirstBackupCard) - loadImageIntoImageView(state.cardArtworkUrl, binding.imvSecondBackupCard) + loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard) + loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard) + loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard) handleOnboardingStep(state) } } - internal fun loadImageIntoImageView(url: String?, view: ImageView) { - view.load(url) { + internal fun loadImageIntoImageView(uri: Uri?, view: ImageView) { + view.load(uri) { placeholder(R.drawable.card_placeholder_black) error(R.drawable.card_placeholder_black) fallback(R.drawable.card_placeholder_black) @@ -327,7 +334,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle private fun showWritePrimaryCard(state: BackupState, isSaltPay: Boolean) = with(binding) { accessCodeDialog?.dismiss() - reInitCardsWidget(state.backupCardsNumber) + reInitCardsWidgetIfNeeded(state.backupCardsNumber) prepareViewForFinalizeStep() val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) @@ -363,6 +370,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle private fun showWriteBackupCard(state: BackupState, isSaltPay: Boolean) = with(binding) { prepareViewForFinalizeStep() + reInitCardsWidgetIfNeeded(state.backupCardsNumber) val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1 if (isSaltPay) { tvHeader.text = getString(R.string.onboarding_saltpay_title_backup_card) @@ -433,4 +441,4 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true) return typedValue.float } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt index 7d4831c434..7385c4d025 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt @@ -88,6 +88,7 @@ class WalletCardsWidget( return getCardTypesTypes().map { createCardAnimator(it, propertyFactory(it)) } } + @Suppress("MagicNumber") private fun getCardTypesTypes(): List = when (leapfrogWidget.getViewsCount()) { 2 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP) 3 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP, BackupCardType.SECOND_BACKUP) @@ -114,6 +115,7 @@ class WalletCardsWidget( return animator } + @Suppress("MagicNumber") private fun createWelcomeProperties(cardType: BackupCardType): CardProperties { return when (cardType) { BackupCardType.ORIGIN -> CardProperties( @@ -140,6 +142,7 @@ class WalletCardsWidget( } } + @Suppress("MagicNumber") private fun createFanProperties(cardType: BackupCardType): CardProperties { return when (cardType) { BackupCardType.ORIGIN -> CardProperties( @@ -166,6 +169,7 @@ class WalletCardsWidget( } } + @Suppress("MagicNumber") private fun createLeapfrogProperties(cardType: BackupCardType): CardProperties { return when (cardType) { BackupCardType.ORIGIN -> CardProperties( @@ -192,17 +196,6 @@ class WalletCardsWidget( } } - private fun createActivateProperties(cardType: BackupCardType): CardProperties { - val topOfAnchorView = getTopOfAnchorViewForActivateState() - val twinProperties = CardProperties.from(getLeapViewByCard(cardType).state) - return twinProperties.copy( - xTranslation = twinProperties.xTranslation, - yTranslation = twinProperties.yTranslation - topOfAnchorView, - rotation = 0f, - scale = twinProperties.scale - 0.4f, - ) - } - @Throws(UnsupportedOperationException::class) fun getOriginCardView(): ImageView { return getLeapViewByCard(BackupCardType.ORIGIN).view as ImageView @@ -218,6 +211,7 @@ class WalletCardsWidget( return getLeapViewByCard(BackupCardType.SECOND_BACKUP).view as ImageView } + @Suppress("MagicNumber") @Throws(UnsupportedOperationException::class) private fun getLeapViewByCard(cardType: BackupCardType): LeapView { fun getByIndex(index: Int): LeapView { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt index c8b5e3ccb0..9244b75bb8 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt @@ -65,7 +65,6 @@ class AccessCodeDialog(context: Context) : BottomSheetDialog(context) { store.dispatch(BackupAction.SaveAccessCodeConfirmation(etAccessCode.text.toString())) } } - } fun showError(error: AccessCodeError?) = with(binding!!.layoutBackupAccessCodeSubmit) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt index d98e9907b9..f8b2c0bf66 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt @@ -7,22 +7,19 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.store import com.tangem.wallet.R -class AddMoreBackupCardsDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.common_warning) - setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added) - setPositiveButton(R.string.common_continue) { _, _ -> - store.dispatch(BackupAction.ShowAccessCodeInfoScreen) - } - setNegativeButton(R.string.common_cancel) { _, _ -> - - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } +object AddMoreBackupCardsDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.common_warning) + setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added) + setPositiveButton(R.string.common_continue) { _, _ -> + store.dispatch(BackupAction.ShowAccessCodeInfoScreen) + } + setNegativeButton(R.string.common_cancel) { _, _ -> + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt index e4fa7525b5..ed4e32a0a4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt @@ -6,19 +6,17 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.store import com.tangem.wallet.R -class BackupInProgressDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.common_warning) - setMessage(R.string.onboarding_backup_exit_warning) - setPositiveButton(R.string.warning_button_ok) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } +object BackupInProgressDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.common_warning) + setMessage(R.string.onboarding_backup_exit_warning) + setPositiveButton(R.string.warning_button_ok) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt index 4d35050937..557c955bb1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ConfirmDiscardingBackupDialog.kt @@ -7,23 +7,21 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.store import com.tangem.wallet.R -class ConfirmDiscardingBackupDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.welcome_interrupted_backup_discard_title) - setMessage(R.string.welcome_interrupted_backup_discard_message) - setPositiveButton(R.string.welcome_interrupted_backup_discard_resume) { _, _ -> - store.dispatch(BackupAction.ResumeFoundUnfinishedBackup) - } - setNegativeButton(R.string.welcome_interrupted_backup_discard_discard) { _, _ -> - store.dispatch(BackupAction.DiscardSavedBackup) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } +object ConfirmDiscardingBackupDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.welcome_interrupted_backup_discard_title) + setMessage(R.string.welcome_interrupted_backup_discard_message) + setPositiveButton(R.string.welcome_interrupted_backup_discard_resume) { _, _ -> + store.dispatch(BackupAction.ResumeFoundUnfinishedBackup) + } + setNegativeButton(R.string.welcome_interrupted_backup_discard_discard) { _, _ -> + store.dispatch(BackupAction.DiscardSavedBackup) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt index d13afaa088..6576a36fce 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt @@ -8,22 +8,20 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog import com.tangem.tap.store import com.tangem.wallet.R -class UnfinishedBackupFoundDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.common_warning) - setMessage(R.string.welcome_interrupted_backup_alert_message) - setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(BackupAction.ResumeFoundUnfinishedBackup) - } - setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup)) - } - setCancelable(false) - }.create() - } +object UnfinishedBackupFoundDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.common_warning) + setMessage(R.string.welcome_interrupted_backup_alert_message) + setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(BackupAction.ResumeFoundUnfinishedBackup) + } + setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup)) + } + setCancelable(false) + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt index 63803d4095..3f0f4f77c4 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -40,8 +40,8 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment Unit, onCloseClick: () -> Unit, ) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Header( - modifier = Modifier.fillMaxWidth(), - onCloseClick = onCloseClick, - ) + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Header(onCloseClick = onCloseClick) SpacerHHalf() Title( modifier = Modifier @@ -62,9 +54,6 @@ internal fun SaveWalletScreenContent( ) SpacerHHalf() Footer( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), showProgress = showProgress, onSaveWalletClick = onSaveWalletClick, ) @@ -73,12 +62,9 @@ internal fun SaveWalletScreenContent( } @Composable -private fun Header( - modifier: Modifier = Modifier, - onCloseClick: () -> Unit, -) { +private fun Header(onCloseClick: () -> Unit) { Column( - modifier = modifier, + modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { Hand() @@ -92,7 +78,7 @@ private fun Header( ) { Icon( modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_close), + painter = painterResource(id = R.drawable.ic_close_24), tint = TangemTheme.colors.icon.secondary, contentDescription = stringResource(id = R.string.common_cancel), ) @@ -150,12 +136,13 @@ private fun Description( @Composable private fun Footer( - modifier: Modifier = Modifier, showProgress: Boolean, onSaveWalletClick: () -> Unit, ) { Column( - modifier = modifier, + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), ) { @@ -177,13 +164,11 @@ private fun Footer( @Composable private fun DescriptionItem( - modifier: Modifier = Modifier, iconPainter: Painter, title: String, description: String, ) { Row( - modifier = modifier, verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.SpaceEvenly, ) { @@ -220,13 +205,6 @@ private fun SaveWalletScreenContentSample( .background(TangemTheme.colors.background.primary), ) { SaveWalletScreenContent( - modifier = Modifier - .padding(top = TangemTheme.dimens.spacing32) - .fillMaxSize() - .background( - color = TangemTheme.colors.background.plain, - shape = TangemTheme.shapes.bottomSheet, - ), showProgress = false, onSaveWalletClick = { /* no-op */ }, onCloseClick = { /* no-op */ }, diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 41b3f9d34a..64a01578f1 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -26,11 +26,11 @@ interface SendScreenActionUi : SendScreenAction object ReleaseSendState : Action data class PrepareSendScreen( - val coinAmount: Amount?, - val coinRate: BigDecimal?, - val walletManager: WalletManager?, - val tokenAmount: Amount? = null, - val tokenRate: BigDecimal? = null + val coinAmount: Amount?, + val coinRate: BigDecimal?, + val walletManager: WalletManager?, + val tokenAmount: Amount? = null, + val tokenRate: BigDecimal? = null, ) : SendScreenAction // Address or PayId @@ -44,18 +44,17 @@ sealed class AddressPayIdActionUi : SendScreenActionUi { data class ChangePayIdState(val sendingToPayIdEnabled: Boolean) : AddressPayIdActionUi() } - sealed class TransactionExtrasAction : SendScreenActionUi { data class Prepare( - val blockchain: Blockchain, - val walletAddress: String, - val xrpTag: String?, + val blockchain: Blockchain, + val walletAddress: String, + val xrpTag: String?, ) : TransactionExtrasAction() object Release : TransactionExtrasAction() sealed class XlmMemo : TransactionExtrasAction() { - // data class ChangeSelectedMemo(val memoType: XlmMemoType) : XlmMemo() + // data class ChangeSelectedMemo(val memoType: XlmMemoType) : XlmMemo() data class HandleUserInput(val data: String) : XlmMemo() } @@ -81,7 +80,11 @@ sealed class AddressPayIdVerifyAction : SendScreenAction { sealed class PayIdVerification : AddressPayIdVerifyAction() { data class SetPayIdError(val error: Error?) : PayIdVerification() - data class SetPayIdWalletAddress(val payId: String, val payIdWalletAddress: String, val isUserInput: Boolean) : PayIdVerification() + data class SetPayIdWalletAddress( + val payId: String, + val payIdWalletAddress: String, + val isUserInput: Boolean, + ) : PayIdVerification() } sealed class AddressVerification : AddressPayIdVerifyAction() { @@ -121,9 +124,9 @@ sealed class FeeAction : SendScreenAction { } data class ChangeLayoutVisibility( - val main: Boolean? = null, - val controls: Boolean? = null, - val chipGroup: Boolean? = null, + val main: Boolean? = null, + val controls: Boolean? = null, + val chipGroup: Boolean? = null, ) : FeeAction() } @@ -147,14 +150,14 @@ sealed class SendAction : SendScreenAction { sealed class Dialog : SendAction(), StateDialog { data class TezosWarningDialog( - val reduceCallback: () -> Unit, - val sendAllCallback: () -> Unit, - val reduceAmount: BigDecimal, + val reduceCallback: () -> Unit, + val sendAllCallback: () -> Unit, + val reduceAmount: BigDecimal, ) : Dialog() sealed class SendTransactionFails : Dialog() { - data class CardSdkError(val error: TangemSdkError): Dialog() - data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError): Dialog() + data class CardSdkError(val error: TangemSdkError) : Dialog() + data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError) : Dialog() } data class RequestFeeError( @@ -173,7 +176,6 @@ sealed class SendAction : SendScreenAction { data class SendSpecificTransaction( val sendAmount: String, val destinationAddress: String, - val transactionId: String + val transactionId: String, ) : SendAction() - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt index 33a7313b5d..7a94dce2a8 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt @@ -6,12 +6,17 @@ import com.tangem.common.services.Result import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.isPayIdSupported -import com.tangem.tap.features.send.redux.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetAddressError import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetWalletAddress import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress +import com.tangem.tap.features.send.redux.AmountAction +import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.FeeAction +import com.tangem.tap.features.send.redux.TransactionExtrasAction import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.Dispatchers @@ -129,7 +134,7 @@ internal class AddressPayIdMiddleware { } } - val supposedAddress = noSchemeAddress.removeShareUriQuery() //TODO: parse query? + val supposedAddress = noSchemeAddress.removeShareUriQuery() // TODO: parse query? val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress) if (failReason == null) { @@ -167,7 +172,6 @@ internal class AddressPayIdMiddleware { val addressPayId = input ?: return val wallet = appState?.sendState?.walletManager?.wallet ?: return - val internalDispatcher: (Action) -> Unit = { when (it) { is SetWalletAddress, is SetPayIdWalletAddress -> { @@ -193,7 +197,7 @@ internal class AddressPayIdMiddleware { fun String.splitToMap(firstDelimiter: String, secondDelimiter: String): Map { return this.split(firstDelimiter) - .map { it.split(secondDelimiter) } - .map { it.first() to it.last().toString() } - .toMap() + .map { it.split(secondDelimiter) } + .map { it.first() to it.last().toString() } + .toMap() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt index f73c2c3241..8a0b096c4c 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.send.redux.middlewares import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.TransactionSender import com.tangem.blockchain.extensions.Result @@ -82,33 +81,4 @@ class RequestFeeMiddleware { } } } -} - -class FeeMock { - companion object { - suspend fun getFee(blockchain: Blockchain): List { - return feeStandard(blockchain) -// return feeSingle(blockchain) -// return feeStellar(blockchain) -// return feeZero(blockchain) - } - - suspend fun feeStellar(blockchain: Blockchain): List = listOf(Amount(0.0001.toBigDecimal(), blockchain)) - - suspend fun feeZero(blockchain: Blockchain): List = listOf(Amount(BigDecimal.ZERO, blockchain)) - - suspend fun feeStandard(blockchain: Blockchain): List = listOf( - Amount(0.001500.toBigDecimal(), blockchain), - Amount(0.0030.toBigDecimal(), blockchain), - Amount(0.0045001.toBigDecimal(), blockchain), - ) - - suspend fun feeStandardBig(blockchain: Blockchain): List = listOf( - Amount(1.76.toBigDecimal(), blockchain), - Amount(2.30.toBigDecimal(), blockchain), - Amount(3.45.toBigDecimal(), blockchain), - ) - - suspend fun feeSingle(blockchain: Blockchain): List = listOf(Amount(0.0015.toBigDecimal(), blockchain)) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index c72e9f9a67..c3431d6d45 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -13,11 +13,11 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogShow @@ -83,7 +83,8 @@ class SendMiddleware { if (transactionData != null) { store.dispatchOnMain( AddressPayIdVerifyAction.AddressVerification.SetWalletAddress( - transactionData.destinationAddress, false, + address = transactionData.destinationAddress, + isUserInput = false, ), ) store.dispatchOnMain(AmountActionUi.SetMainCurrency(MainCurrencyType.CRYPTO)) @@ -104,7 +105,9 @@ class SendMiddleware { } private fun verifyAndSendTransaction( - action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit, + action: SendActionUi.SendAmountToRecipient, + appState: AppState?, + dispatch: (Action) -> Unit, ) { val sendState = appState?.sendState ?: return val walletManager = sendState.walletManager ?: return @@ -145,6 +148,7 @@ private fun verifyAndSendTransaction( } } +@Suppress("LongParameterList", "LongMethod", "ComplexMethod") private fun sendTransaction( action: SendActionUi.SendAmountToRecipient, walletManager: WalletManager, @@ -183,7 +187,7 @@ private fun sendTransaction( return@launch } - val isLinkedTerminal = tangemSdk.config.linkedTerminal + val linkedTerminalState = tangemSdk.config.linkedTerminal if (card.isStart2Coin) { tangemSdk.config.linkedTerminal = false } @@ -211,6 +215,7 @@ private fun sendTransaction( FirebaseCrashlytics.getInstance().recordException(ex) delay(DELAY_SDK_DIALOG_CLOSE) withMainContext { + tangemSdk.config.linkedTerminal = linkedTerminalState dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) store.dispatchErrorNotification(TapError.CustomError(ex.localizedMessage ?: "Unknown error")) } @@ -219,7 +224,7 @@ private fun sendTransaction( withMainContext { dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) - tangemSdk.config.linkedTerminal = isLinkedTerminal + tangemSdk.config.linkedTerminal = linkedTerminalState val currencyType = AnalyticsParam.CurrencyType.Amount(amountToSend) when (sendResult) { @@ -234,7 +239,7 @@ private fun sendTransaction( } scope.launch(Dispatchers.IO) { updateWallet(walletManager) - delay(11000) // more than 10000 to avoid throttling + delay(timeMillis = 11000) // more than 10000 to avoid throttling updateWallet(walletManager) } } @@ -245,13 +250,13 @@ private fun sendTransaction( feeAmount = feeAmount, destinationAddress = destinationAddress, ) - val error = (sendResult.error as? BlockchainSdkError) ?: return@withMainContext + val error = sendResult.error as? BlockchainSdkError ?: return@withMainContext Analytics.send(Token.Send.TransactionSent(currencyType, error)) when (error) { is BlockchainSdkError.WrappedTangemError -> { - val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return@withMainContext + val tangemSdkError = error.tangemError as? TangemSdkError ?: return@withMainContext if (tangemSdkError is TangemSdkError.UserCancelled) return@withMainContext dispatch(SendAction.Dialog.SendTransactionFails.CardSdkError(tangemSdkError)) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt index faf6f1b4b0..ddacac0df8 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt @@ -19,7 +19,11 @@ class AddressPayIdReducer : SendInternalReducer { else -> sendState } - private fun handleUiAction(action: AddressPayIdActionUi, sendState: SendState, state: AddressPayIdState): SendState { + private fun handleUiAction( + action: AddressPayIdActionUi, + sendState: SendState, + state: AddressPayIdState, + ): SendState { val result = when (action) { is AddressPayIdActionUi.HandleUserInput -> state is AddressPayIdActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler) @@ -35,24 +39,28 @@ class AddressPayIdReducer : SendInternalReducer { return updateLastState(sendState.copy(addressPayIdState = result), result) } - private fun handleAction(action: AddressPayIdVerifyAction, sendState: SendState, state: AddressPayIdState): SendState { + private fun handleAction( + action: AddressPayIdVerifyAction, + sendState: SendState, + state: AddressPayIdState, + ): SendState { val result = when (action) { is PayIdVerification.SetPayIdWalletAddress -> { state.copy( - viewFieldValue = InputViewValue(action.payId, action.isUserInput), - normalFieldValue = action.payId, - truncatedFieldValue = state.truncate(action.payId), - destinationWalletAddress = action.payIdWalletAddress, - error = null + viewFieldValue = InputViewValue(action.payId, action.isUserInput), + normalFieldValue = action.payId, + truncatedFieldValue = state.truncate(action.payId), + destinationWalletAddress = action.payIdWalletAddress, + error = null, ) } is AddressVerification.SetWalletAddress -> { state.copy( - viewFieldValue = InputViewValue(action.address, action.isUserInput), - normalFieldValue = action.address, - truncatedFieldValue = state.truncate(action.address), - destinationWalletAddress = action.address, - error = null + viewFieldValue = InputViewValue(action.address, action.isUserInput), + normalFieldValue = action.address, + truncatedFieldValue = state.truncate(action.address), + destinationWalletAddress = action.address, + error = null, ) } is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt index 7429910244..727964e0c4 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt @@ -37,21 +37,21 @@ class AmountReducer : SendInternalReducer { val rescaledBalance = sendState.convertExtractCryptoToFiat(state.balanceCrypto, true) val viewValue = state.restoreDecimalSeparator(fiatToSend.stripZeroPlainString()) state.copy( - viewAmountValue = InputViewValue(viewValue), - viewBalanceValue = rescaledBalance.stripZeroPlainString(), - mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched), - maxLengthOfAmount = sendState.getDecimals(currency), - cursorAtTheSamePosition = false + viewAmountValue = InputViewValue(viewValue), + viewBalanceValue = rescaledBalance.stripZeroPlainString(), + mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched), + maxLengthOfAmount = sendState.getDecimals(currency), + cursorAtTheSamePosition = false ) } MainCurrencyType.CRYPTO -> { val viewValue = state.restoreDecimalSeparator(state.amountToSendCrypto.stripZeroPlainString()) state.copy( - viewAmountValue = InputViewValue(viewValue), - viewBalanceValue = state.balanceCrypto.stripZeroPlainString(), - mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched), - maxLengthOfAmount = sendState.getDecimals(currency), - cursorAtTheSamePosition = false + viewAmountValue = InputViewValue(viewValue), + viewBalanceValue = state.balanceCrypto.stripZeroPlainString(), + mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched), + maxLengthOfAmount = sendState.getDecimals(currency), + cursorAtTheSamePosition = false ) } } @@ -72,10 +72,10 @@ class AmountReducer : SendInternalReducer { } val viewValue = state.restoreDecimalSeparator(amount.stripZeroPlainString()) state.copy( - viewAmountValue = InputViewValue(viewValue, action.isUserInput), - amountToSendCrypto = action.amountCrypto, - cursorAtTheSamePosition = true, - error = null + viewAmountValue = InputViewValue(viewValue, action.isUserInput), + amountToSendCrypto = action.amountCrypto, + cursorAtTheSamePosition = true, + error = null ) } is AmountAction.SetAmountError -> state.copy(error = action.error) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt index 3238c3b372..d0548132b9 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt @@ -21,7 +21,9 @@ class FeeReducer : SendInternalReducer { private fun handleUiAction(action: FeeActionUi, sendState: SendState, state: FeeState): SendState { val result = when (action) { - is FeeActionUi.ToggleControlsVisibility -> state.copy(controlsLayoutIsVisible = !state.controlsLayoutIsVisible) + is FeeActionUi.ToggleControlsVisibility -> { + state.copy(controlsLayoutIsVisible = !state.controlsLayoutIsVisible) + } is FeeActionUi.ChangeSelectedFee -> { val currentFee = createValueOfFeeAmount(action.feeType, state.feeList) state.copy( diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt index 4d4154218c..3f403ea92c 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt @@ -37,13 +37,12 @@ class ReceiptReducer : SendInternalReducer { this.feeState = sendState.feeState return when (action) { - is RefreshReceipt -> handleRefresh(action, sendState.receiptState) + is RefreshReceipt -> handleRefresh(sendState.receiptState) else -> sendState } } - - private fun handleRefresh(action: RefreshReceipt, state: ReceiptState): SendState { + private fun handleRefresh(state: ReceiptState): SendState { val wallet = sendState.walletManager?.wallet ?: return sendState val layoutType = determineLayoutType(amountState.mainCurrency.type, amountState.typeOfAmount) @@ -55,7 +54,7 @@ class ReceiptReducer : SendInternalReducer { fiat = createFiatType(symbols, showBlank), crypto = createCryptoType(symbols, showBlank), tokenFiat = createTokenFiatType(symbols, showBlank), - tokenCrypto = createTokenCryptoType(symbols, showBlank) + tokenCrypto = createTokenCryptoType(symbols, showBlank), ) return updateLastState(sendState.copy(receiptState = result), result) } @@ -76,7 +75,7 @@ class ReceiptReducer : SendInternalReducer { feeFiat = feeFiat, totalFiat = totalFiat, willSentCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } else { val totalAmountCrypto = amountState.amountToSendCrypto.plus(feeCrypto) @@ -87,7 +86,7 @@ class ReceiptReducer : SendInternalReducer { feeFiat = feeFiat, totalFiat = totalFiat, willSentCrypto = totalAmountCrypto.stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } } @@ -106,7 +105,7 @@ class ReceiptReducer : SendInternalReducer { totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), feeFiat = feeFiat, willSentFiat = convertToFiatPrecision(amountState.amountToSendCrypto), - symbols = symbols + symbols = symbols, ) } else { val totalCrypto = amountState.amountToSendCrypto.plus(feeCrypto) @@ -116,7 +115,7 @@ class ReceiptReducer : SendInternalReducer { totalCrypto = totalCrypto.stripZeroPlainString(), feeFiat = feeFiat, willSentFiat = convertToFiatPrecision(totalCrypto), - symbols = symbols + symbols = symbols, ) } } @@ -140,7 +139,7 @@ class ReceiptReducer : SendInternalReducer { totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), willSentToken = tokensToSend.stripZeroPlainString(), willSentFeeCoin = feeCoin.stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } else { ReceiptTokenFiat( @@ -149,7 +148,7 @@ class ReceiptReducer : SendInternalReducer { totalFiat = UNKNOWN_AMOUNT_SIGN, willSentToken = tokensToSend.stripZeroPlainString(), willSentFeeCoin = feeCoin.stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } } @@ -170,17 +169,16 @@ class ReceiptReducer : SendInternalReducer { amountToken = tokensToSend.stripZeroPlainString(), feeCoin = feeCoin.stripZeroPlainString().addPrecisionSign(), totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), - symbols = symbols + symbols = symbols, ) } else { ReceiptTokenCrypto( amountToken = tokensToSend.stripZeroPlainString(), feeCoin = feeCoin.stripZeroPlainString().addPrecisionSign(), totalFiat = UNKNOWN_AMOUNT_SIGN, - symbols = symbols + symbols = symbols, ) } - } private fun determineSymbols(wallet: Wallet, amountType: AmountType): ReceiptSymbols { @@ -190,7 +188,7 @@ class ReceiptReducer : SendInternalReducer { token = when (amountType) { is AmountType.Token -> amountType.token.symbol else -> null - } + }, ) } @@ -211,9 +209,15 @@ class ReceiptReducer : SendInternalReducer { private fun convertToFiatPrecision(value: BigDecimal, isToken: Boolean = false): String { return when { - !isToken && sendState.coinIsConvertible() -> sendState.coinConverter!!.toFiatWithPrecision(value).stripZeroPlainString() - isToken && sendState.tokenIsConvertible() -> sendState.tokenConverter!!.toFiatWithPrecision(value).stripZeroPlainString() - else -> UNKNOWN_AMOUNT_SIGN + !isToken && sendState.coinIsConvertible() -> { + sendState.coinConverter!!.toFiatWithPrecision(value).stripZeroPlainString() + } + isToken && sendState.tokenIsConvertible() -> { + sendState.tokenConverter!!.toFiatWithPrecision(value).stripZeroPlainString() + } + else -> { + UNKNOWN_AMOUNT_SIGN + } } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index d44a27f0d1..a0d911ccc8 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -28,25 +28,23 @@ interface SendInternalReducer { fun handle(action: SendScreenAction, sendState: SendState): SendState } -class SendScreenReducer { - companion object { - fun reduce(incomingAction: Action, sendState: SendState): SendState { - if (incomingAction is ReleaseSendState) return SendState() - val action = incomingAction as? SendScreenAction ?: return sendState +object SendScreenReducer { + fun reduce(incomingAction: Action, sendState: SendState): SendState { + if (incomingAction is ReleaseSendState) return SendState() + val action = incomingAction as? SendScreenAction ?: return sendState - val reducer: SendInternalReducer = when (action) { - is PrepareSendScreen -> PrepareSendScreenStatesReducer() - is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer() - is TransactionExtrasAction -> TransactionExtrasReducer() - is AmountActionUi, is AmountAction -> AmountReducer() - is FeeActionUi, is FeeAction -> FeeReducer() - is ReceiptAction -> ReceiptReducer() - is SendAction -> SendReducer() - else -> EmptyReducer() - } - - return reducer.handle(action, sendState) + val reducer: SendInternalReducer = when (action) { + is PrepareSendScreen -> PrepareSendScreenStatesReducer() + is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer() + is TransactionExtrasAction -> TransactionExtrasReducer() + is AmountActionUi, is AmountAction -> AmountReducer() + is FeeActionUi, is FeeAction -> FeeReducer() + is ReceiptAction -> ReceiptReducer() + is SendAction -> SendReducer() + else -> EmptyReducer() } + + return reducer.handle(action, sendState) } } @@ -70,19 +68,20 @@ private class SendReducer : SendInternalReducer { } private fun handleSendSpecificTransactionAction( - action: SendAction.SendSpecificTransaction, state: SendState + action: SendAction.SendSpecificTransaction, + state: SendState, ): SendState { return state.copy( externalTransactionData = ExternalTransactionData(action.sendAmount, action.destinationAddress, action.transactionId), feeState = state.feeState.copy( - includeFeeSwitcherIsEnabled = false + includeFeeSwitcherIsEnabled = false, ), amountState = state.amountState.copy( - inputIsEnabled = false + inputIsEnabled = false, ), addressPayIdState = state.addressPayIdState.copy( - inputIsEnabled = false + inputIsEnabled = false, ), ) } @@ -100,17 +99,17 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer { val decimals = amountToExtract.decimals return sendState.copy( - walletManager = walletManager, - coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) }, - tokenConverter = action.tokenRate?.let { CurrencyConverter(it, decimals) }, - amountState = sendState.amountState.copy( - amountToExtract = amountToExtract, - typeOfAmount = amountToExtract.type, - balanceCrypto = amountToExtract.value ?: BigDecimal.ZERO - ), - feeState = sendState.feeState.copy( - includeFeeSwitcherIsEnabled = amountToExtract.type == AmountType.Coin - ) + walletManager = walletManager, + coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) }, + tokenConverter = action.tokenRate?.let { CurrencyConverter(it, decimals) }, + amountState = sendState.amountState.copy( + amountToExtract = amountToExtract, + typeOfAmount = amountToExtract.type, + balanceCrypto = amountToExtract.value ?: BigDecimal.ZERO, + ), + feeState = sendState.feeState.copy( + includeFeeSwitcherIsEnabled = amountToExtract.type == AmountType.Coin, + ), ) } } @@ -118,4 +117,4 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer { internal fun updateLastState(sendState: SendState, lastChangedState: IdStateHolder): SendState { sendState.lastChangedStates.add(lastChangedState.stateId) return sendState -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt index 71000abec1..b59ed31979 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt @@ -24,7 +24,7 @@ class TransactionExtrasReducer : SendInternalReducer { override fun handle(action: SendScreenAction, sendState: SendState): SendState { return when (action) { is Prepare -> handleInitialization(action, sendState) - Release -> handleRelease(action, sendState) + Release -> handleRelease(sendState) is XlmMemo -> handleXlmMemo(action, sendState, sendState.transactionExtrasState) is BinanceMemo -> handleBinanceMemo(action, sendState, sendState.transactionExtrasState) is XrpDestinationTag -> handleXrpTag(action, sendState, sendState.transactionExtrasState) @@ -43,8 +43,11 @@ class TransactionExtrasReducer : SendInternalReducer { if (tag == null) { TransactionExtrasState(xrpDestinationTag = XrpDestinationTagState()) } else { - TransactionExtrasState(xrpDestinationTag = XrpDestinationTagState( - InputViewValue("$tag", false), tag) + TransactionExtrasState( + xrpDestinationTag = XrpDestinationTagState( + viewFieldValue = InputViewValue("$tag", false), + tag = tag, + ), ) } } else { @@ -58,7 +61,7 @@ class TransactionExtrasReducer : SendInternalReducer { return updateLastState(sendState.copy(transactionExtrasState = result), result) } - private fun handleRelease(action: SendScreenAction, sendState: SendState): SendState { + private fun handleRelease(sendState: SendState): SendState { val result = TransactionExtrasState() return updateLastState(sendState.copy(transactionExtrasState = result), result) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt index 30fe37f60a..6d0b9bee8a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt @@ -59,6 +59,7 @@ data class XlmMemoState( else -> XlmMemoType.TEXT } + @Suppress("MagicNumber") fun isAssignableValue(value: String): Boolean = when (determineMemoType(value)) { XlmMemoType.TEXT -> { // from org.stellar.sdk.MemoText @@ -67,7 +68,7 @@ data class XlmMemoState( XlmMemoType.ID -> { try { // from com.tangem.blockchain.blockchains.stellar.StellarMemo.toStellarSdkMemo - value.toBigInteger() in BigInteger.ZERO..(Long.MAX_VALUE.toBigInteger() * 2.toBigInteger()) + value.toBigInteger() in BigInteger.ZERO..Long.MAX_VALUE.toBigInteger() * 2.toBigInteger() } catch (ex: NumberFormatException) { false } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt index 90e22a0e9e..29a6b2e210 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt @@ -6,51 +6,51 @@ enum class ReceiptLayoutType { } data class ReceiptState( - val visibleTypeOfReceipt: ReceiptLayoutType? = null, - val fiat: ReceiptFiat? = null, - val crypto: ReceiptCrypto? = null, - val tokenFiat: ReceiptTokenFiat? = null, - val tokenCrypto: ReceiptTokenCrypto? = null, - val mainCurrency: MainCurrency? = null + val visibleTypeOfReceipt: ReceiptLayoutType? = null, + val fiat: ReceiptFiat? = null, + val crypto: ReceiptCrypto? = null, + val tokenFiat: ReceiptTokenFiat? = null, + val tokenCrypto: ReceiptTokenCrypto? = null, + val mainCurrency: MainCurrency? = null ) : SendScreenState { override val stateId: StateId = StateId.RECEIPT } data class ReceiptSymbols( - val fiat: String, - val crypto: String, - val token: String? = null + val fiat: String, + val crypto: String, + val token: String? = null ) data class ReceiptFiat( - val amountFiat: String, - val feeFiat: String, - val totalFiat: String, - val willSentCrypto: String, - val symbols: ReceiptSymbols + val amountFiat: String, + val feeFiat: String, + val totalFiat: String, + val willSentCrypto: String, + val symbols: ReceiptSymbols ) data class ReceiptCrypto( - val amountCrypto: String, - val feeCrypto: String, - val totalCrypto: String, - val feeFiat: String, - val willSentFiat: String, - val symbols: ReceiptSymbols + val amountCrypto: String, + val feeCrypto: String, + val totalCrypto: String, + val feeFiat: String, + val willSentFiat: String, + val symbols: ReceiptSymbols ) data class ReceiptTokenCrypto( - val amountToken: String, - val feeCoin: String, - val totalFiat: String, - val symbols: ReceiptSymbols + val amountToken: String, + val feeCoin: String, + val totalFiat: String, + val symbols: ReceiptSymbols ) data class ReceiptTokenFiat( - val amountFiat: String, - val feeFiat: String, - val totalFiat: String, - val willSentToken: String, - val willSentFeeCoin: String, - val symbols: ReceiptSymbols + val amountFiat: String, + val feeFiat: String, + val totalFiat: String, + val willSentToken: String, + val willSentFeeCoin: String, + val symbols: ReceiptSymbols ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index 98fbfa47e1..6e7d3596d6 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -30,19 +30,19 @@ enum class StateId { interface SendScreenState : StateType, IdStateHolder data class SendState( - val walletManager: WalletManager? = null, - val coinConverter: CurrencyConverter? = null, - val tokenConverter: CurrencyConverter? = null, - val lastChangedStates: LinkedHashSet = linkedSetOf(), - val addressPayIdState: AddressPayIdState = AddressPayIdState(), - val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(), - val amountState: AmountState = AmountState(), - val feeState: FeeState = FeeState(), - val receiptState: ReceiptState = ReceiptState(), - val sendWarningsList: List = listOf(), - val sendButtonState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.DISABLED), - val dialog: StateDialog? = null, - val externalTransactionData: ExternalTransactionData? = null + val walletManager: WalletManager? = null, + val coinConverter: CurrencyConverter? = null, + val tokenConverter: CurrencyConverter? = null, + val lastChangedStates: LinkedHashSet = linkedSetOf(), + val addressPayIdState: AddressPayIdState = AddressPayIdState(), + val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(), + val amountState: AmountState = AmountState(), + val feeState: FeeState = FeeState(), + val receiptState: ReceiptState = ReceiptState(), + val sendWarningsList: List = listOf(), + val sendButtonState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.DISABLED), + val dialog: StateDialog? = null, + val externalTransactionData: ExternalTransactionData? = null, ) : SendScreenState { override val stateId: StateId = StateId.SEND_SCREEN @@ -114,7 +114,7 @@ data class SendState( fun isReadyToRequestFee(): Boolean = addressPayIdIsReady() && amountIsReady() fun isReadyToSend(): Boolean = addressPayIdIsReady() && amountIsReady() && - store.state.sendState.feeState.isReady() + store.state.sendState.feeState.isReady() } } @@ -144,10 +144,13 @@ data class AmountState( fun isCoinAmount(): Boolean = typeOfAmount == AmountType.Coin fun createMainCurrency(type: MainCurrencyType, canSwitched: Boolean): MainCurrency { - return if (!canSwitched) MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE", false) - else when (type) { - MainCurrencyType.FIAT -> MainCurrency(type, store.state.globalState.appCurrency.code) - MainCurrencyType.CRYPTO -> MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE") + return if (!canSwitched) { + MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE", false) + } else { + when (type) { + MainCurrencyType.FIAT -> MainCurrency(type, store.state.globalState.appCurrency.code) + MainCurrencyType.CRYPTO -> MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE") + } } } @@ -166,9 +169,9 @@ enum class MainCurrencyType { } data class MainCurrency( - val type: MainCurrencyType, - val currencySymbol: String, - val isEnabled: Boolean = true + val type: MainCurrencyType, + val currencySymbol: String, + val isEnabled: Boolean = true, ) data class ExternalTransactionData( @@ -176,5 +179,5 @@ data class ExternalTransactionData( val destinationAddress: String, val transactionId: String, val canAmountBeModified: Boolean = false, - val canDestinationAddressBeModified: Boolean = false + val canDestinationAddressBeModified: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 5c5a0b924d..55b1211cfb 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -1,3 +1,5 @@ +@file:Suppress("MagicNumber") + package com.tangem.tap.features.send.ui import android.content.Context @@ -14,9 +16,9 @@ import androidx.recyclerview.widget.RecyclerView import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.textfield.TextInputEditText import com.tangem.Message +import com.tangem.core.analytics.Analytics import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard import com.tangem.tap.common.KeyboardObserver -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.getFromClipboard @@ -30,10 +32,25 @@ import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget import com.tangem.tap.common.toggleWidget.ViewStateWidget import com.tangem.tap.features.BaseStoreFragment import com.tangem.tap.features.addBackPressHandler -import com.tangem.tap.features.send.redux.* -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* -import com.tangem.tap.features.send.redux.AmountActionUi.* -import com.tangem.tap.features.send.redux.FeeActionUi.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.CheckClipboard +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.PasteAddressPayId +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.SetTruncateHandler +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.TruncateOrRestore +import com.tangem.tap.features.send.redux.AmountAction +import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.AmountActionUi.CheckAmountToSend +import com.tangem.tap.features.send.redux.AmountActionUi.SetMainCurrency +import com.tangem.tap.features.send.redux.AmountActionUi.SetMaxAmount +import com.tangem.tap.features.send.redux.AmountActionUi.ToggleMainCurrency +import com.tangem.tap.features.send.redux.FeeActionUi.ChangeIncludeFee +import com.tangem.tap.features.send.redux.FeeActionUi.ChangeSelectedFee +import com.tangem.tap.features.send.redux.FeeActionUi.ToggleControlsVisibility +import com.tangem.tap.features.send.redux.ReceiptAction +import com.tangem.tap.features.send.redux.ReleaseSendState +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.send.redux.SendActionUi +import com.tangem.tap.features.send.redux.TransactionExtrasAction import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber @@ -45,7 +62,12 @@ import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentSendBinding import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import java.text.DecimalFormatSymbols /** @@ -85,13 +107,16 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { private fun initSendButtonStates() = with(binding) { btnSend.setOnClickListener { - store.dispatch(SendActionUi.SendAmountToRecipient( - Message(getString(R.string.initial_message_sign_header)) - )) + store.dispatch( + SendActionUi.SendAmountToRecipient( + Message(getString(R.string.initial_message_sign_header)), + ), + ) } sendBtn = IndeterminateProgressButtonWidget(btnSend, progress) } + @Suppress("MagicNumber") private fun setupAddressOrPayIdLayout() = with(binding.lSendAddressPayid) { store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") }) store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString())) @@ -100,12 +125,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { store.dispatch(TruncateOrRestore(!hasFocus)) } etAddressOrPayId.inputtedTextAsFlow() - .debounce(400) - .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it } - .onEach { - store.dispatch(AddressPayIdActionUi.HandleUserInput(it)) - } - .launchIn(mainScope) + .debounce(400) + .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it } + .onEach { + store.dispatch(AddressPayIdActionUi.HandleUserInput(it)) + } + .launchIn(mainScope) imvPaste.setOnClickListener { Analytics.send(Token.Send.ButtonPaste()) @@ -116,29 +141,29 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { Analytics.send(Token.Send.ButtonQRCode()) startActivityForResult( Intent(requireContext(), ScanQrCodeActivity::class.java), - ScanQrCodeActivity.SCAN_QR_REQUEST_CODE + ScanQrCodeActivity.SCAN_QR_REQUEST_CODE, ) } } private fun setupTransactionExtrasLayout() = with(binding.lSendAddressPayid) { etXlmMemo.inputtedTextAsFlow() - .debounce(400) - .filter { - val info = store.state.sendState.transactionExtrasState - info.xlmMemo?.viewFieldValue?.value != it - } - .onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) } - .launchIn(mainScope) + .debounce(400) + .filter { + val info = store.state.sendState.transactionExtrasState + info.xlmMemo?.viewFieldValue?.value != it + } + .onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) } + .launchIn(mainScope) etDestinationTag.inputtedTextAsFlow() - .debounce(400) - .filter { - val info = store.state.sendState.transactionExtrasState - info.xrpDestinationTag?.viewFieldValue?.value != it - } - .onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) } - .launchIn(mainScope) + .debounce(400) + .filter { + val info = store.state.sendState.transactionExtrasState + info.xrpDestinationTag?.viewFieldValue?.value != it + } + .onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) } + .launchIn(mainScope) etBinanceMemo.inputtedTextAsFlow() .debounce(400) @@ -159,10 +184,13 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { // Delayed launch is needed in order for the UI to be drawn and to process the sent events. // If do not use the delay, then etAmount error field is not displayed when // inserting an incorrect amount by shareUri - binding.lSendAddressPayid.imvQrCode.postDelayed({ - store.dispatch(PasteAddressPayId(scannedCode)) - store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused)) - }, 200) + binding.lSendAddressPayid.imvQrCode.postDelayed( + { + store.dispatch(PasteAddressPayId(scannedCode)) + store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused)) + }, + 200, + ) } private fun setupAmountLayout() { @@ -221,10 +249,10 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } etAmountToSend.inputtedTextAsFlow() - .debounce(400) - .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() } - .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) } - .launchIn(mainScope) + .debounce(400) + .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() } + .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) } + .launchIn(mainScope) etAmountToSend.setOnImeActionListener(EditorInfo.IME_ACTION_DONE) { it.hideSoftKeyboard() @@ -287,7 +315,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { store.dispatch(NavigationAction.PopBackTo()) } else { store.dispatch( - WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId) + WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId), ) } } @@ -309,24 +337,22 @@ fun EditText.inputtedTextAsFlow(): Flow = callbackFlow { awaitClose { removeTextChangedListener(watcher) } } -class FeeUiHelper { - companion object { - fun toId(fee: FeeType): Int { - return when (fee) { - FeeType.SINGLE -> View.NO_ID - FeeType.LOW -> R.id.chipLow - FeeType.NORMAL -> R.id.chipNormal - FeeType.PRIORITY -> R.id.chipPriority - } +object FeeUiHelper { + fun toId(fee: FeeType): Int { + return when (fee) { + FeeType.SINGLE -> View.NO_ID + FeeType.LOW -> R.id.chipLow + FeeType.NORMAL -> R.id.chipNormal + FeeType.PRIORITY -> R.id.chipPriority } + } - fun toType(id: Int): FeeType { - return when (id) { - R.id.chipLow -> FeeType.LOW - R.id.chipNormal -> FeeType.NORMAL - R.id.chipPriority -> FeeType.PRIORITY - else -> FeeType.NORMAL - } + fun toType(id: Int): FeeType { + return when (id) { + R.id.chipLow -> FeeType.LOW + R.id.chipNormal -> FeeType.NORMAL + R.id.chipPriority -> FeeType.PRIORITY + else -> FeeType.NORMAL } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt index 353989e76c..8da2d25445 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt @@ -11,22 +11,19 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class RequestFeeErrorDialog { +object RequestFeeErrorDialog { + fun create(context: Context, dialog: SendAction.Dialog.RequestFeeError): AlertDialog { + val errorMessage = dialog.error.customMessage - companion object { - fun create(context: Context, dialog: SendAction.Dialog.RequestFeeError): AlertDialog { - val errorMessage = dialog.error.customMessage - - return AlertDialog.Builder(context).apply { - setTitle(R.string.common_fee_error) - setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) - setNegativeButton(R.string.alert_button_send_feedback) { _, _ -> - store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) - } - setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() } - setNeutralButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) } - }.create() - } + return AlertDialog.Builder(context).apply { + setTitle(R.string.common_fee_error) + setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) + setNegativeButton(R.string.alert_button_send_feedback) { _, _ -> + store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) + } + setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() } + setNeutralButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) } + }.create() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt index 0e35552e43..600e7a99d7 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt @@ -16,28 +16,26 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class SendTransactionFailsDialog { - companion object { - fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.CardSdkError): AlertDialog { - return create(context, dialog.error.localizedDescription(context)) - } +object SendTransactionFailsDialog { + fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.CardSdkError): AlertDialog { + return create(context, dialog.error.localizedDescription(context)) + } - fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.BlockchainSdkError): AlertDialog { - val errorConverter = BlockchainSdkErrorConverter(context) - return create(context, errorConverter.convert(dialog.error)) - } + fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.BlockchainSdkError): AlertDialog { + val errorConverter = BlockchainSdkErrorConverter(context) + return create(context, errorConverter.convert(dialog.error)) + } - private fun create(context: Context, errorMessage: String): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.alert_failed_to_send_transaction_title) - setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) - setNeutralButton(R.string.alert_button_send_feedback) { _, _ -> - store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) - } - setPositiveButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) } - }.create() - } + private fun create(context: Context, errorMessage: String): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.alert_failed_to_send_transaction_title) + setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) + setNeutralButton(R.string.alert_button_send_feedback) { _, _ -> + store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) + } + setPositiveButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) } + }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index de2fd70ef6..ac418ad22c 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -69,9 +69,9 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : } } + @Suppress("ComplexMethod") private fun handleTransactionExtrasState(fg: SendFragment, infoState: TransactionExtrasState) = with(fg.binding.lSendAddressPayid) { - fun showView(view: View, info: Any?) { view.show(info != null) } @@ -220,7 +220,11 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : tilAmountToSend.enableError(false) } - val filter = DecimalDigitsInputFilter(12, state.maxLengthOfAmount, state.decimalSeparator) + val filter = DecimalDigitsInputFilter( + digitsBeforeDecimal = 12, + digitsAfterDecimal = state.maxLengthOfAmount, + decimalSeparator = state.decimalSeparator, + ) etAmountToSend.filters = arrayOf(filter) val amountToSend = state.viewAmountValue if (!amountToSend.isFromUserInput) etAmountToSend.update(amountToSend.value) @@ -249,8 +253,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : tvAmountCurrency.setTextColor(fg.getColor(textColor)) } + @Suppress("MagicNumber") private fun handleFeeState(fg: SendFragment, state: FeeState) = with(fg.binding.clNetworkFee) { -// chipGroup.fitChipsByGroupWidth() fg.view?.findViewById(R.id.clNetworkFee)?.show(state.mainLayoutIsVisible) flExpandCollapse.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f llFeeControlsContainer.show(state.controlsLayoutIsVisible) @@ -265,6 +269,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : if (chipGroup.checkedChipId != chipId && chipId != View.NO_ID) chipGroup.check(chipId) } + @Suppress("LongMethod", "ComplexMethod") private fun handleReceiptState( fg: SendFragment, state: ReceiptState, @@ -289,9 +294,9 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : pbReceiptFee.hide() tvReceiptFeeValue.show() } + else -> {} } - when (state.visibleTypeOfReceipt) { ReceiptLayoutType.FIAT -> { val receipt = state.fiat ?: return @@ -316,13 +321,19 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : tvReceiptAmountValue.update("${receipt.amountCrypto} ${receipt.symbols.crypto}") tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}") llTotalContainer.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}") - llTotalContainer.tvWillBeSentValue.update( - getString( - R.string.send_total_subtitle_fiat_format, - "${roughOrEmpty(receipt.willSentFiat)} ${receipt.symbols.fiat}", - "${receipt.feeFiat} ${receipt.symbols.fiat}", - ), - ) + + if (receipt.willSentFiat == UNKNOWN_AMOUNT_SIGN) { + llTotalContainer.tvWillBeSentValue.hide() + } else { + llTotalContainer.tvWillBeSentValue.show() + llTotalContainer.tvWillBeSentValue.update( + getString( + R.string.send_total_subtitle_fiat_format, + "${receipt.willSentFiat} ${receipt.symbols.fiat}", + "${receipt.feeFiat} ${receipt.symbols.fiat}", + ), + ) + } } ReceiptLayoutType.TOKEN_FIAT -> { val receipt = state.tokenFiat ?: return @@ -356,6 +367,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : } llTotalContainer.tvTotalTokenCryptoValue.update(willSent.toString()) } + else -> {} } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt index 27a4a5b9e5..219753c469 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt @@ -2,9 +2,9 @@ package com.tangem.tap.features.shop.redux import android.content.Intent import com.tangem.tap.common.redux.NotificationAction -import com.tangem.tap.common.shop.GooglePayService import com.tangem.tap.common.shop.data.ProductType import com.tangem.tap.common.shop.data.TangemProduct +import com.tangem.tap.common.shop.googlepay.GooglePayService import com.tangem.wallet.R import org.rekotlin.Action diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt index 62e21e95e1..3af546cab6 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt @@ -27,8 +27,8 @@ class ShopMiddleware { } } +@Suppress("LongMethod", "ComplexMethod") private fun handle(action: Action) { - val shopState = store.state.shopState if (action is NavigationAction.NavigateTo && action.screen == AppScreen.Shop) { @@ -51,8 +51,8 @@ private fun handle(action: Action) { store.dispatchOnMain( ShopAction.ApplyPromoCode.Success( promoCode = products.first { it.type == shopState.selectedProduct }.appliedDiscount, - products = products - ) + products = products, + ), ) } result.onFailure { store.dispatchOnMain(ShopAction.ApplyPromoCode.InvalidPromoCode) } @@ -78,7 +78,7 @@ private fun handle(action: Action) { val result = shopService.handleGooglePayResult( action.resultCode, action.data, - shopState.selectedProduct + shopState.selectedProduct, ) result.onSuccess { store.dispatchOnMain(ShopAction.BuyWithGooglePay.Success) @@ -87,7 +87,6 @@ private fun handle(action: Action) { store.dispatchOnMain(ShopAction.BuyWithGooglePay.Failure(it)) } } - } ShopAction.LoadProducts -> { scope.launch { @@ -96,7 +95,7 @@ private fun handle(action: Action) { onFailure = { Timber.e(it) store.dispatchOnMain(ShopAction.LoadProducts.Failure) - } + }, ) } } @@ -111,7 +110,6 @@ private fun handle(action: Action) { ShopAction.CheckIfGooglePayAvailable.Failure } store.dispatchOnMain(newAction) - } } ShopAction.StartWebCheckout -> { @@ -125,5 +123,6 @@ private fun handle(action: Action) { shopService.waitForCheckout(shopState.selectedProduct) } } + else -> {} } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt index 001c48a46f..8a97a1a58c 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt @@ -2,20 +2,18 @@ package com.tangem.tap.features.shop.redux import org.rekotlin.Action -class ShopReducer { - companion object { - fun reduce(action: Action, state: ShopState): ShopState = internalReduce(action, state) - } +object ShopReducer { + fun reduce(action: Action, state: ShopState): ShopState = internalReduce(action, state) } +@Suppress("ComplexMethod") private fun internalReduce(action: Action, state: ShopState): ShopState { - if (action !is ShopAction) return state return when (action) { is ShopAction.ApplyPromoCode -> state.copy( promoCode = action.promoCode, - promoCodeLoading = true + promoCodeLoading = true, ) ShopAction.BuyWithGooglePay -> state ShopAction.LoadProducts -> state @@ -26,13 +24,14 @@ private fun internalReduce(action: Action, state: ShopState): ShopState { } ShopAction.StartWebCheckout -> state ShopAction.ApplyPromoCode.InvalidPromoCode -> state.copy( - promoCode = null, promoCodeLoading = false + promoCode = null, + promoCodeLoading = false, ) is ShopAction.ApplyPromoCode.Success -> { state.copy( promoCode = action.promoCode, availableProducts = action.products, - promoCodeLoading = false + promoCodeLoading = false, ) } @@ -49,7 +48,6 @@ private fun internalReduce(action: Action, state: ShopState): ShopState { } ShopAction.CheckIfGooglePayAvailable.Success -> { state.copy(isGooglePayAvailable = false) // TODO: change when we add support for GPay - } is ShopAction.BuyWithGooglePay.Failure -> { state diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt index a4ead917a3..ebbbb7cb79 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt @@ -15,11 +15,11 @@ data class ShopState( get() = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum?.finalValue val priceBeforeDiscount: String? - get() { - val totalSum = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum - if (totalSum?.finalValue != totalSum?.beforeDiscount) { - return totalSum?.beforeDiscount + get() { + val totalSum = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum + if (totalSum?.finalValue != totalSum?.beforeDiscount) { + return totalSum?.beforeDiscount + } + return null } - return null - } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt index 622f440a18..dccbe4d7b7 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt @@ -12,6 +12,7 @@ import androidx.activity.OnBackPressedCallback import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.tap.common.GlobalLayoutStateHandler import com.tangem.tap.common.KeyboardObserver +import com.tangem.tap.common.extensions.getQuantityString import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.shop.data.ProductType @@ -23,12 +24,12 @@ import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentShopBinding import org.rekotlin.StoreSubscriber - class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber { private val binding: FragmentShopBinding by viewBinding(FragmentShopBinding::bind) + private var cardTranslationY = 70f - lateinit var keyboardObserver: KeyboardObserver + private lateinit var keyboardObserver: KeyboardObserver override fun subscribeToStore() { store.subscribe(this) { state -> @@ -42,12 +43,15 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(ShopAction.ResetState) - } - }) + activity?.onBackPressedDispatcher?.addCallback( + this, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + store.dispatch(ShopAction.ResetState) + } + }, + ) } override fun onDestroyView() { @@ -73,7 +77,7 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< } } - private var cardTranslationY = 70f + @Suppress("MagicNumber") private fun setupCardsImages() { GlobalLayoutStateHandler(binding.imvSecond).apply { onStateChanged = { @@ -91,7 +95,6 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< detach() } } - } private fun setupProductSelection() = with(binding) { @@ -101,6 +104,8 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< chipProduct2.setOnCheckedChangeListener { _, isChecked -> if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_2_CARDS)) } + chipProduct1.text = chipProduct1.getQuantityString(R.plurals.card_label_card_count, quantity = 3) + chipProduct2.text = chipProduct2.getQuantityString(R.plurals.card_label_card_count, quantity = 2) } private fun setupPromoCodeEditText() = with(binding) { @@ -144,12 +149,14 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< if (show) imvThird.show() imvThird.animate() .translationY(translationY) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - super.onAnimationEnd(animation) - imvThird.show(show) - } - }) + .setListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + imvThird.show(show) + } + }, + ) } private fun handlePriceState(state: ShopState) = with(binding) { @@ -157,7 +164,6 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< tvTotalBeforeDiscount.text = state.priceBeforeDiscount pbPrice.show(state.total == null) - } private fun handlePromoCodeState(state: ShopState) = with(binding) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt index 0c03a6869d..2a41a990b6 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt @@ -10,10 +10,10 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.core.analytics.Analytics +import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState import com.tangem.domain.redux.domainStore -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.compose.ClosePopupTrigger import com.tangem.tap.features.BaseStoreFragment @@ -57,14 +57,13 @@ class AddCustomTokenFragment : BaseStoreFragment(R.layout.view_compose_fragment) val closePopupTrigger = initClosingPopupTriggerEvent() view.findViewById(R.id.view_compose)?.setContent { - AppCompatTheme(requireContext()) { + TangemTheme { Box( modifier = Modifier .fillMaxSize(), ) { AddCustomTokenScreen(state, closePopupTrigger) } - } } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt index 93066fc926..1d57e7472d 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt @@ -6,23 +6,16 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.BottomSheetScaffold import androidx.compose.material.BottomSheetScaffoldState import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ExtendedFloatingActionButton import androidx.compose.material.FabPosition -import androidx.compose.material.FloatingActionButtonDefaults -import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable @@ -33,11 +26,14 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.keyboardAsState +import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.AddCustomTokenError import com.tangem.domain.common.form.DataField import com.tangem.domain.common.form.FieldId @@ -55,19 +51,12 @@ import com.tangem.domain.redux.domainStore import com.tangem.tap.common.compose.AddCustomTokenWarning import com.tangem.tap.common.compose.ClosePopupTrigger import com.tangem.tap.common.compose.ComposeDialogManager -import com.tangem.tap.common.compose.ToggledRippleTheme -import com.tangem.tap.common.compose.keyboardAsState import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCase import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCasesList import com.tangem.wallet.R import kotlinx.coroutines.launch -/** -[REDACTED_AUTHOR] - */ -private class AddCustomTokenScreen {} // for simple search - @OptIn(ExperimentalMaterialApi::class) @Composable fun AddCustomTokenScreen( @@ -77,7 +66,7 @@ fun AddCustomTokenScreen( val selectedTestCase = remember { mutableStateOf(TestCase.ContractAddress) } val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( - bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed) + bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed), ) val coroutineScope = rememberCoroutineScope() val toggleBottomSheet = { coroutineScope.launch { bottomSheetScaffoldState.toggle() } } @@ -91,11 +80,13 @@ fun AddCustomTokenScreen( }, sheetPeekHeight = 0.dp, ) { - Column() { - TestCasesList(onItemClick = { - selectedTestCase.value = it - toggleBottomSheet() - }) + Column { + TestCasesList( + onItemClick = { + selectedTestCase.value = it + toggleBottomSheet() + }, + ) ScreenContent(state, closePopupTrigger) } } @@ -130,8 +121,12 @@ private fun ScreenContent( } }, floatingActionButtonPosition = FabPosition.Center, - ) { - Box(Modifier.fillMaxSize()) { + ) { paddings -> + Box( + modifier = Modifier + .padding(paddings) + .fillMaxSize(), + ) { LazyColumn( contentPadding = PaddingValues(bottom = 90.dp), ) { @@ -144,7 +139,7 @@ private fun ScreenContent( Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp) + .padding(16.dp), ) { FormFields(state, closePopupTrigger) } @@ -195,7 +190,7 @@ fun Warnings(warnings: List) { AddCustomTokenWarning( modifier = modifier.fillMaxWidth(), warning = item, - converter = warningConverter + converter = warningConverter, ) } } @@ -203,67 +198,34 @@ fun Warnings(warnings: List) { @Composable private fun AddButton(state: MutableState) { - AddCustomTokenFab( + PrimaryButtonIconLeft( modifier = Modifier - .widthIn(210.dp, 280.dp), - isEnabled = state.value.screenState.addButton.isEnabled - ) { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) } -} - -@Composable -private fun AddCustomTokenFab( - modifier: Modifier = Modifier, - isEnabled: Boolean = true, - onClick: () -> Unit -) { - val contentColor = Color.White - val backgroundColor = if (isEnabled) { - Color(0xFF1ACE80) - } else { - Color(0xFFB9E6D3) - } - val elevation = if (!isEnabled) { - FloatingActionButtonDefaults.elevation(0.dp, 0.dp) - } else { - FloatingActionButtonDefaults.elevation() - } - - ToggledRippleTheme(isEnabled) { - ExtendedFloatingActionButton( - modifier = modifier, - icon = { - Icon( - imageVector = Icons.Filled.Add, - tint = contentColor, - contentDescription = "Add", - ) - }, - text = { Text(text = stringResource(id = R.string.common_add)) }, - onClick = { if (isEnabled) onClick() }, - backgroundColor = backgroundColor, - contentColor = contentColor, - elevation = elevation, - ) - } + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(id = R.string.common_add), + icon = painterResource(id = R.drawable.ic_plus_24), + enabled = state.value.screenState.addButton.isEnabled, + onClick = { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) }, + ) } data class ScreenFieldData( val field: DataField<*>, val error: AddCustomTokenError?, val errorConverter: ModuleMessageConverter, - val viewState: ViewStates.TokenField + val viewState: ViewStates.TokenField, ) { companion object { fun fromState( field: DataField<*>, state: AddCustomTokenState, - errorConverter: ModuleMessageConverter + errorConverter: ModuleMessageConverter, ): ScreenFieldData { return ScreenFieldData( field = field, error = state.getError(field.id), errorConverter = errorConverter, - viewState = selectField(field.id, state.screenState) + viewState = selectField(field.id, state.screenState), ) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt index bc4c0ae9c6..7a14ca2d85 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt @@ -2,10 +2,8 @@ package com.tangem.tap.features.tokens.addCustomToken.compose import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.SpacerH8 import com.tangem.domain.common.form.Field import com.tangem.domain.features.addCustomToken.TokenBlockchainField @@ -20,12 +18,9 @@ import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTo import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState import com.tangem.domain.redux.domainStore import com.tangem.tap.common.compose.BlockchainSpinner -import com.tangem.tap.common.compose.ClearButton import com.tangem.tap.common.compose.ClosePopupTrigger import com.tangem.tap.common.compose.OutlinedTextFieldWidget -import com.tangem.tap.common.compose.PasteButton import com.tangem.tap.common.compose.TitleSubtitle -import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.wallet.R /** @@ -52,31 +47,6 @@ fun TokenContractAddressView(screenFieldData: ScreenFieldData) { SpacerH8() } -@Composable -private fun PasteClearButton( - showFirst: Boolean, -) { - if (showFirst) { - val context = LocalContext.current - PasteButton( - onClick = { - context.getFromClipboard()?.let { - val fieldData = Field.Data(it.toString(), false) - domainStore.dispatch(OnTokenContractAddressChanged(fieldData)) - } - - } - ) - } else { - ClearButton( - onClick = { - val fieldData = Field.Data("", false) - domainStore.dispatch(OnTokenContractAddressChanged(fieldData)) - } - ) - } -} - @Composable fun TokenNameView(screenFieldData: ScreenFieldData) { if (!screenFieldData.viewState.isVisible) return @@ -178,10 +148,4 @@ fun TokenDerivationPathView( closePopupTrigger = closePopupTrigger, ) { domainStore.dispatch(OnTokenDerivationPathChanged(Field.Data(it, true))) } SpacerH8() -} - -@Preview -@Composable -fun TestTokenContractAddressView() { - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt index 46e31151a8..ceae8063fa 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt @@ -9,8 +9,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.Keyboard import com.tangem.tangem_sdk_new.extensions.pxToDp -import com.tangem.tap.common.compose.Keyboard /** [REDACTED_AUTHOR] @@ -20,9 +20,8 @@ fun HangingOverKeyboardView( modifier: Modifier = Modifier, keyboardState: State, spaceBetweenKeyboard: Dp = 0.dp, - content: @Composable() (BoxScope.() -> Unit) + content: @Composable (BoxScope.() -> Unit) ) { - val context = LocalContext.current val padding = when (keyboardState.value) { Keyboard.Closed -> 0.dp diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/test/ContractAddressTests.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/test/ContractAddressTests.kt index 25494b3627..2eb3b4923b 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/test/ContractAddressTests.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/test/ContractAddressTests.kt @@ -22,7 +22,7 @@ import com.tangem.domain.redux.domainStore */ @Composable fun ContractAddressTests( - onItemClick: VoidCallback + onItemClick: VoidCallback, ) { val casesInfo = listOf( "USDC on ETH" to "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", @@ -38,7 +38,7 @@ fun ContractAddressTests( @Composable fun SolanaAddressTests( - onItemClick: VoidCallback + onItemClick: VoidCallback, ) { val casesInfo = listOf( "USDT (full)" to "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", @@ -54,70 +54,84 @@ private fun CasesListContent( casesList: List>, onItemClick: VoidCallback, ) { - LazyColumn(content = { - item { - Row() { - ResetContractAddressButton(onItemClick) - Text("", modifier = Modifier.weight(1f)) - ResetAllFieldsButton(onItemClick) + LazyColumn( + content = { + item { + Row { + ResetContractAddressButton(onItemClick) + Text("", modifier = Modifier.weight(1f)) + ResetAllFieldsButton(onItemClick) + } + Divider() } - Divider() - } - items(casesList.size) { - val (info, address) = casesList[it] - ContractAddressButton(info, address, onItemClick) - } - }) + items(casesList.size) { + val (info, address) = casesList[it] + ContractAddressButton(info, address, onItemClick) + } + }, + ) } @Composable -fun ResetAllFieldsButton( - onItemClick: VoidCallback -) { - ActionButton(name = "Reset") { - onItemClick() - domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false))) - domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(Blockchain.Unknown, false))) - domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data("", false))) - domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data("", false))) - domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data("", false))) - domainStore.dispatch(AddCustomTokenAction.OnTokenDerivationPathChanged(Field.Data(Blockchain.Unknown, false))) - } +fun ResetAllFieldsButton(onItemClick: VoidCallback) { + ActionButton( + name = "Reset", + onClick = { + onItemClick() + domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false))) + domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(Blockchain.Unknown, false))) + domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data("", false))) + domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data("", false))) + domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data("", false))) + domainStore.dispatch( + AddCustomTokenAction.OnTokenDerivationPathChanged( + Field.Data( + Blockchain.Unknown, + false, + ), + ), + ) + }, + ) } @Composable fun ResetContractAddressButton( - onItemClick: VoidCallback + onItemClick: VoidCallback, ) { - ActionButton(name = "Set empty address") { - onItemClick() - domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false))) - } + ActionButton( + name = "Set empty address", + onClick = { + onItemClick() + domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false))) + }, + ) } @Composable private fun ContractAddressButton( name: String, address: String, - onItemClick: VoidCallback + onItemClick: VoidCallback, ) { ActionButton( modifier = Modifier.fillMaxWidth(), name = name, - ) { - onItemClick() - domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data(address, false))) - } + onClick = { + onItemClick() + domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data(address, false))) + }, + ) } @Composable fun ActionButton( - modifier: Modifier = Modifier, name: String, onClick: () -> Unit, + modifier: Modifier = Modifier, ) { Button( modifier = modifier.padding(horizontal = 8.dp), - onClick = onClick + onClick = onClick, ) { Text(name, fontSize = 12.sp) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/test/TestCasesList.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/test/TestCasesList.kt index ecdc68bd8c..ef6a3bc4cd 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/test/TestCasesList.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/test/TestCasesList.kt @@ -19,30 +19,19 @@ import com.tangem.wallet.R [REDACTED_AUTHOR] */ @Composable -fun TestCasesList( - onItemClick: (TestCase) -> Unit -) { +fun TestCasesList(onItemClick: (TestCase) -> Unit) { if (!BuildConfig.TEST_ACTION_ENABLED) return - Surface( - color = colorResource(id = R.color.lightGray5) - ) { - Column( - Modifier.padding(horizontal = 16.dp) - ) { - listOf( - TestCase.ContractAddress, - TestCase.SolanaTokens, - ).map { TestCaseListItem(it, onItemClick) } + Surface(color = colorResource(id = R.color.lightGray5)) { + Column(Modifier.padding(horizontal = 16.dp)) { + listOf(TestCase.ContractAddress, TestCase.SolanaTokens) + .map { case -> TestCaseListItem(testCase = case, onItemClick = { onItemClick(case) }) } } } } @Composable -fun TestCaseListItem( - testCase: TestCase, - onItemClick: (TestCase) -> Unit, -) { +fun TestCaseListItem(testCase: TestCase, onItemClick: () -> Unit) { Row( verticalAlignment = Alignment.CenterVertically, ) { @@ -51,7 +40,7 @@ fun TestCaseListItem( text = testCase.description, ) Button( - onClick = { onItemClick(testCase) } + onClick = onItemClick, ) { Text("Start") } } } @@ -59,5 +48,6 @@ fun TestCaseListItem( enum class TestCase(val description: String, val content: @Composable (VoidCallback) -> Unit) { ContractAddress("Test contract address field", { ContractAddressTests(it) }), Auto("Test contract address field", { ContractAddressTests(it) }), - SolanaTokens("Test Solana contract addresses", { SolanaAddressTests(it) }), ; + SolanaTokens("Test Solana contract addresses", { SolanaAddressTests(it) }), + ; } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt index 79f171b03f..39ac838653 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt @@ -21,11 +21,13 @@ sealed class TokensAction : Action { object Failure : TokensAction() } - data class LoadMore(val scanResponse: ScanResponse? = null + data class LoadMore( + val scanResponse: ScanResponse? = null ) : TokensAction() data class SetAddedCurrencies( - val wallets: List, val derivationStyle: DerivationStyle? + val wallets: List, + val derivationStyle: DerivationStyle? ) : TokensAction() data class SaveChanges( diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 7a4a1a1b2a..606ee227cc 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -10,6 +10,7 @@ import com.tangem.common.extensions.toMapKey import com.tangem.common.flatMap import com.tangem.common.hdWallet.DerivationPath import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics import com.tangem.domain.DomainWrapped import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.derivationStyle @@ -21,7 +22,6 @@ import com.tangem.domain.redux.domainStore import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.assetReader -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain @@ -40,10 +40,12 @@ import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletCurrenciesManager +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Middleware +@Suppress("LargeClass") class TokensMiddleware { val tokensMiddleware: Middleware = { _, _ -> @@ -61,8 +63,8 @@ class TokensMiddleware { ) } is TokensAction.LoadMore -> { - if (store.state.tokensState.needToLoadMore - && store.state.tokensState.currencies.isNotEmpty() + if (store.state.tokensState.needToLoadMore && + store.state.tokensState.currencies.isNotEmpty() ) { handleLoadCurrencies(action.scanResponse) } @@ -83,22 +85,25 @@ class TokensMiddleware { .filter { !it.isTestnet() } val loadCoinsService = LoadAvailableCoinsService( - store.state.domainNetworks.tangemTechService, - assetReader, + tangemTechApi = store.state.domainNetworks.tangemTechService.api, + dispatchers = AppCoroutineDispatcherProvider(), + assetReader = assetReader, ) scope.launch { - val loadCoinsResult = if (newSearchInput == null) { loadCoinsService.getSupportedTokens( isTestcard, supportedBlockchains, tokensState.pageToLoad, - tokensState.searchInput + tokensState.searchInput, ) } else { loadCoinsService.getSupportedTokens( - isTestcard, supportedBlockchains, 0, newSearchInput.ifBlank { null } + isTestNet = isTestcard, + supportedBlockchains = supportedBlockchains, + page = 0, + searchInput = newSearchInput.ifBlank { null }, ) } when (loadCoinsResult) { @@ -107,13 +112,13 @@ class TokensMiddleware { .filter(supportedBlockchains.toSet()) store.dispatchOnMain( TokensAction.LoadCurrencies.Success( - currencies, loadCoinsResult.data.moreAvailable, + currencies, + loadCoinsResult.data.moreAvailable, ), ) } is Result.Failure -> store.dispatchOnMain(TokensAction.LoadCurrencies.Failure) } - } } @@ -141,12 +146,13 @@ class TokensMiddleware { convertToCurrencies( blockchains = blockchainsToRemove, tokens = tokensToRemove, - derivationStyle = derivationStyle - ) + derivationStyle = derivationStyle, + ), ) - if (tokensToAdd.isEmpty() && tokensToRemove.isEmpty() - && blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() + @Suppress("ComplexCondition") + if (tokensToAdd.isEmpty() && tokensToRemove.isEmpty() && + blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() ) { store.dispatchDebugErrorNotification("Nothing to save") store.dispatchOnMain(NavigationAction.PopBackTo()) @@ -156,7 +162,7 @@ class TokensMiddleware { val currencyList = convertToCurrencies( blockchains = blockchainsToAdd, tokens = tokensToAdd, - derivationStyle = derivationStyle + derivationStyle = derivationStyle, ) if (scanResponse.supportsHdWallet()) { deriveMissingBlockchains(scanResponse, currencyList) { @@ -172,7 +178,7 @@ class TokensMiddleware { private fun convertToCurrencies( blockchains: List, tokens: List, - derivationStyle: DerivationStyle? + derivationStyle: DerivationStyle?, ): List { return blockchains.map { Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath) @@ -180,7 +186,7 @@ class TokensMiddleware { Currency.Token( it.token, it.blockchain, - it.blockchain.derivationPath(derivationStyle)?.rawPath + it.blockchain.derivationPath(derivationStyle)?.rawPath, ) } } @@ -192,7 +198,7 @@ class TokensMiddleware { ) { val derivationDataList = listOfNotNull( getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList), - getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList) + getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList), ) val derivations = derivationDataList.associate { it.derivations } if (derivations.isEmpty()) { @@ -265,29 +271,24 @@ class TokensMiddleware { return DerivationData( derivations = mapKeyOfWalletPublicKey to toDerive, alreadyDerivedKeys = alreadyDerivedKeys, - mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey + mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey, ) } private class DerivationData( val derivations: Pair>, val alreadyDerivedKeys: ExtendedPublicKeysMap, - val mapKeyOfWalletPublicKey: ByteArrayKey + val mapKeyOfWalletPublicKey: ByteArrayKey, ) - private fun submitAdd( - scanResponse: ScanResponse, - currencyList: List, - ) { + private fun submitAdd(scanResponse: ScanResponse, currencyList: List) { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { scope.launch { userWalletsListManager.update( userWalletId = selectedUserWallet.walletId, update = { userWallet -> - userWallet.copy( - scanResponse = scanResponse, - ) + userWallet.copy(scanResponse = scanResponse) }, ) .flatMap { updatedUserWallet -> @@ -305,7 +306,6 @@ class TokensMiddleware { when (currency) { is Currency.Blockchain -> { val derivationPath = currency.derivationPath?.let { DerivationPath(it) } - val derivationParams = derivationStyle?.let { when (derivationPath) { null -> DerivationParams.Default(derivationStyle) @@ -317,9 +317,8 @@ class TokensMiddleware { blockchain = currency.blockchain, derivationParams = derivationParams, ) ?: return@mapIndexedNotNull null - val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) WalletAction.MultiWallet.AddBlockchain( - blockchain = blockchainNetwork, + blockchain = BlockchainNetwork.fromWalletManager(walletManager), walletManager = walletManager, save = index == currencyList.lastIndex, ) @@ -392,12 +391,12 @@ class TokensMiddleware { when (it) { is Currency.Blockchain -> DomainWrapped.Currency.Blockchain( it.blockchain, - it.derivationPath + it.derivationPath, ) is Currency.Token -> DomainWrapped.Currency.Token( it.token, it.blockchain, - it.derivationPath + it.derivationPath, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt index 007a1e9711..e8ab2383d8 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt @@ -3,10 +3,8 @@ package com.tangem.tap.features.tokens.redux import com.tangem.tap.common.redux.AppState import org.rekotlin.Action -class TokensReducer { - companion object { - fun reduce(action: Action, state: AppState): TokensState = internalReduce(action, state) - } +object TokensReducer { + fun reduce(action: Action, state: AppState): TokensState = internalReduce(action, state) } private fun internalReduce(action: Action, state: AppState): TokensState { @@ -23,7 +21,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState { } tokensState.copy( scanResponse = action.scanResponse, - loadCoinsState = loadingState + loadCoinsState = loadingState, ) } is TokensAction.LoadCurrencies.Success -> { @@ -31,16 +29,15 @@ private fun internalReduce(action: Action, state: AppState): TokensState { currencies = tokensState.currencies + action.currencies, needToLoadMore = action.loadMore, pageToLoad = tokensState.pageToLoad + 1, - loadCoinsState = LoadCoinsState.LOADED + loadCoinsState = LoadCoinsState.LOADED, ) } is TokensAction.SetAddedCurrencies -> { - tokensState.copy( addedBlockchains = action.wallets.toNonCustomBlockchains(action.derivationStyle), addedTokens = action.wallets.toNonCustomTokensWithBlockchains(action.derivationStyle), addedWallets = action.wallets, - derivationStyle = action.derivationStyle + derivationStyle = action.derivationStyle, ) } is TokensAction.AllowToAddTokens -> { @@ -52,7 +49,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState { needToLoadMore = true, currencies = emptyList(), pageToLoad = 0, - loadCoinsState = LoadCoinsState.LOADING + loadCoinsState = LoadCoinsState.LOADING, ) } else -> tokensState diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt index ab1b3731a5..5c4f723467 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt @@ -9,6 +9,7 @@ import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.wallet.redux.WalletData import org.rekotlin.StateType +import com.tangem.tap.features.wallet.models.Currency.Token as CurrencyToken data class TokensState( val addedWallets: List = emptyList(), @@ -32,18 +33,18 @@ data class TokensState( typealias ContractAddress = String fun List.toTokensContractAddresses(): List { - return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.models.Currency.Token)?.token?.contractAddress }.distinct() + return mapNotNull { (it.currency as? CurrencyToken)?.token?.contractAddress }.distinct() } fun List.toNonCustomTokens(derivationStyle: DerivationStyle?): List { return filter { !it.currency.isCustomCurrency(derivationStyle) } - .mapNotNull { (it.currency as? com.tangem.tap.features.wallet.models.Currency.Token)?.token } + .mapNotNull { (it.currency as? CurrencyToken)?.token } .distinct() } fun List.toNonCustomTokensWithBlockchains(derivationStyle: DerivationStyle?): List { return mapNotNull { - if (it.currency !is com.tangem.tap.features.wallet.models.Currency.Token) return@mapNotNull null + if (it.currency !is CurrencyToken) return@mapNotNull null if (it.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null TokenWithBlockchain(it.currency.token, it.currency.blockchain) }.distinct() @@ -61,17 +62,18 @@ fun List.toNonCustomBlockchains(derivationStyle: DerivationStyle?): data class TokenWithBlockchain( val token: Token, - val blockchain: Blockchain + val blockchain: Blockchain, ) fun List.filter(supportedBlockchains: Set?): List { if (supportedBlockchains == null) return this return map { - it.copy(contracts = - it.contracts.filter { - supportedBlockchains.contains(it.blockchain) && - (it.blockchain.canHandleTokens() || it.address == null) - } + it.copy( + contracts = + it.contracts.filter { + supportedBlockchains.contains(it.blockchain) && + (it.blockchain.canHandleTokens() || it.address == null) + }, ) }.filterNot { it.contracts.isNullOrEmpty() && !supportedBlockchains.contains(Blockchain.fromNetworkId(it.id)) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 6550d26e1e..955d0e73fd 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -11,9 +11,9 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding -import com.google.accompanist.appcompattheme.AppCompatTheme import com.tangem.blockchain.common.Blockchain import com.tangem.core.analytics.Analytics +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchNotification @@ -72,11 +72,11 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc } cvCurrencies.setContent { - AppCompatTheme { + TangemTheme { CurrenciesScreen( tokensState = tokensState, onSaveChanges = onSaveChanges, - onNetworkItemClicked = onNetworkItemClicked, + onNetworkItemClick = onNetworkItemClicked, onLoadMore = onLoadMore, ) } @@ -102,6 +102,7 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc super.onDestroy() } + @Suppress("MagicNumber") override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.menu_popular_tokens, menu) @@ -167,5 +168,5 @@ fun SearchView.inputtedTextAsFlow(): Flow = callbackFlow { } }, ) - awaitClose { (watcher) } + awaitClose { watcher } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 09ddf515fc..88cc970852 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -1,3 +1,5 @@ +@file:Suppress("MagicNumber") + package com.tangem.tap.features.tokens.ui.compose import androidx.compose.animation.AnimatedVisibility @@ -7,12 +9,11 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Box 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.CircularProgressIndicator -import androidx.compose.material.ExtendedFloatingActionButton import androidx.compose.material.FabPosition import androidx.compose.material.Scaffold -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue @@ -26,16 +27,18 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.blockchain.common.Blockchain +import com.tangem.core.analytics.Analytics +import com.tangem.core.ui.components.Keyboard +import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.components.keyboardAsState +import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.ManageTokens -import com.tangem.tap.common.compose.Keyboard import com.tangem.tap.common.compose.extensions.addAndNotify import com.tangem.tap.common.compose.extensions.removeAndNotify -import com.tangem.tap.common.compose.keyboardAsState import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.pixelsToDp import com.tangem.tap.common.redux.AppDialog @@ -48,11 +51,12 @@ import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.store import com.tangem.wallet.R +@Suppress("LongMethod") @Composable fun CurrenciesScreen( - tokensState: MutableState = mutableStateOf(store.state.tokensState), + tokensState: MutableState, onSaveChanges: (List, List) -> Unit, - onNetworkItemClicked: (ContractAddress) -> Unit, + onNetworkItemClick: (ContractAddress) -> Unit, onLoadMore: () -> Unit, ) { val tokensAddedOnMainScreen = remember { tokensState.value.addedTokens } @@ -119,15 +123,14 @@ fun CurrenciesScreen( addedTokens = addedTokensState.value, addedBlockchains = addedBlockchainsState.value, allowToAdd = tokensState.value.allowToAdd, - onAddCurrencyToggled = { currency, token -> + onAddCurrencyToggle = { currency, token -> onAddCurrencyToggleClick(currency, token) }, - onNetworkItemClicked = onNetworkItemClicked, + onNetworkItemClick = onNetworkItemClick, onLoadMore = onLoadMore, ) } } - } } @@ -221,22 +224,18 @@ private fun AnalyticsParam.CurrencyType.sendOff() { @Composable fun SaveChangesButton(keyboardState: Keyboard, onSaveChanges: () -> Unit) { - val padding = if (keyboardState is Keyboard.Opened) { LocalContext.current.pixelsToDp(keyboardState.height) } else { 0 } - ExtendedFloatingActionButton( - text = { - Text( - text = stringResource(id = R.string.common_save_changes), - ) - }, + PrimaryButton( + modifier = Modifier + .padding(bottom = padding.dp) + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(id = R.string.common_save_changes), onClick = onSaveChanges, - backgroundColor = colorResource(id = R.color.accent), - contentColor = Color.White, - modifier = Modifier.padding(bottom = padding.dp), ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesWarning.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesWarning.kt index b5c11c60b8..bfd230a1c7 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesWarning.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesWarning.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tangem.wallet.R +@Suppress("MagicNumber") @Composable fun CurrenciesWarning() { Box( @@ -28,8 +29,7 @@ fun CurrenciesWarning() { color = Color(0xFFF2F2F2), shape = RoundedCornerShape(10.dp), ), - ) - { + ) { val warning = stringResource(id = R.string.alert_manage_tokens_addresses_message) val end = warning.indexOf(" ") val spanStyles = listOf( diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrencyPlaceholderIcon.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrencyPlaceholderIcon.kt index cb00117ffa..c62f31075e 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrencyPlaceholderIcon.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrencyPlaceholderIcon.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.unit.sp @Composable fun CurrencyPlaceholderIcon(id: String) { - val letterColor: Color = Color.White val circleColor: Color = Color.Black @@ -24,8 +23,7 @@ fun CurrencyPlaceholderIcon(id: String) { contentAlignment = Alignment.Center, modifier = Modifier .background(circleColor, shape = CircleShape) - ) - { + ) { Text( text = id.firstOrNull()?.titlecase() ?: "", textAlign = TextAlign.Center, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt index 613f741c55..1c5ba31d04 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt @@ -19,6 +19,7 @@ import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain import com.tangem.tap.features.tokens.ui.compose.currencyItem.CurrencyItem +@Suppress("LongParameterList") @Composable fun ListOfCurrencies( header: @Composable () -> Unit, @@ -26,11 +27,10 @@ fun ListOfCurrencies( addedTokens: List, addedBlockchains: List, allowToAdd: Boolean, - onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit, - onNetworkItemClicked: (ContractAddress) -> Unit, + onAddCurrencyToggle: (Currency, TokenWithBlockchain?) -> Unit, + onNetworkItemClick: (ContractAddress) -> Unit, onLoadMore: () -> Unit, ) { - val expandedCurrencies = remember { mutableStateOf(listOf("")) } val onCurrencyClick = { currencyId: String -> @@ -55,16 +55,16 @@ fun ListOfCurrencies( verticalArrangement = Arrangement.spacedBy(12.dp), ) { item { header() } - itemsIndexed(currencies) { index, currency -> + itemsIndexed(currencies) { _, currency -> CurrencyItem( currency = currency, addedTokens = addedTokens, addedBlockchains = addedBlockchains, allowToAdd = allowToAdd, isExpanded = expandedCurrencies.value.contains(currency.id), - onCurrencyClick = onCurrencyClick, - onAddCurrencyToggled = onAddCurrencyToggled, - onNetworkItemClicked = onNetworkItemClicked, + onCurrencyClick = { onCurrencyClick(currency.id) }, + onAddCurrencyToggle = onAddCurrencyToggle, + onNetworkItemClick = onNetworkItemClick, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt index c4b1b21abe..4783c5ac67 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt @@ -15,6 +15,7 @@ import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain +@Suppress("LongParameterList") @Composable fun CurrencyExpandedContent( currency: Currency, @@ -22,8 +23,8 @@ fun CurrencyExpandedContent( addedBlockchains: List, allowToAdd: Boolean, isExpanded: Boolean, - onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit, - onNetworkItemClicked: (ContractAddress) -> Unit, + onAddCurrencyToggle: (Currency, TokenWithBlockchain?) -> Unit, + onNetworkItemClick: (ContractAddress) -> Unit, ) { AnimatedVisibility( visible = isExpanded, @@ -50,8 +51,8 @@ fun CurrencyExpandedContent( added = added, index = index, size = blockchains.size, - onAddCurrencyToggled = onAddCurrencyToggled, - onNetworkItemClicked = onNetworkItemClicked, + onAddCurrencyToggle = onAddCurrencyToggle, + onNetworkItemClick = onNetworkItemClick, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt index 6758c71e82..c2aa9f238f 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt @@ -7,6 +7,7 @@ import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain +@Suppress("LongParameterList") @Composable fun CurrencyItem( currency: Currency, @@ -14,9 +15,9 @@ fun CurrencyItem( addedBlockchains: List, allowToAdd: Boolean, isExpanded: Boolean, - onCurrencyClick: (String) -> Unit, - onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit, - onNetworkItemClicked: (ContractAddress) -> Unit, + onCurrencyClick: () -> Unit, + onAddCurrencyToggle: (Currency, TokenWithBlockchain?) -> Unit, + onNetworkItemClick: (ContractAddress) -> Unit, ) { Column { CurrencyItemHeader( @@ -32,8 +33,8 @@ fun CurrencyItem( addedBlockchains = addedBlockchains, allowToAdd = allowToAdd, isExpanded = isExpanded, - onAddCurrencyToggled = onAddCurrencyToggled, - onNetworkItemClicked = onNetworkItemClicked, + onAddCurrencyToggle = onAddCurrencyToggle, + onNetworkItemClick = onNetworkItemClick, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt index 4be02043d5..3c0c5655cc 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt @@ -36,6 +36,7 @@ fun CurrencyItemArrow( } } +@Suppress("MagicNumber") @Composable private fun MiddleArrowView(rowHeight: Dp) { Box { @@ -67,11 +68,8 @@ private fun LastArrowView(rowHeight: Dp) { @Preview @Composable -fun ArrowViewPreview() { - Box( - Modifier - .background(color = Color.White), - ) { +private fun ArrowViewPreview() { + Box(Modifier.background(color = Color.White)) { Column( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt index b029341439..bcc681addb 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt @@ -31,22 +31,24 @@ import androidx.compose.ui.unit.sp import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.common.extensions.getGreyedOutIconRes -import com.tangem.tap.common.extensions.getRoundIconRes +import com.tangem.tap.domain.tokens.Contract import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.TokenWithBlockchain import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon import com.tangem.tap.features.tokens.ui.compose.fullName import com.tangem.wallet.R +@Suppress("LongMethod", "MagicNumber") @Composable fun CurrencyItemHeader( currency: Currency, addedTokens: List, addedBlockchains: List, isExpanded: Boolean, - onCurrencyClick: (String) -> Unit, + onCurrencyClick: () -> Unit, ) { Row( modifier = Modifier @@ -55,7 +57,7 @@ fun CurrencyItemHeader( .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null, - onClick = { onCurrencyClick(currency.id) }, + onClick = onCurrencyClick, ), ) { Box( @@ -140,9 +142,9 @@ private fun NetworksRow( addedBlockchains = addedBlockchains, ) } else { - val isAdded = addedTokens.map { it.token.contractAddress }.contains(contract.address) + val isAdded = addedTokens.any(contract::isInclude) val iconResId = if (isAdded) { - contract.blockchain.getRoundIconRes() + getActiveIconRes(contract.blockchain.id) } else { contract.blockchain.getGreyedOutIconRes() } @@ -154,7 +156,6 @@ private fun NetworksRow( ) Spacer(Modifier.size(5.dp)) } - } } else { BlockchainNetworkItem( @@ -166,6 +167,7 @@ private fun NetworksRow( } } +@Suppress("MagicNumber") @Composable private fun BlockchainNetworkItem( blockchain: Blockchain?, @@ -173,7 +175,7 @@ private fun BlockchainNetworkItem( addedBlockchains: List, ) { val added = addedBlockchains.contains(blockchain) - val icon = if (added) blockchain?.getRoundIconRes() else blockchain?.getGreyedOutIconRes() + val icon = if (added) blockchain?.let { getActiveIconRes(it.id) } else blockchain?.getGreyedOutIconRes() if (icon != null) { Box( Modifier @@ -205,4 +207,7 @@ private fun BlockchainNetworkItem( } Spacer(modifier = Modifier.size(5.dp)) } -} \ No newline at end of file +} + +private fun Contract.isInclude(addedToken: TokenWithBlockchain): Boolean = + address == addedToken.token.contractAddress && blockchain == addedToken.blockchain \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt index 2928ace0d8..cf744085ba 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -31,16 +31,17 @@ import androidx.compose.ui.unit.sp import coil.compose.SubcomposeAsyncImage import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.tap.common.extensions.fullNameWithoutTestnet import com.tangem.tap.common.extensions.getGreyedOutIconRes import com.tangem.tap.common.extensions.getNetworkName -import com.tangem.tap.common.extensions.getRoundIconRes import com.tangem.tap.domain.tokens.Contract import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon +@Suppress("LongParameterList", "LongMethod", "MagicNumber") @OptIn(ExperimentalFoundationApi::class) @Composable fun NetworkItem( @@ -51,19 +52,19 @@ fun NetworkItem( added: Boolean, index: Int, size: Int, - onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit, - onNetworkItemClicked: (ContractAddress) -> Unit, + onAddCurrencyToggle: (Currency, TokenWithBlockchain?) -> Unit, + onNetworkItemClick: (ContractAddress) -> Unit, ) { val rowHeight = 53.dp Row( modifier = Modifier .fillMaxWidth() - .height(rowHeight) + .heightIn(rowHeight) .combinedClickable( enabled = allowToAdd, onLongClick = { - contract.address?.let { onNetworkItemClicked(it) } + contract.address?.let { onNetworkItemClick(it) } }, onClick = {}, indication = null, @@ -85,7 +86,7 @@ fun NetworkItem( .align(Alignment.CenterVertically), ) { SubcomposeAsyncImage( - model = if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes(), + model = if (added) getActiveIconRes(blockchain.id) else blockchain.getGreyedOutIconRes(), contentDescription = blockchain.fullName, loading = { CurrencyPlaceholderIcon(blockchain.id) }, error = { CurrencyPlaceholderIcon(blockchain.id) }, @@ -149,7 +150,7 @@ fun NetworkItem( .fillMaxHeight() .padding(start = 16.dp, end = 16.dp), checked = added, - onCheckedChange = { onAddCurrencyToggled(currencyToSave, tokenWithBlockchain) }, + onCheckedChange = { onAddCurrencyToggle(currencyToSave, tokenWithBlockchain) }, colors = SwitchDefaults.colors( checkedThumbColor = Color(0xFF1ACE80), ), @@ -158,12 +159,12 @@ fun NetworkItem( } } +@Suppress("MagicNumber") @Composable private fun prepareNetworkNameSpannableText( blockchain: Blockchain, contractAddress: String?, ): AnnotatedString { - val blockchainName = blockchain.fullNameWithoutTestnet.uppercase() val additionalText = if (contractAddress == null) "MAIN" else blockchain.getNetworkName().uppercase() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt new file mode 100644 index 0000000000..e610e03389 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/data/WalletRepositoryImpl.kt @@ -0,0 +1,23 @@ +package com.tangem.tap.features.wallet.data + +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse +import com.tangem.tap.features.wallet.domain.WalletRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +/** + * Implementation of repository for Wallet feature + * + * @property tangemTechApi API for server requests + * @property dispatchers coroutine dispatcher provider + */ +class WalletRepositoryImpl( + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, +) : WalletRepository { + + override suspend fun getCurrencyList(): CurrenciesResponse = withContext(dispatchers.io) { + tangemTechApi.getCurrencyList() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/domain/WalletRepository.kt b/app/src/main/java/com/tangem/tap/features/wallet/domain/WalletRepository.kt new file mode 100644 index 0000000000..c3ba927d31 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/domain/WalletRepository.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.features.wallet.domain + +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse + +/** Repository for Wallet feature */ +interface WalletRepository { + + /** Get list of currency */ + suspend fun getCurrencyList(): CurrenciesResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 146d622d16..c67c02d950 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -1,11 +1,10 @@ package com.tangem.tap.features.wallet.models import com.tangem.blockchain.common.DerivationStyle +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toCoinId -import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.features.addCustomToken.CustomCurrency -import com.tangem.datasource.api.tangemTech.TokenResponse import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.tokens.redux.TokenWithBlockchain @@ -57,17 +56,6 @@ sealed interface Currency { fun isBlockchain(): Boolean = this is Blockchain fun isToken(): Boolean = this is Token - fun toTokenResponse(): TokenResponse { - return TokenResponse( - id = coinId, - networkId = blockchain.toNetworkId(), - derivationPath = derivationPath, - name = currencyName, - symbol = currencySymbol, - decimals = decimals, - contractAddress = if (this is Token) token.contractAddress else null, - ) - } companion object { fun fromBlockchainNetwork( @@ -110,24 +98,24 @@ sealed interface Currency { ) } - fun fromTokenResponse(tokenResponse: TokenResponse): Currency? { - val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId) + fun fromTokenResponse(tokenBody: UserTokensResponse.Token): Currency? { + val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenBody.networkId) ?: return null return when { - tokenResponse.contractAddress != null -> Token( + tokenBody.contractAddress != null -> Token( token = SdkToken( - name = tokenResponse.name, - symbol = tokenResponse.symbol, - contractAddress = tokenResponse.contractAddress!!, - decimals = tokenResponse.decimals, - id = tokenResponse.id, + name = tokenBody.name, + symbol = tokenBody.symbol, + contractAddress = tokenBody.contractAddress!!, + decimals = tokenBody.decimals, + id = tokenBody.id, ), blockchain = blockchain, - derivationPath = tokenResponse.derivationPath, + derivationPath = tokenBody.derivationPath, ) else -> Blockchain( blockchain = blockchain, - derivationPath = tokenResponse.derivationPath, + derivationPath = tokenBody.derivationPath, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt index f2cd8d24b1..0e3aea8db6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt @@ -26,7 +26,7 @@ data class PendingTransaction( val currency: String = transactionData.amount.currencySymbol - fun nullIfUnknown(address: String):String? = if (address == "unknown") null else address + fun nullIfUnknown(address: String): String? = if (address == "unknown") null else address } enum class PendingTransactionType { Incoming, Outgoing, Unknown } @@ -99,7 +99,6 @@ fun Wallet.hasSendableAmountsOrPendingTransactions(): Boolean { fun Wallet.isSendableAmount(type: AmountType): Boolean { return amounts[type]?.isAboveZero() == true - } fun Wallet.isSendableAmount(token: Token): Boolean { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt index e2709d2bb9..951d4f1e10 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt @@ -1,24 +1,23 @@ package com.tangem.tap.features.wallet.models -sealed class WalletWarning( - val showingPosition: Int, -) { +sealed class WalletWarning(val showingPosition: Int) { + data class ExistentialDeposit( val currencyName: String, val edStringValueWithSymbol: String, ) : WalletWarning(1) - data class TransactionInProgress(val currencyName: String) : WalletWarning(10) - data class BalanceNotEnoughForFee(val blockchainFullName: String) : WalletWarning(30) - data class Rent(val walletRent: WalletRent) : WalletWarning(40) + data class TransactionInProgress(val currencyName: String) : WalletWarning(showingPosition = 10) + + data class BalanceNotEnoughForFee( + val currencyName: String, + val blockchainFullName: String, + val blockchainSymbol: String, + ) : WalletWarning(showingPosition = 30) + + data class Rent(val walletRent: WalletRent) : WalletWarning(showingPosition = 40) } -data class WalletWarningDescription( - val title: String, - val message: String, -) +data class WalletWarningDescription(val title: String, val message: String) -data class WalletRent( - val minRentValue: String, - val rentExemptValue: String, -) \ No newline at end of file +data class WalletRent(val minRentValue: String, val rentExemptValue: String) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index af3edbfebd..f839eb03fe 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -81,7 +81,8 @@ sealed class WalletAction : Action { ) : MultiWallet() data class SaveCurrencies( - val blockchainNetworks: List, val card: CardDTO? = null, + val blockchainNetworks: List, + val card: CardDTO? = null, ) : MultiWallet() data class TokenLoaded( @@ -125,7 +126,8 @@ sealed class WalletAction : Action { } data class LoadFiatRate( - val wallet: Wallet? = null, val coinsList: List? = null, + val wallet: Wallet? = null, + val coinsList: List? = null, ) : WalletAction() { data class Success( val fiatRates: Map, @@ -160,7 +162,12 @@ sealed class WalletAction : Action { ) : DialogAction() object SignedHashesMultiWalletDialog : DialogAction() - object ChooseTradeActionDialog : DialogAction() + data class ChooseTradeActionDialog( + val buyAllowed: Boolean, + val sellAllowed: Boolean, + val swapAllowed: Boolean, + ) : DialogAction() + data class ChooseCurrency(val amounts: List?) : DialogAction() data class RussianCardholdersWarningDialog( val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null, @@ -189,6 +196,8 @@ sealed class WalletAction : Action { val destinationAddress: String, val transactionId: String, ) : TradeCryptoAction() + + object Swap : TradeCryptoAction() } data class ChangeSelectedAddress(val type: AddressType) : WalletAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt new file mode 100644 index 0000000000..a2fc562b3a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt @@ -0,0 +1,118 @@ +package com.tangem.tap.features.wallet.redux + +import com.tangem.common.extensions.isZero +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.feature.swap.domain.SwapInteractor +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.PendingTransaction +import com.tangem.tap.features.wallet.models.WalletRent +import com.tangem.tap.features.wallet.models.WalletWarning +import com.tangem.tap.features.wallet.ui.BalanceStatus +import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager +import java.math.BigDecimal + +data class WalletData( + val pendingTransactions: List = emptyList(), + val hashesCountVerified: Boolean? = null, + val walletAddresses: WalletAddresses? = null, + val currencyData: BalanceWidgetData = BalanceWidgetData(), + val updatingWallet: Boolean = false, + val fiatRateString: String? = null, + val fiatRate: BigDecimal? = null, + val mainButton: WalletMainButton = WalletMainButton.SendButton(false), + val currency: Currency, + val walletRent: WalletRent? = null, + val existentialDepositString: String? = null, +) { + fun isAvailableToBuy(exchangeManager: CurrencyExchangeManager): Boolean { + return exchangeManager.availableForBuy(currency) + } + + fun isAvailableToSell(exchangeManager: CurrencyExchangeManager): Boolean { + return exchangeManager.availableForSell(currency) + } + + fun isAvailableToSwap(swapInteractor: SwapInteractor, isExchangeFeatureOn: Boolean): Boolean { + return swapInteractor.isAvailableToSwap(currency.blockchain.toNetworkId()) && + isExchangeFeatureOn && + !currency.isCustomCurrency(null) + } + + fun getAvailableActions( + swapInteractor: SwapInteractor, + exchangeManager: CurrencyExchangeManager, + isExchangeFeatureOn: Boolean, + ): Set { + return setOfNotNull( + if (isAvailableToBuy(exchangeManager)) CurrencyAction.Buy else null, + if (isAvailableToSell(exchangeManager)) CurrencyAction.Sell else null, + if (isAvailableToSwap(swapInteractor, isExchangeFeatureOn)) CurrencyAction.Swap else null, + ) + } + + fun shouldShowMultipleAddress(): Boolean { + val listOfAddresses = walletAddresses?.list ?: return false + return listOfAddresses.size > 1 + } + + fun shouldEnableTokenSendButton(): Boolean = if (blockchainAmountIsEmpty()) { + false + } else { + !tokenAmountIsEmpty() + } + + fun assembleWarnings(): List { + val walletWarnings = mutableListOf() + assembleNonTypedWarnings(walletWarnings) + assembleBlockchainWarnings(walletWarnings) + assembleTokenWarnings(walletWarnings) + + return walletWarnings.sortedBy { it.showingPosition } + } + + private fun assembleNonTypedWarnings(walletWarnings: MutableList) { + if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) { + walletWarnings.add(WalletWarning.TransactionInProgress(currency.currencyName)) + } + } + + private fun assembleBlockchainWarnings(walletWarnings: MutableList) { + if (!currency.isBlockchain()) return + + if (existentialDepositString != null) { + val warning = WalletWarning.ExistentialDeposit( + currencyName = currency.currencyName, + edStringValueWithSymbol = "$existentialDepositString ${currency.currencySymbol}", + ) + walletWarnings.add(warning) + } + if (walletRent != null) { + walletWarnings.add(WalletWarning.Rent(walletRent)) + } + } + + private fun assembleTokenWarnings(walletWarnings: MutableList) { + with(currency) { + if (!isToken()) return + + if (blockchainAmountIsEmpty() && !tokenAmountIsEmpty()) { + walletWarnings.add( + WalletWarning.BalanceNotEnoughForFee( + currencyName = currencyName, + blockchainFullName = blockchain.fullName, + blockchainSymbol = blockchain.currency, + ), + ) + } + } + } + + private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() == true + + private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true +} + +enum class CurrencyAction { + Buy, Sell, Swap +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 1b2a47181a..44c24784e2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -6,7 +6,6 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType -import com.tangem.common.extensions.isZero import com.tangem.tap.common.entities.Button import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.redux.global.CryptoCurrencyName @@ -16,18 +15,13 @@ import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.models.TotalBalance -import com.tangem.tap.features.wallet.models.WalletRent -import com.tangem.tap.features.wallet.models.WalletWarning import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount import com.tangem.tap.features.wallet.redux.reducers.findProgressState import com.tangem.tap.features.wallet.ui.BalanceStatus -import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.store import com.tangem.tap.userWalletsListManager import org.rekotlin.StateType -import java.math.BigDecimal import kotlin.properties.ReadOnlyProperty /** @@ -66,7 +60,7 @@ data class WalletState( } val isTangemTwins: Boolean - get() = store.state.globalState.scanResponse?.isTangemTwins() == true + get() = store.state.globalState.scanResponse?.cardTypesResolver?.isTangemTwins() == true val isExchangeServiceFeatureOn: Boolean get() = store.state.globalState.exchangeManager.featureIsSwitchedOn() @@ -130,7 +124,7 @@ data class WalletState( if (currency == null) return null return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == currency.derivationPath && - (it.blockchainNetwork.blockchain == currency.blockchain) + it.blockchainNetwork.blockchain == currency.blockchain } } @@ -145,7 +139,7 @@ data class WalletState( if (blockchainNetwork == null) return null return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath && - (it.blockchainNetwork.blockchain == blockchainNetwork.blockchain) + it.blockchainNetwork.blockchain == blockchainNetwork.blockchain } } @@ -210,8 +204,8 @@ data class WalletState( return when (val currency = walletData.currency) { is Currency.Blockchain -> { val walletStores = walletsStores.filterNot { - it.blockchainNetwork.blockchain == currency.blockchain - && it.blockchainNetwork.derivationPath == currency.derivationPath + it.blockchainNetwork.blockchain == currency.blockchain && + it.blockchainNetwork.derivationPath == currency.derivationPath } copy(walletsStores = walletStores) .updateTotalBalance() @@ -244,9 +238,9 @@ data class WalletState( fiatCurrency = store.state.globalState.appCurrency, ), ) - } else this.copy( - totalBalance = null, - ) + } else { + this.copy(totalBalance = null) + } } private fun updateProgressState(): WalletState { @@ -323,80 +317,6 @@ data class Artwork( } } -data class WalletData( - val pendingTransactions: List = emptyList(), - val hashesCountVerified: Boolean? = null, - val walletAddresses: WalletAddresses? = null, - val currencyData: BalanceWidgetData = BalanceWidgetData(), - val updatingWallet: Boolean = false, - val fiatRateString: String? = null, - val fiatRate: BigDecimal? = null, - val mainButton: WalletMainButton = WalletMainButton.SendButton(false), - val currency: Currency, - val walletRent: WalletRent? = null, - val existentialDepositString: String? = null, -) { - val isAvailableToBuy: Boolean - get() = store.state.globalState.exchangeManager.availableForBuy(currency) - - val isAvailableToSell: Boolean - get() = store.state.globalState.exchangeManager.availableForSell(currency) - - fun shouldShowMultipleAddress(): Boolean { - val listOfAddresses = walletAddresses?.list ?: return false - return listOfAddresses.size > 1 - } - - fun shouldEnableTokenSendButton(): Boolean = if (blockchainAmountIsEmpty()) { - false - } else { - !tokenAmountIsEmpty() - } - - fun assembleWarnings(): List { - val walletWarnings = mutableListOf() - assembleNonTypedWarnings(walletWarnings) - assembleBlockchainWarnings(walletWarnings) - assembleTokenWarnings(walletWarnings) - - return walletWarnings.sortedBy { it.showingPosition } - } - - private fun assembleNonTypedWarnings(walletWarnings: MutableList) { - if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) { - walletWarnings.add(WalletWarning.TransactionInProgress(currency.currencyName)) - } - } - - private fun assembleBlockchainWarnings(walletWarnings: MutableList) { - if (!currency.isBlockchain()) return - - if (existentialDepositString != null) { - val warning = WalletWarning.ExistentialDeposit( - currencyName = currency.currencyName, - edStringValueWithSymbol = "$existentialDepositString ${currency.currencySymbol}", - ) - walletWarnings.add(warning) - } - if (walletRent != null) { - walletWarnings.add(WalletWarning.Rent(walletRent)) - } - } - - private fun assembleTokenWarnings(walletWarnings: MutableList) { - if (!currency.isToken()) return - - val blockchainFullName = currency.blockchain.fullName - if ((blockchainAmountIsEmpty() && !tokenAmountIsEmpty())) { - walletWarnings.add(WalletWarning.BalanceNotEnoughForFee(blockchainFullName)) - } - } - - private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() == true - - private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true -} - data class WalletStore( val walletManager: WalletManager?, val blockchainNetwork: BlockchainNetwork, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index 5b05b971d0..a2b7aa110e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -1,8 +1,6 @@ package com.tangem.tap.features.wallet.redux.middlewares -import com.tangem.common.services.Result -import com.tangem.datasource.api.tangemTech.CurrenciesResponse -import com.tangem.datasource.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen @@ -11,6 +9,7 @@ import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.features.wallet.domain.WalletRepository import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction @@ -21,7 +20,7 @@ import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch class AppCurrencyMiddleware( - private val tangemTechService: TangemTechService, + private val walletRepository: WalletRepository, private val tapWalletManager: TapWalletManager, private val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage, private val appCurrencyProvider: () -> FiatCurrency, @@ -39,29 +38,25 @@ class AppCurrencyMiddleware( store.dispatchDialogShow( WalletDialog.CurrencySelectionDialog( currenciesList = storedFiatCurrencies.mapToUiModel(), - currentAppCurrency = appCurrencyProvider.invoke() + currentAppCurrency = appCurrencyProvider.invoke(), ) ) } scope.launch { - when (val result = tangemTechService.currencies()) { - is Result.Success -> { - val currenciesList = result.data.currencies - if (currenciesList.isNotEmpty() && - currenciesList.toSet() != storedFiatCurrencies.toSet() - ) { + runCatching { walletRepository.getCurrencyList() } + .onSuccess { + val currenciesList = it.currencies + if (currenciesList.isNotEmpty() && !currenciesList.toSet().equals(storedFiatCurrencies.toSet())) { fiatCurrenciesPrefStorage.save(currenciesList) store.dispatchDialogShow( WalletDialog.CurrencySelectionDialog( currenciesList = currenciesList.mapToUiModel(), - currentAppCurrency = appCurrencyProvider.invoke() + currentAppCurrency = appCurrencyProvider.invoke(), ) ) } } - is Result.Failure -> {} - } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt index 91bf93586e..43667b8a7b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt @@ -49,6 +49,7 @@ internal fun WalletStoreModel.mapToReduxModel(): WalletStore { .updateTokenModels(blockchainWalletData.status.amount) } +@Suppress("LongMethod", "ComplexMethod") private fun List.mapToReduxModels( walletRent: WalletStoreModel.WalletRent?, appCurrencySymbol: String, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index e6798ea6ca..1f322a6152 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -41,8 +41,11 @@ import kotlinx.coroutines.withContext import java.math.BigDecimal class MultiWalletMiddleware { + @Suppress("LongMethod", "ComplexMethod") fun handle( - action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?, + action: WalletAction.MultiWallet, + walletState: WalletState?, + globalState: GlobalState?, ) { val globalState = globalState ?: return @@ -68,7 +71,6 @@ class MultiWalletMiddleware { val currencies: List = (walletState?.currencies ?: emptyList()) + action.blockchain.toCurrencies() - if (action.save && globalState.scanResponse != null) { scope.launch { userTokensRepository.saveUserTokens( @@ -90,7 +92,8 @@ class MultiWalletMiddleware { ) store.dispatch( WalletAction.LoadWallet( - action.blockchain, action.walletManager, + blockchain = action.blockchain, + walletManager = action.walletManager, ), ) } @@ -128,11 +131,13 @@ class MultiWalletMiddleware { } is WalletAction.MultiWallet.RemoveWallet -> { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) scope.launch { - walletCurrenciesManager.removeCurrency( - userWallet = selectedUserWallet, - currencyToRemove = action.currency, - ) + if (selectedUserWallet != null) { + scope.launch { + walletCurrenciesManager.removeCurrency( + userWallet = selectedUserWallet, + currencyToRemove = action.currency, + ) + } } else { val currency = action.currency val card = globalState.scanResponse?.card.guard { @@ -143,8 +148,9 @@ class MultiWalletMiddleware { var currencies = walletState?.currencies ?: emptyList() currencies = currencies.filterNot { it == currency } if (currency.isBlockchain()) { - currencies - .filter { it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath } + currencies.filter { + it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath + } } scope.launch { userTokensRepository.saveUserTokens(card, currencies) } } @@ -178,6 +184,7 @@ class MultiWalletMiddleware { store.dispatch(WalletAction.Scan(onScanSuccessEvent = null)) } } + else -> {} } } @@ -225,8 +232,10 @@ class MultiWalletMiddleware { } private fun addTokens( - tokens: List, blockchainNetwork: BlockchainNetwork, - walletState: WalletState?, globalState: GlobalState?, + tokens: List, + blockchainNetwork: BlockchainNetwork, + walletState: WalletState?, + globalState: GlobalState?, save: Boolean, ) { if (tokens.isEmpty()) return @@ -247,7 +256,9 @@ class MultiWalletMiddleware { WalletAction.LoadFiatRate( coinsList = tokens.map { token -> Currency.Token( - token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath, + token, + blockchainNetwork.blockchain, + blockchainNetwork.derivationPath, ) }, ), 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 33b7f54788..1afc0422ec 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 @@ -1,8 +1,12 @@ package com.tangem.tap.features.wallet.redux.middlewares +import androidx.core.os.bundleOf import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.AmountType import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.extensions.toCoinId +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -11,6 +15,7 @@ import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.tokens.getIconUrl import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.send.redux.PrepareSendScreen @@ -22,6 +27,9 @@ import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import com.tangem.feature.swap.domain.models.domain.Currency as SwapCurrency class TradeCryptoMiddleware { fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) { @@ -32,6 +40,7 @@ class TradeCryptoMiddleware { is WalletAction.TradeCryptoAction.Sell -> proceedSellAction() is WalletAction.TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action) is WalletAction.TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) + is WalletAction.TradeCryptoAction.Swap -> openSwap() } } @@ -137,4 +146,33 @@ class TradeCryptoMiddleware { transactionId = transactionId, )?.let { store.dispatchOpenUrl(it) } } + + private fun openSwap() { + val currency = store.state.walletState.selectedWalletData?.currency?.toSwapCurrency() + val bundle = bundleOf(SwapFragment.CURRENCY_BUNDLE_KEY to Json.encodeToString(currency)) + store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle)) + } + + private fun Currency.toSwapCurrency(): SwapCurrency { + return when (this) { + is Currency.Blockchain -> { + SwapCurrency.NativeToken( + id = blockchain.toCoinId(), + name = this.currencyName, + symbol = this.currencySymbol, + networkId = this.blockchain.toNetworkId(), + logoUrl = getIconUrl(this.blockchain.toCoinId()), + ) + } + is Currency.Token -> SwapCurrency.NonNativeToken( + id = this.token.id ?: "", + name = this.currencyName, + symbol = this.currencySymbol, + networkId = this.blockchain.toNetworkId(), + logoUrl = getIconUrl(this.token.id ?: ""), + contractAddress = this.token.contractAddress, + decimalCount = decimals, + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index 262f1780fc..c21a15ce98 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -20,7 +20,13 @@ class WalletDialogsMiddleware { store.state.walletState.selectedWalletData?.let { Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency))) } - store.dispatchDialogShow(WalletDialog.ChooseTradeActionDialog) + store.dispatchDialogShow( + WalletDialog.ChooseTradeActionDialog( + buyAllowed = action.buyAllowed, + sellAllowed = action.sellAllowed, + swapAllowed = action.swapAllowed, + ), + ) } is WalletAction.DialogAction.QrCode -> { store.dispatchDialogShow( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index b49a33c1f1..07953a62ea 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -67,11 +67,11 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action -import org.rekotlin.DispatchFunction import org.rekotlin.Middleware import timber.log.Timber import java.math.BigDecimal +@Suppress("LargeClass") class WalletMiddleware { private val tradeCryptoMiddleware = TradeCryptoMiddleware() private val warningsMiddleware = WarningsMiddleware() @@ -79,23 +79,25 @@ class WalletMiddleware { private val walletDialogMiddleware = WalletDialogsMiddleware() private val appCurrencyMiddleware by lazy(mode = LazyThreadSafetyMode.NONE) { AppCurrencyMiddleware( - tangemTechService = store.state.domainNetworks.tangemTechService, + // TODO("After adding DI") get dependencies by DI + walletRepository = store.state.featureRepositoryProvider.walletRepository, tapWalletManager = store.state.globalState.tapWalletManager, fiatCurrenciesPrefStorage = preferencesStorage.fiatCurrenciesPrefStorage, appCurrencyProvider = { store.state.globalState.appCurrency }, ) } - val walletMiddleware: Middleware = { dispatch, state -> + val walletMiddleware: Middleware = { _, state -> { next -> { action -> - handleAction(state, action, dispatch) + handleAction(state, action) next(action) } } } - private fun handleAction(state: () -> AppState?, action: Action, dispatch: DispatchFunction) { + @Suppress("LongMethod", "ComplexMethod") + private fun handleAction(state: () -> AppState?, action: Action) { if (DemoHelper.tryHandle(state, action)) return val globalState = store.state.globalState @@ -124,17 +126,15 @@ class WalletMiddleware { is WalletAction.LoadWallet.Success -> { checkForRentWarning(walletState.getWalletManager(action.blockchain)) val coinAmount = action.wallet.amounts[AmountType.Coin]?.value - if (coinAmount != null && !coinAmount.isZero()) { - if (walletState.getWalletData(action.blockchain) == null) { - store.dispatch( - WalletAction.MultiWallet.AddBlockchain( - action.blockchain, - null, - true, - ), - ) - store.dispatch(WalletAction.LoadWallet.Success(action.wallet, action.blockchain)) - } + if (coinAmount?.isZero() == false && walletState.getWalletData(action.blockchain) == null) { + store.dispatch( + WalletAction.MultiWallet.AddBlockchain( + blockchain = action.blockchain, + walletManager = null, + save = true, + ), + ) + store.dispatch(WalletAction.LoadWallet.Success(action.wallet, action.blockchain)) } store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) warningsMiddleware.tryToShowAppRatingWarning(action.wallet) @@ -203,7 +203,7 @@ class WalletMiddleware { is WalletAction.Scan -> { store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) scope.launch { - delay(700) + delay(timeMillis = 700) store.dispatchOnMain(HomeAction.ReadCard(action.onScanSuccessEvent)) } } @@ -343,7 +343,7 @@ class WalletMiddleware { .filter { store -> store.walletsData.any { it.status is WalletDataModel.MissedDerivation } } - .map { it.blockchainNetwork } + .map(WalletStoreModel::blockchainNetwork) store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(missedDerivations)) } @@ -360,9 +360,9 @@ class WalletMiddleware { } private fun showSaveWalletIfNeeded() { - if (preferencesStorage.shouldShowSaveUserWalletScreen - && tangemSdkManager.canUseBiometry - && store.state.navigationState.backStack.lastOrNull() == AppScreen.Wallet + if (preferencesStorage.shouldShowSaveUserWalletScreen && + tangemSdkManager.canUseBiometry && + store.state.navigationState.backStack.lastOrNull() == AppScreen.Wallet ) { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) } @@ -417,7 +417,8 @@ class WalletMiddleware { } } } else { - if ((amounts?.size ?: 0) > 1) { + val amountsSize = amounts?.size ?: 0 + if (amountsSize > 1) { WalletAction.DialogAction.ChooseCurrency(amounts) } else { val amountToSend = amounts?.first() @@ -479,8 +480,8 @@ class WalletMiddleware { dispatchOnMain( WalletAction.SetWalletRent( wallet = walletManager.wallet, - minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"), - rentExempt = ("${rentExempt.stripZeroPlainString()} $currency"), + minRent = "${rentProvider.rentAmount().stripZeroPlainString()} $currency", + rentExempt = "${rentExempt.stripZeroPlainString()} $currency", ), ) } else { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index b2ef29f6b3..d9a40e965d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -14,7 +14,6 @@ import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.extensions.hasSignedHashes -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity @@ -98,10 +97,8 @@ class WarningsMiddleware { } else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) { checkIfWarningNeeded(scanResponse)?.let { warning -> addWarningMessage(warning) } } - if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release) { - if (!globalState.cardVerifiedOnline) { - addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) - } + if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release && !globalState.cardVerifiedOnline) { + addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) } if (scanResponse.isDemoCard()) { addWarningMessage(WarningMessagesManager.demoCardWarning()) @@ -122,9 +119,9 @@ class WarningsMiddleware { private fun checkIfWarningNeeded( scanResponse: ScanResponse, ): WarningMessage? { - if (scanResponse.isTangemTwins() || scanResponse.isDemoCard()) return null + if (scanResponse.cardTypesResolver.isTangemTwins() || scanResponse.isDemoCard()) return null - if (scanResponse.card.isMultiwalletAllowed) { + if (scanResponse.cardTypesResolver.isMultiwalletAllowed()) { val isBackupForbidden = with(scanResponse.card.settings) { !(isBackupAllowed || isHDWalletAllowed) } return if (scanResponse.card.hasSignedHashes() && isBackupForbidden) { WarningMessagesManager.signedHashesMultiWalletWarning() @@ -156,7 +153,9 @@ class WarningsMiddleware { val card = scanResponse?.card if (card == null || preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) return - if (scanResponse.isTangemTwins() || card.isMultiwalletAllowed) return + if (scanResponse.cardTypesResolver.isTangemTwins() || scanResponse.cardTypesResolver.isMultiwalletAllowed()) { + return + } val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt index fb16e8420c..c772684c48 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt @@ -8,7 +8,12 @@ import com.tangem.wallet.R sealed interface WalletDialog : StateDialog { data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog object SignedHashesMultiWalletDialog : WalletDialog - object ChooseTradeActionDialog : WalletDialog + data class ChooseTradeActionDialog( + val buyAllowed: Boolean, + val sellAllowed: Boolean, + val swapAllowed: Boolean, + ) : WalletDialog + data class CurrencySelectionDialog( val currenciesList: List, val currentAppCurrency: FiatCurrency, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 408cd883d5..28686a5b55 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -31,13 +31,14 @@ import com.tangem.wallet.R import java.math.BigDecimal class MultiWalletReducer { + @Suppress("LongMethod", "ComplexMethod") fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { val walletStores: List = action.blockchains.map { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && - (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) + it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath } val wallet = walletManager?.wallet val walletData = WalletData( @@ -128,15 +129,16 @@ class MultiWalletReducer { else -> BalanceStatus.VerifiedOnline } val tokenWalletData = state.getWalletData(currency) - val isTokenSendButtonEnabled = tokenWalletData?.shouldEnableTokenSendButton() == true - && pendingTransactions.isEmpty() + val isTokenSendButtonEnabled = tokenWalletData?.shouldEnableTokenSendButton() == true && + pendingTransactions.isEmpty() val newTokenWalletData = tokenWalletData?.copy( currencyData = tokenWalletData.currencyData.copy( status = tokenBalanceStatus, amount = action.amount.value, amountFormatted = action.amount.value?.toFormattedCurrencyString( - action.amount.decimals, action.amount.currencySymbol, + decimals = action.amount.decimals, + currency = action.amount.currencySymbol, ), fiatAmountFormatted = tokenWalletData.fiatRate?.let { action.amount.value?.toFiatString(it, store.state.globalState.appCurrency.symbol) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 4648ebdd54..699750e1ba 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -30,6 +30,7 @@ class OnWalletLoadedReducer { } } + @Suppress("LongMethod") private fun onMultiWalletLoaded( wallet: Wallet, blockchainNetwork: BlockchainNetwork, @@ -85,8 +86,8 @@ class OnWalletLoadedReducer { val tokenFiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrency.symbol) ?: UNKNOWN_AMOUNT_SIGN - val isTokenSendButtonEnabled = tokenWalletData?.shouldEnableTokenSendButton() == true - && pendingTransactions.isEmpty() + val isTokenSendButtonEnabled = tokenWalletData?.shouldEnableTokenSendButton() == true && + pendingTransactions.isEmpty() tokenWalletData?.copy( currencyData = tokenWalletData.currencyData.copy( status = tokenBalanceStatus, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt index 08a7def4bd..1e15f2460a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt @@ -25,22 +25,25 @@ fun List.calculateTotalCryptoAmount(): BigDecimal { private fun List.mapToProgressState(): List { return this.map { wallet -> - if (wallet.fiatRate == null) ProgressState.Error - else when (wallet.currencyData.status) { - BalanceStatus.Refreshing -> ProgressState.Refreshing - BalanceStatus.VerifiedOnline, - BalanceStatus.SameCurrencyTransactionInProgress, - BalanceStatus.TransactionInProgress, - BalanceStatus.NoAccount, - -> ProgressState.Done - BalanceStatus.Unreachable, - BalanceStatus.EmptyCard, - BalanceStatus.UnknownBlockchain, - BalanceStatus.MissedDerivation, - -> ProgressState.Error - BalanceStatus.Loading, - null, - -> ProgressState.Loading + if (wallet.fiatRate == null) { + ProgressState.Error + } else { + when (wallet.currencyData.status) { + BalanceStatus.Refreshing -> ProgressState.Refreshing + BalanceStatus.VerifiedOnline, + BalanceStatus.SameCurrencyTransactionInProgress, + BalanceStatus.TransactionInProgress, + BalanceStatus.NoAccount, + -> ProgressState.Done + BalanceStatus.Unreachable, + BalanceStatus.EmptyCard, + BalanceStatus.UnknownBlockchain, + BalanceStatus.MissedDerivation, + -> ProgressState.Error + BalanceStatus.Loading, + null, + -> ProgressState.Loading + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index f3366b882f..6c0ed18bd8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -36,15 +36,13 @@ import com.tangem.tap.proxy.AppStateHolder import org.rekotlin.Action import java.math.BigDecimal -class WalletReducer { - companion object { - fun reduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState = - internalReduce(action, state, appStateHolder) - } +object WalletReducer { + fun reduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState = + internalReduce(action, state, appStateHolder) } +@Suppress("LongMethod", "ComplexMethod") private fun internalReduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState { - val multiWalletReducer = MultiWalletReducer() val onWalletLoadedReducer = OnWalletLoadedReducer() val appCurrencyReducer = AppCurrencyReducer() @@ -254,7 +252,8 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS .map { it.copy( currencyData = it.currencyData.copy( - status = BalanceStatus.Unreachable, errorMessage = message, + status = BalanceStatus.Unreachable, + errorMessage = message, ), ) } @@ -389,7 +388,6 @@ fun findSelectedCurrency( private fun CardDTO.findCardsCount(): Int? { return (this.backupStatus as? CardDTO.BackupStatus.Active)?.cardCount?.inc() - ?.takeIf { this.isMultiwalletAllowed } } fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 53d576c61d..16703319a4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -41,8 +41,8 @@ class BalanceWidget( private val isTwinCard: Boolean, ) { + @Suppress("LongMethod", "ComplexMethod") fun setup() { - when (data.status) { BalanceStatus.Loading -> { with(binding) { @@ -54,7 +54,6 @@ class BalanceWidget( lBalance.tvAmount.text = "" } - showStatus(R.id.tv_status_loading) if (token != null) { @@ -119,7 +118,8 @@ class BalanceWidget( tvErrorDescriptions.text = fragment.getString( R.string.no_account_generic, - data.amountToCreateAccount, data.currencySymbol, + data.amountToCreateAccount, + data.currencySymbol, ) } BalanceStatus.UnknownBlockchain -> with(binding.lBalanceError) { @@ -130,6 +130,7 @@ class BalanceWidget( tvErrorDescriptions.text = fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle) } + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt index ecbad2650a..67e10ca9a6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt @@ -7,36 +7,34 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.address.AddressType import com.tangem.wallet.R -class MultipleAddressUiHelper { - companion object { - fun typeToId(type: AddressType): Int { - return when (type) { - is BitcoinAddressType.Legacy -> R.id.chip_legacy - is BitcoinAddressType.Segwit -> R.id.chip_default - is CardanoAddressType.Byron -> R.id.chip_legacy - is CardanoAddressType.Shelley -> R.id.chip_default - else -> View.NO_ID - } +object MultipleAddressUiHelper { + fun typeToId(type: AddressType): Int { + return when (type) { + is BitcoinAddressType.Legacy -> R.id.chip_legacy + is BitcoinAddressType.Segwit -> R.id.chip_default + is CardanoAddressType.Byron -> R.id.chip_legacy + is CardanoAddressType.Shelley -> R.id.chip_default + else -> View.NO_ID } + } - fun idToType(id: Int, blockchain: Blockchain?): AddressType? { - return when (id) { - R.id.chip_default -> { - when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Segwit - Blockchain.CardanoShelley -> CardanoAddressType.Shelley - else -> null - } + fun idToType(id: Int, blockchain: Blockchain?): AddressType? { + return when (id) { + R.id.chip_default -> { + when (blockchain) { + Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Segwit + Blockchain.CardanoShelley -> CardanoAddressType.Shelley + else -> null } - R.id.chip_legacy -> { - when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Legacy - Blockchain.CardanoShelley -> CardanoAddressType.Byron - else -> null - } - } - else -> null } + R.id.chip_legacy -> { + when (blockchain) { + Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Legacy + Blockchain.CardanoShelley -> CardanoAddressType.Byron + else -> null + } + } + else -> null } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index e38d9d947b..45b6f7140a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -22,6 +22,7 @@ import com.badoo.mvicore.modelWatcher import com.tangem.common.doOnResult import com.tangem.core.analytics.Analytics import com.tangem.domain.common.TapWorkarounds.derivationStyle +import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.TestActions @@ -58,14 +59,21 @@ import com.tangem.tap.userWalletsListManagerSafe import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletDetailsBinding +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.rekotlin.StoreSubscriber +import javax.inject.Inject +@Suppress("LargeClass", "MagicNumber") +@AndroidEntryPoint class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreSubscriber { + @Inject + lateinit var swapInteractor: SwapInteractor + private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter private lateinit var warningMessagesAdapter: WalletDetailWarningMessagesAdapter @@ -184,15 +192,6 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } private fun setupButtons() = with(binding) { - rowButtons.onBuyClick = { - store.dispatch(WalletAction.TradeCryptoAction.Buy()) - } - rowButtons.onSellClick = { - store.dispatch(WalletAction.TradeCryptoAction.Sell) - } - rowButtons.onTradeClick = { - store.dispatch(WalletAction.DialogAction.ChooseTradeActionDialog) - } rowButtons.onSendClick = { store.dispatch(WalletAction.Send()) } @@ -288,10 +287,29 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } private fun setupButtonsRow(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) { + val exchangeManager = store.state.globalState.exchangeManager + binding.rowButtons.apply { + onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) } + onSellClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) } + onSwapClick = { store.dispatch(WalletAction.TradeCryptoAction.Swap) } + onTradeClick = { + store.dispatch( + WalletAction.DialogAction.ChooseTradeActionDialog( + buyAllowed = selectedWallet.isAvailableToBuy(exchangeManager), + sellAllowed = selectedWallet.isAvailableToSell(exchangeManager), + swapAllowed = selectedWallet.isAvailableToSwap(swapInteractor, isExchangeServiceFeatureOn), + ), + ) + } + } + val actions = selectedWallet.getAvailableActions( + swapInteractor = swapInteractor, + exchangeManager = exchangeManager, + isExchangeFeatureOn = isExchangeServiceFeatureOn, + ) binding.rowButtons.updateButtonsVisibility( + actions = actions, exchangeServiceFeatureOn = isExchangeServiceFeatureOn, - buyAllowed = selectedWallet.isAvailableToBuy, - sellAllowed = selectedWallet.isAvailableToSell, sendAllowed = selectedWallet.mainButton.enabled, ) } @@ -397,7 +415,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalance.groupBalance.show() lBalance.tvError.hide() lBalance.tvAmount.text = data.amountFormatted - lBalance.tvFiatAmount.text = data.fiatAmountFormatted + lBalance.tvFiatAmount.text = data.fiatAmountFormatted ?: UNKNOWN_AMOUNT_SIGN lBalance.tvStatus.setLoadingStatus(R.string.wallet_balance_loading) } BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress, @@ -408,7 +426,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalance.groupBalance.show() lBalance.tvError.hide() lBalance.tvAmount.text = data.amountFormatted - lBalance.tvFiatAmount.text = data.fiatAmountFormatted + lBalance.tvFiatAmount.text = data.fiatAmountFormatted ?: UNKNOWN_AMOUNT_SIGN when (data.status) { BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress -> { lBalance.tvStatus.setVerifiedBalanceStatus(R.string.wallet_balance_verified) @@ -435,9 +453,11 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalanceError.tvErrorDescriptions.text = getString( R.string.no_account_generic, - data.amountToCreateAccount, data.currencySymbol, + data.amountToCreateAccount, + data.currencySymbol, ) } + else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 85de69de54..ae7efb71bf 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -21,6 +21,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.domain.common.TapWorkarounds.isSaltPay +import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Portfolio @@ -47,15 +48,21 @@ import com.tangem.tap.userWalletsListManager import com.tangem.wallet.BuildConfig import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletBinding +import dagger.hilt.android.AndroidEntryPoint import org.rekotlin.StoreSubscriber +import javax.inject.Inject +@AndroidEntryPoint class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + @Inject + lateinit var swapInteractor: SwapInteractor + private lateinit var warningsAdapter: WarningMessagesAdapter private val binding: FragmentWalletBinding by viewBinding(FragmentWalletBinding::bind) - private var walletView: WalletView = SingleWalletView() + private var walletView: WalletView = MultiWalletView() private val viewModel by viewModels() @@ -133,6 +140,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + isSaltPay && walletView !is SaltPayWalletView -> { walletView = SaltPayWalletView() walletView.changeWalletView(this, binding) } @@ -165,6 +174,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber {} // we keep the same view unless we scan a card that requires a different view } + walletView.swapInteractor = swapInteractor + walletView.onNewState(state) if (!state.shouldShowDetails) { @@ -212,8 +223,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { context.getString( R.string.warning_existential_deposit_message, - message.currencyName, message.edStringValueWithSymbol, + message.currencyName, + message.edStringValueWithSymbol, ) } is WalletWarning.BalanceNotEnoughForFee -> { context.getString( R.string.token_details_send_blocked_fee_format, + message.currencyName, message.blockchainFullName, + message.currencyName, message.blockchainFullName, + message.blockchainSymbol, ) } is WalletWarning.TransactionInProgress -> { @@ -37,7 +41,8 @@ class WalletWarningConverter( is WalletWarning.Rent -> { context.getString( R.string.solana_rent_warning, - message.walletRent.minRentValue, message.walletRent.rentExemptValue, + message.walletRent.minRentValue, + message.walletRent.rentExemptValue, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt index bafc35673b..6d50e30035 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt @@ -13,12 +13,14 @@ import com.tangem.tap.features.wallet.models.PendingTransactionType import com.tangem.wallet.R import com.tangem.wallet.databinding.ItemPendingTransactionBinding -class PendingTransactionsAdapter - : ListAdapter(DiffUtilCallback) { +class PendingTransactionsAdapter : + ListAdapter(DiffUtilCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionsViewHolder { val binding = ItemPendingTransactionBinding.inflate( - LayoutInflater.from(parent.context), parent, false + LayoutInflater.from(parent.context), + parent, + false ) return TransactionsViewHolder(binding) } @@ -29,19 +31,20 @@ class PendingTransactionsAdapter object DiffUtilCallback : DiffUtil.ItemCallback() { override fun areContentsTheSame( - oldItem: PendingTransaction, newItem: PendingTransaction + oldItem: PendingTransaction, + newItem: PendingTransaction ) = oldItem == newItem override fun areItemsTheSame( - oldItem: PendingTransaction, newItem: PendingTransaction + oldItem: PendingTransaction, + newItem: PendingTransaction ) = oldItem == newItem } class TransactionsViewHolder(val binding: ItemPendingTransactionBinding) : - RecyclerView.ViewHolder(binding.root) { + RecyclerView.ViewHolder(binding.root) { fun bind(transaction: PendingTransaction) { - if (transaction.type == PendingTransactionType.Unknown) { binding.root.hide() } @@ -69,7 +72,7 @@ class PendingTransactionsAdapter if (transaction.address != null) { binding.tvPendingTransactionAddress.text = - binding.root.getString(transactionAddressRes, transaction.address) + binding.root.getString(transactionAddressRes, transaction.address) } binding.ivPendingTransaction.setImageDrawable(binding.root.context.getDrawableCompat(image)) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index fb19f281bc..60c0d65fbc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -6,8 +6,8 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.tap.common.analytics.events.Portfolio import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.hide @@ -28,7 +28,9 @@ class WalletAdapter : ListAdapter(D override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder { val layout = ItemCurrencyWalletBinding.inflate( - LayoutInflater.from(parent.context), parent, false, + LayoutInflater.from(parent.context), + parent, + false, ) return WalletsViewHolder(layout) } @@ -39,11 +41,13 @@ class WalletAdapter : ListAdapter(D object DiffUtilCallback : DiffUtil.ItemCallback() { override fun areContentsTheSame( - oldItem: WalletData, newItem: WalletData, + oldItem: WalletData, + newItem: WalletData, ) = oldItem == newItem override fun areItemsTheSame( - oldItem: WalletData, newItem: WalletData, + oldItem: WalletData, + newItem: WalletData, ) = oldItem == newItem } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletDetailWarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletDetailWarningMessagesAdapter.kt index 5164928569..a96101b35a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletDetailWarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletDetailWarningMessagesAdapter.kt @@ -13,8 +13,8 @@ import com.tangem.wallet.databinding.LayoutWarningCardBinding /** [REDACTED_AUTHOR] */ -class WalletDetailWarningMessagesAdapter - : ListAdapter(DiffUtilCallback()) { +class WalletDetailWarningMessagesAdapter : + ListAdapter(DiffUtilCallback()) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletDetailsWarningMessageVH { val inflater = LayoutInflater.from(parent.context) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index 774bfc2aee..090a503220 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt @@ -28,7 +28,9 @@ class WarningMessagesAdapter : ListAdapter(Dif override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH { val binding = LayoutWarningCardActionBinding.inflate( - LayoutInflater.from(parent.context), parent, false, + LayoutInflater.from(parent.context), + parent, + false, ) return WarningMessageVH(binding) } @@ -75,6 +77,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi binding.warningCardAction.setCardBackgroundColor(binding.root.getColor(color)) } + @Suppress("LongMethod") private fun setupControlButtons(warning: WarningMessage) = when (warning.type) { WarningMessage.Type.Permanent, WarningMessage.Type.TestCard -> { binding.groupControlsTemporary.hide() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt index ff82942f2b..db783afc52 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt @@ -5,7 +5,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import androidx.appcompat.view.ContextThemeWrapper -import androidx.recyclerview.widget.* +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog import com.tangem.blockchain.common.Amount import com.tangem.tap.common.extensions.toFormattedString @@ -51,13 +55,13 @@ class AmountToSendBottomSheetDialog( } } - -class ChooseAmountAdapter - : ListAdapter(DiffUtilCallback) { +class ChooseAmountAdapter : ListAdapter(DiffUtilCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder { val binding = ItemWalletAmountToSendBinding.inflate( - LayoutInflater.from(parent.context), parent, false + LayoutInflater.from(parent.context), + parent, + false ) return AmountViewHolder(binding) } @@ -68,11 +72,13 @@ class ChooseAmountAdapter object DiffUtilCallback : DiffUtil.ItemCallback() { override fun areContentsTheSame( - oldItem: Amount, newItem: Amount + oldItem: Amount, + newItem: Amount ) = oldItem.currencySymbol == newItem.currencySymbol override fun areItemsTheSame( - oldItem: Amount, newItem: Amount + oldItem: Amount, + newItem: Amount ) = oldItem == newItem } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt index 3fc5905990..1495219c54 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt @@ -4,11 +4,16 @@ import android.content.Context import android.os.Bundle import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tap.common.extensions.show import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.store import com.tangem.wallet.databinding.DialogWalletTradeBinding -class ChooseTradeActionBottomSheetDialog(context: Context) : BottomSheetDialog(context) { +class ChooseTradeActionBottomSheetDialog( + context: Context, + private val dialogData: WalletDialog.ChooseTradeActionDialog, +) : BottomSheetDialog(context) { var binding: DialogWalletTradeBinding? = null @@ -26,13 +31,25 @@ class ChooseTradeActionBottomSheetDialog(context: Context) : BottomSheetDialog(c store.dispatch(WalletAction.DialogAction.Hide) } - binding!!.dialogBtnBuy.setOnClickListener { - dismiss() - store.dispatch(WalletAction.TradeCryptoAction.Buy()) - } - binding!!.dialogBtnSell.setOnClickListener { - dismiss() - store.dispatch(WalletAction.TradeCryptoAction.Sell) + binding?.let { + with(it) { + dialogBtnBuy.show(dialogData.buyAllowed) + dialogBtnSell.show(dialogData.sellAllowed) + dialogBtnSwap.show(dialogData.swapAllowed) + + dialogBtnBuy.setOnClickListener { + dismiss() + store.dispatch(WalletAction.TradeCryptoAction.Buy()) + } + dialogBtnSell.setOnClickListener { + dismiss() + store.dispatch(WalletAction.TradeCryptoAction.Sell) + } + dialogBtnSwap.setOnClickListener { + dismiss() + store.dispatch(WalletAction.TradeCryptoAction.Swap) + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt index e504ff3172..9c8d5997b9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt @@ -5,8 +5,6 @@ import android.os.Bundle import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetDialog import com.tangem.core.analytics.Analytics -import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchOpenUrl @@ -20,7 +18,7 @@ class RussianCardholdersWarningBottomSheetDialog( context: Context, private val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data?, ) : BottomSheetDialog - (context) { +(context) { private var binding: DialogRussiansCardholdersWarningBinding? = null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index 3251bce185..f1081006c2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -13,21 +13,17 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class ScanFailsDialog { - - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.common_warning)) - setMessage(R.string.alert_troubleshooting_scan_card_title) - setPositiveButton(R.string.alert_button_request_support) { _, _ -> - Analytics.send(IntroductionProcess.ButtonRequestSupport()) - store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) - } - setNeutralButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatchDialogHide() } - }.create() - } +object ScanFailsDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.common_warning)) + setMessage(R.string.alert_troubleshooting_scan_card_title) + setPositiveButton(R.string.alert_button_request_support) { _, _ -> + Analytics.send(IntroductionProcess.ButtonRequestSupport()) + store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) + } + setNeutralButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatchDialogHide() } + }.create() } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt index c8db3c3d23..b419a42b81 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt @@ -8,26 +8,23 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R -class SignedHashesWarningDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.warning_important_security_info)) - setMessage(R.string.alert_signed_hashes_message) - setPositiveButton(R.string.common_understand) { _, _ -> - store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) - store.dispatch( - GlobalAction.HideWarningMessage( - WarningMessagesManager.signedHashesMultiWalletWarning() - ) - ) - } - setNegativeButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { - store.dispatch(WalletAction.DialogAction.Hide) - } - }.create() - } +object SignedHashesWarningDialog { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.warning_important_security_info)) + setMessage(R.string.alert_signed_hashes_message) + setPositiveButton(R.string.common_understand) { _, _ -> + store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) + store.dispatch( + GlobalAction.HideWarningMessage( + WarningMessagesManager.signedHashesMultiWalletWarning(), + ), + ) + } + setNegativeButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { + store.dispatch(WalletAction.DialogAction.Hide) + } + }.create() } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt index d52c26763e..dc44583536 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt @@ -10,49 +10,47 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class SimpleOkDialog { +object SimpleOkDialog { - companion object { - fun create(dialog: AppDialog.SimpleOkDialog, context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(dialog.header) - setMessage(dialog.message) - setPositiveButton(R.string.common_ok) { _, _ -> } - setOnDismissListener { - store.dispatchDialogHide() - dialog.onOk?.invoke() - } - }.create() - } - - fun create(dialog: AppDialog.SimpleOkDialogRes, context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(dialog.headerId)) - setMessage(dialog.messageId) - setPositiveButton(R.string.common_ok) { _, _ -> } - setOnDismissListener { - store.dispatchDialogHide() - dialog.onOk?.invoke() - } - }.create() - } - - fun create(dialog: AppDialog.SimpleOkErrorDialog, context: Context): AlertDialog = create( - dialog = AppDialog.SimpleOkDialog( - header = context.getString(R.string.common_error), - message = dialog.message, - onOk = dialog.onOk, - ), - context = context, - ) - - fun create(dialog: AppDialog.SimpleOkWarningDialog, context: Context): AlertDialog = create( - dialog = AppDialog.SimpleOkDialog( - header = context.getString(R.string.common_warning), - message = dialog.message, - onOk = dialog.onOk, - ), - context = context, - ) + fun create(dialog: AppDialog.SimpleOkDialog, context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(dialog.header) + setMessage(dialog.message) + setPositiveButton(R.string.common_ok) { _, _ -> } + setOnDismissListener { + store.dispatchDialogHide() + dialog.onOk?.invoke() + } + }.create() } + + fun create(dialog: AppDialog.SimpleOkDialogRes, context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(dialog.headerId)) + setMessage(dialog.messageId) + setPositiveButton(R.string.common_ok) { _, _ -> } + setOnDismissListener { + store.dispatchDialogHide() + dialog.onOk?.invoke() + } + }.create() + } + + fun create(dialog: AppDialog.SimpleOkErrorDialog, context: Context): AlertDialog = create( + dialog = AppDialog.SimpleOkDialog( + header = context.getString(R.string.common_error), + message = dialog.message, + onOk = dialog.onOk, + ), + context = context, + ) + + fun create(dialog: AppDialog.SimpleOkWarningDialog, context: Context): AlertDialog = create( + dialog = AppDialog.SimpleOkDialog( + header = context.getString(R.string.common_warning), + message = dialog.message, + onOk = dialog.onOk, + ), + context = context, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt index 729bfe3fcc..b02511017c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt @@ -8,9 +8,9 @@ import coil.request.ImageRequest import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.IconsUtil import com.tangem.blockchain.common.Token +import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.domain.common.extensions.toNetworkId import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.common.extensions.getRoundIconRes import com.tangem.tap.common.extensions.getTextColor import com.tangem.tap.domain.extensions.getCustomIconUrl import com.tangem.tap.domain.tokens.getIconUrl @@ -85,8 +85,8 @@ class CurrencyIconRequest( crossinline onError: (Blockchain) -> Unit = {}, ) { currencyImageView.loadIcon( - data = if (getLocalImage) blockchain.getRoundIconRes() else getIconUrl(blockchain.toNetworkId()), - placeholderRes = blockchain.getRoundIconRes(), + data = if (getLocalImage) getActiveIconRes(blockchain.id) else getIconUrl(blockchain.toNetworkId()), + placeholderRes = getActiveIconRes(blockchain.id), onStart = { onStart(blockchain) }, onSuccess = { onSuccess(blockchain) }, onError = { onError(blockchain) }, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt index b6d34873df..d05ac08cc9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt @@ -13,6 +13,7 @@ import com.tangem.tap.features.wallet.models.Currency import com.tangem.wallet.databinding.ViewCurrencyIconBinding import kotlin.math.roundToInt +@Suppress("MagicNumber") class CurrencyIconView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt deleted file mode 100644 index 7a9b0a3015..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.tangem.tap.features.wallet.ui.images - -import android.graphics.Bitmap -import android.graphics.BitmapShader -import android.graphics.Matrix -import android.graphics.Paint -import android.graphics.RectF -import android.graphics.Shader -import androidx.annotation.Px -import androidx.core.graphics.applyCanvas -import coil.size.Size -import coil.size.pxOrElse -import coil.transform.Transformation - -class RoundedCornersCropTransformation( - @Px val radiusPx: Float, -) : Transformation { - override val cacheKey: String = "${javaClass.name}-$radiusPx" - - override suspend fun transform(input: Bitmap, size: Size): Bitmap { - val outputWidth = size.width.pxOrElse { Int.MAX_VALUE } - val outputHeight = size.height.pxOrElse { Int.MAX_VALUE } - val outputSize = minOf(outputWidth, outputHeight) - val output = Bitmap.createBitmap(outputSize, outputSize, Bitmap.Config.ARGB_8888) - - val rect = RectF( - /* left = */ 0f, - /* top = */ 0f, - /* right = */ outputWidth.toFloat(), - /* bottom = */ outputHeight.toFloat() - ) - val matrix = Matrix().apply { - setTranslate( - /* dx = */ (outputWidth - input.width) * .5f, - /* dy = */ (outputHeight - input.height) * .5f - ) - } - val bitmapShader = BitmapShader( - /* bitmap = */ input, - /* tileX = */ Shader.TileMode.DECAL, - /* tileY = */ Shader.TileMode.DECAL - ).apply { - setLocalMatrix(matrix) - } - val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply { - shader = bitmapShader - } - - return output.applyCanvas { - drawRoundRect( - /* rect = */ rect, - /* rx = */ radiusPx, - /* ry = */ radiusPx, - /* paint = */ paint - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWallet.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWallet.kt new file mode 100644 index 0000000000..5b3dab3e8b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWallet.kt @@ -0,0 +1,160 @@ +package com.tangem.tap.features.wallet.ui.test + +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.WalletManager +import com.tangem.common.extensions.guard +import com.tangem.tap.common.TestAction +import com.tangem.tap.common.TestActions +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import java.math.BigDecimal +import kotlin.random.Random + +/** +[REDACTED_AUTHOR] + */ +object TestWallet { + fun solanaRentExemptWarning(): List { + val checker = SolanaRentWarningActionEmitter() + return listOf( + "BALANCE = 0.0" to { checker.setZeroBalance() }, + "BALANCE < 0.00089088" to { checker.setLessThanRentExempt() }, + "BALANCE > 0.00089088" to { checker.setMoreThanRentExempt() }, + "BALANCE = 0.00089087" to { checker.setLessThanRentExemptByOne() }, + "BALANCE = 0.00089088 (rent exempt)" to { checker.setForRentExemptBarrier() }, + "BALANCE = 0.00089089" to { checker.setMoreThanRentExemptByOne() }, + ) + } + + fun getBlockchainBalanceActions(blockchainNetwork: BlockchainNetwork): List { + return getBlockchainBalanceActions( + getWalletManager(blockchainNetwork), + blockchainNetwork.blockchain.decimals(), + ) + } + + fun getTokenBalanceAction(blockchainNetwork: BlockchainNetwork, token: Token): List { + return getTokenBalanceAction(getWalletManager(blockchainNetwork), token) + } + + fun getBlockchainBalanceActions(walletManager: WalletManager?, decimals: Int): List { + val minValue = BigDecimal.ONE.movePointLeft(decimals) + val averageValue = BigDecimal.ONE.movePointRight(decimals) + .divide(BigDecimal(2)).movePointLeft(decimals) + val maxValue = BigDecimal(2).pow(32) + + return listOf( + "0.0" to { setBalance(walletManager, BigDecimal.ZERO) }, + "Min value" to { setBalance(walletManager, minValue) }, + "Average value" to { setBalance(walletManager, averageValue) }, + "Max value" to { setBalance(walletManager, maxValue) }, + "1234567890123.01234567890123" to { setBalance(walletManager, BigDecimal.ZERO) }, + ) + } + + fun getTokenBalanceAction(walletManager: WalletManager?, token: Token): List { + val minValue = BigDecimal.ONE.movePointLeft(token.decimals) + val averageValue = BigDecimal.ONE.movePointRight(token.decimals) + .divide(BigDecimal(2)).movePointLeft(token.decimals) + val maxValue = BigDecimal(2).pow(32) + + return listOf( + "0.0" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, + "Min value" to { setTokenBalance(walletManager, minValue, token) }, + "Average value" to { setTokenBalance(walletManager, averageValue, token) }, + "Max value" to { setTokenBalance(walletManager, maxValue, token) }, + "1234567890123.01234567890123" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, + ) + } + + fun setBalance(walletManager: WalletManager?, value: BigDecimal) { + val manager = walletManager.guard { + store.dispatchDebugErrorNotification("WalletManager not found") + return + } + val amount = manager.wallet.amounts[AmountType.Coin] ?: Amount(manager.wallet.blockchain) + setBalance(manager, amount, value) + } + + private fun setTokenBalance(walletManager: WalletManager?, value: BigDecimal, token: Token? = null) { + val manager = walletManager.guard { + store.dispatchDebugErrorNotification("WalletManager not found") + return + } + val token = token.guard { + store.dispatchDebugErrorNotification("Token not found") + return + } + + val amount = manager.wallet.amounts[AmountType.Token(token)] ?: Amount(token) + setBalance(manager, amount, value) + } + + private fun setBalance(walletManager: WalletManager, amount: Amount, value: BigDecimal) { + TestActions.testAmountInjectionForWalletManagerEnabled = true + walletManager.wallet.setAmount(amount.copy(value = value)) + store.dispatch(WalletAction.LoadData) + } + + private fun getWalletManager(blockchainNetwork: BlockchainNetwork): WalletManager? { + return store.state.walletState.getWalletManager(blockchainNetwork) + } +} + +private class SolanaRentWarningActionEmitter { + + private val zero = BigDecimal.ZERO + private val one = BigDecimal(0.00000001) + private val rentExemptBarrier = BigDecimal(0.00089088) + private val lessThanRentExemptByOne = rentExemptBarrier.minus(one) + private val moreThanRentExemptByOne = rentExemptBarrier.plus(one) + private val moreThanRentExempt = rentExemptBarrier.plus(Random.nextDouble().toBigDecimal()) + private val lessThanRentExempt = rentExemptBarrier + .minus(Random.nextDouble(0.0, rentExemptBarrier.minus(one).toDouble()).toBigDecimal()) + + fun setZeroBalance() { + setBalance(zero) + } + + fun setForRentExemptBarrier() { + setBalance(rentExemptBarrier) + } + + fun setLessThanRentExemptByOne() { + setBalance(lessThanRentExemptByOne) + } + + fun setMoreThanRentExemptByOne() { + setBalance(moreThanRentExemptByOne) + } + + fun setMoreThanRentExempt() { + setBalance(moreThanRentExempt) + } + + fun setLessThanRentExempt() { + setBalance(lessThanRentExempt) + } + + private fun setBalance(value: BigDecimal) { + val amount = Amount(getBlockchainNetwork().blockchain).copy(value = value) + getWalletManager().apply { + TestActions.testAmountInjectionForWalletManagerEnabled = true + wallet.setAmount(amount) + } + store.dispatch(WalletAction.LoadData) + } + + private fun getWalletManager(): WalletManager { + return store.state.walletState.getWalletManager(getBlockchainNetwork())!! + } + + private fun getBlockchainNetwork(): BlockchainNetwork { + val currency = store.state.walletState.selectedWalletData!!.currency + return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt deleted file mode 100644 index 857cdd2631..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.tangem.tap.features.wallet.ui.test - -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.WalletManager -import com.tangem.common.extensions.guard -import com.tangem.tap.common.TestAction -import com.tangem.tap.common.TestActions -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.store -import java.math.BigDecimal -import kotlin.random.Random - -/** -[REDACTED_AUTHOR] - */ -class TestWallet { - companion object { - fun solanaRentExemptWarning(): List { - val checker = SolanaRentWarningActionEmitter() - return listOf( - "BALANCE = 0.0" to { checker.setZeroBalance() }, - "BALANCE < 0.00089088" to { checker.setLessThanRentExempt() }, - "BALANCE > 0.00089088" to { checker.setMoreThanRentExempt() }, - "BALANCE = 0.00089087" to { checker.setLessThanRentExemptByOne() }, - "BALANCE = 0.00089088 (rent exempt)" to { checker.setForRentExemptBarrier() }, - "BALANCE = 0.00089089" to { checker.setMoreThanRentExemptByOne() }, - ) - } - - fun getBlockchainBalanceActions(blockchainNetwork: BlockchainNetwork): List { - return getBlockchainBalanceActions( - getWalletManager(blockchainNetwork), - blockchainNetwork.blockchain.decimals(), - ) - } - - fun getTokenBalanceAction(blockchainNetwork: BlockchainNetwork, token: Token): List { - return getTokenBalanceAction(getWalletManager(blockchainNetwork), token) - } - - fun getBlockchainBalanceActions(walletManager: WalletManager?, decimals: Int): List { - val minValue = BigDecimal.ONE.movePointLeft(decimals) - val averageValue = BigDecimal.ONE.movePointRight(decimals) - .divide(BigDecimal(2)).movePointLeft(decimals) - val maxValue = BigDecimal(2).pow(32) - - return listOf( - "0.0" to { setBalance(walletManager, BigDecimal.ZERO) }, - "Min value" to { setBalance(walletManager, minValue) }, - "Average value" to { setBalance(walletManager, averageValue) }, - "Max value" to { setBalance(walletManager, maxValue) }, - "1234567890123.01234567890123" to { setBalance(walletManager, BigDecimal.ZERO) }, - ) - } - - fun getTokenBalanceAction(walletManager: WalletManager?, token: Token): List { - val minValue = BigDecimal.ONE.movePointLeft(token.decimals) - val averageValue = BigDecimal.ONE.movePointRight(token.decimals) - .divide(BigDecimal(2)).movePointLeft(token.decimals) - val maxValue = BigDecimal(2).pow(32) - - return listOf( - "0.0" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, - "Min value" to { setTokenBalance(walletManager, minValue, token) }, - "Average value" to { setTokenBalance(walletManager, averageValue, token) }, - "Max value" to { setTokenBalance(walletManager, maxValue, token) }, - "1234567890123.01234567890123" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) }, - ) - } - - fun setBalance(walletManager: WalletManager?, value: BigDecimal) { - val manager = walletManager.guard { - store.dispatchDebugErrorNotification("WalletManager not found") - return - } - val amount = manager.wallet.amounts[AmountType.Coin] ?: Amount(manager.wallet.blockchain) - setBalance(manager, amount, value) - } - - private fun setTokenBalance(walletManager: WalletManager?, value: BigDecimal, token: Token? = null) { - val manager = walletManager.guard { - store.dispatchDebugErrorNotification("WalletManager not found") - return - } - val token = token.guard { - store.dispatchDebugErrorNotification("Token not found") - return - } - - val amount = manager.wallet.amounts[AmountType.Token(token)] ?: Amount(token) - setBalance(manager, amount, value) - } - - private fun setBalance(walletManager: WalletManager, amount: Amount, value: BigDecimal) { - TestActions.testAmountInjectionForWalletManagerEnabled = true - walletManager.wallet.setAmount(amount.copy(value = value)) - store.dispatch(WalletAction.LoadData) - } - - private fun getWalletManager(blockchainNetwork: BlockchainNetwork): WalletManager? { - return store.state.walletState.getWalletManager(blockchainNetwork) - } - } -} - -private class SolanaRentWarningActionEmitter { - - private val zero = BigDecimal.ZERO - private val one = BigDecimal(0.00000001) - private val rentExemptBarrier = BigDecimal(0.00089088) - private val lessThanRentExemptByOne = rentExemptBarrier.minus(one) - private val moreThanRentExemptByOne = rentExemptBarrier.plus(one) - private val moreThanRentExempt = rentExemptBarrier.plus(Random.nextDouble().toBigDecimal()) - private val lessThanRentExempt = rentExemptBarrier - .minus(Random.nextDouble(0.0, rentExemptBarrier.minus(one).toDouble()).toBigDecimal()) - - fun setZeroBalance() { - setBalance(zero) - } - - fun setForRentExemptBarrier() { - setBalance(rentExemptBarrier) - } - - fun setLessThanRentExemptByOne() { - setBalance(lessThanRentExemptByOne) - } - - fun setMoreThanRentExemptByOne() { - setBalance(moreThanRentExemptByOne) - } - - fun setMoreThanRentExempt() { - setBalance(moreThanRentExempt) - } - - fun setLessThanRentExempt() { - setBalance(lessThanRentExempt) - } - - private fun setBalance(value: BigDecimal) { - val amount = Amount(getBlockchainNetwork().blockchain).copy(value = value) - getWalletManager().apply { - TestActions.testAmountInjectionForWalletManagerEnabled = true - wallet.setAmount(amount) - } - store.dispatch(WalletAction.LoadData) - } - - private fun getWalletManager(): WalletManager { - return store.state.walletState.getWalletManager(getBlockchainNetwork())!! - } - - private fun getBlockchainNetwork(): BlockchainNetwork { - val currency = store.state.walletState.selectedWalletData!!.currency - return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt index 582b5f178c..30d48309a8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt @@ -2,9 +2,14 @@ package com.tangem.tap.features.wallet.ui.view import android.content.Context import android.util.AttributeSet +import android.view.Gravity import android.view.LayoutInflater import android.widget.LinearLayout import androidx.core.view.isVisible +import com.google.android.material.button.MaterialButton +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.features.wallet.redux.CurrencyAction import com.tangem.wallet.databinding.ViewWalletDetailsButtonsRowBinding internal class WalletDetailsButtonsRow @JvmOverloads constructor( @@ -19,6 +24,7 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor( var onBuyClick: (() -> Unit)? = null var onSellClick: (() -> Unit)? = null var onTradeClick: (() -> Unit)? = null + var onSwapClick: (() -> Unit)? = null var onSendClick: (() -> Unit)? = null init { @@ -27,23 +33,45 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor( with(binding) { btnBuy.setOnClickListener { onBuyClick?.invoke() } btnSell.setOnClickListener { onSellClick?.invoke() } + btnSwap.setOnClickListener { onSwapClick?.invoke() } btnTrade.setOnClickListener { onTradeClick?.invoke() } btnSend.setOnClickListener { onSendClick?.invoke() } } } fun updateButtonsVisibility( + actions: Set, exchangeServiceFeatureOn: Boolean, - buyAllowed: Boolean, - sellAllowed: Boolean, - sendAllowed: Boolean, + sendAllowed: Boolean ) = with(binding) { - containerExchangeButtons.isVisible = exchangeServiceFeatureOn + containerActionButtons.isVisible = exchangeServiceFeatureOn - btnBuy.isVisible = (buyAllowed && !sellAllowed) || (!buyAllowed && !sellAllowed) - btnBuy.isEnabled = buyAllowed - btnSell.isVisible = !buyAllowed && sellAllowed - btnTrade.isVisible = buyAllowed && sellAllowed + when { + actions.isEmpty() -> { + containerActionButtons.hide() + } + actions.size == 1 -> { + val action = actions.first() + btnTrade.hide() + btnBuy.show(action == CurrencyAction.Buy) + btnSell.show(action == CurrencyAction.Sell) + btnSwap.show(action == CurrencyAction.Swap) + } + else -> { + btnBuy.hide() + btnSell.hide() + btnSwap.hide() + btnTrade.show() + } + } + + if (containerActionButtons.isVisible) { + btnSend.gravity = Gravity.START or Gravity.CENTER_VERTICAL + btnSend.iconGravity = MaterialButton.ICON_GRAVITY_END + } else { + btnSend.gravity = Gravity.CENTER + btnSend.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_END + } btnSend.isEnabled = sendAllowed } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 550340717a..2e9f1b426f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -243,7 +243,9 @@ class MultiWalletView : WalletView() { } private fun showErrorState( - binding: FragmentWalletBinding, errorTitle: CharSequence, errorDescription: CharSequence, + binding: FragmentWalletBinding, + errorTitle: CharSequence, + errorDescription: CharSequence, ) = with(binding) { lCardBalance.root.show() with(lCardBalance) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 1723934656..61c7b614b6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -134,17 +134,34 @@ class SingleWalletView : WalletView() { rowButtons: WalletDetailsButtonsRow, isExchangeServiceFeatureEnabled: Boolean, ) { - rowButtons.updateButtonsVisibility( + val swapInteractor = this.swapInteractor ?: return + + val exchangeManager = store.state.globalState.exchangeManager + binding?.rowButtons?.apply { + onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) } + onSellClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) } + onSwapClick = { store.dispatch(WalletAction.TradeCryptoAction.Swap) } + onTradeClick = { + store.dispatch( + WalletAction.DialogAction.ChooseTradeActionDialog( + buyAllowed = walletData.isAvailableToBuy(exchangeManager), + sellAllowed = walletData.isAvailableToSell(exchangeManager), + swapAllowed = false, // always disable for single wallet + ), + ) + } + } + val actions = walletData.getAvailableActions( + swapInteractor = swapInteractor, + exchangeManager = exchangeManager, + isExchangeFeatureOn = isExchangeServiceFeatureEnabled, + ) + binding?.rowButtons?.updateButtonsVisibility( + actions = actions, exchangeServiceFeatureOn = isExchangeServiceFeatureEnabled, - buyAllowed = walletData.isAvailableToBuy, - sellAllowed = walletData.isAvailableToSell, sendAllowed = walletData.mainButton.enabled, ) - rowButtons.onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) } - rowButtons.onSendClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) } - rowButtons.onTradeClick = { store.dispatch(WalletAction.DialogAction.ChooseTradeActionDialog) } - rowButtons.onSendClick = { when (walletData.mainButton) { is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send()) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt index d7d7cbb405..393f1ea97d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt @@ -1,10 +1,13 @@ package com.tangem.tap.features.wallet.ui.wallet +import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.wallet.databinding.FragmentWalletBinding abstract class WalletView { + var swapInteractor: SwapInteractor? = null + protected var fragment: WalletFragment? = null protected var binding: FragmentWalletBinding? = null fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) { diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index 0fd46c00ba..4e8b5ec2d6 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -55,7 +55,5 @@ internal sealed interface WalletSelectorAction : Action { val fiatCurrency: FiatCurrency, ) : WalletSelectorAction - data class HandleError(val error: TangemError) : WalletSelectorAction - object CloseError : WalletSelectorAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index aa4f559888..33e8bcae4b 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -20,6 +20,7 @@ import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.preferencesStorage @@ -27,16 +28,18 @@ import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.totalFiatBalanceCalculator +import com.tangem.tap.userTokensRepository import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber +// Refactoring is coming +@Suppress("LargeClass") internal class WalletSelectorMiddleware { val middleware: Middleware = { _, appStateProvider -> { next -> @@ -83,7 +86,6 @@ internal class WalletSelectorMiddleware { is WalletSelectorAction.UnlockWithBiometry.Success, is WalletSelectorAction.BalancesLoaded, is WalletSelectorAction.IsLockedChanged, - is WalletSelectorAction.HandleError, is WalletSelectorAction.CloseError, -> Unit } @@ -94,7 +96,6 @@ internal class WalletSelectorMiddleware { walletStoresManager.fetch(userWallets) .doOnFailure { error -> Timber.e(error, "Unable to fetch wallet stores") - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } } } @@ -103,10 +104,12 @@ internal class WalletSelectorMiddleware { updatedWalletStores: Map>, state: WalletSelectorState, ) { - if (updatedWalletStores.isNotEmpty()) scope.launch(Dispatchers.Default) { - val updatedWallets = state.wallets.updateWalletStoresAndCalculateFiatBalance(updatedWalletStores) - if (updatedWallets != state.wallets) { - store.dispatchOnMain(WalletSelectorAction.BalancesLoaded(updatedWallets)) + if (updatedWalletStores.isNotEmpty()) { + scope.launch(Dispatchers.Default) { + val updatedWallets = state.wallets.updateWalletStoresAndCalculateFiatBalance(updatedWalletStores) + if (updatedWallets != state.wallets) { + store.dispatchOnMain(WalletSelectorAction.BalancesLoaded(updatedWallets)) + } } } } @@ -117,6 +120,7 @@ internal class WalletSelectorMiddleware { scope.launch { userWalletsListManager.unlockWithBiometry() .doOnFailure { error -> + Timber.e(error, "Unable to unlock all user wallets") store.dispatchOnMain(WalletSelectorAction.UnlockWithBiometry.Error(error)) } .doOnSuccess { @@ -150,6 +154,7 @@ internal class WalletSelectorMiddleware { .doOnFailure { error -> // Rollback policy if card saving was failed tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + Timber.e(error, "Unable to save user wallet") store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) } }, @@ -162,6 +167,7 @@ internal class WalletSelectorMiddleware { store.dispatchOnMain(NavigationAction.PopBackTo()) } else -> { + Timber.e(error, "Unable to scan card") store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) } } @@ -192,7 +198,13 @@ internal class WalletSelectorMiddleware { } } .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + Timber.e( + error, + """ + Unable to select user wallet + |- Wallet ID to select: $userWalletId + """.trimIndent(), + ) } .doOnSuccess { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync @@ -210,13 +222,22 @@ internal class WalletSelectorMiddleware { private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { Analytics.send(MyWallets.Button.WalletUnlockTapped()) tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) - return tangemSdkManager.scanCard(userWallet.cardId) - .map { scannedCard -> - userWallet.copy( - scanResponse = userWallet.scanResponse.copy( - card = scannedCard, - ), - ) + return tangemSdkManager.scanProduct(userTokensRepository) + .map { scanResponse -> + val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() + if (scannedUserWalletId == userWallet.walletId) { + userWallet.updateCardWallets(scanResponse) + } else { + // TODO: Display error + Timber.e( + """ + Unable to unlock and select user wallet + |- Excepted ID: ${userWallet.walletId} + |- Received ID: $scannedUserWalletId + """.trimIndent(), + ) + error("Wrong card") + } } .flatMap { updatedUserWallet -> userWalletsListManager.save(updatedUserWallet, canOverride = true) @@ -228,6 +249,16 @@ internal class WalletSelectorMiddleware { } } + private fun UserWallet.updateCardWallets(scanResponse: ScanResponse): UserWallet { + return this.copy( + scanResponse = this.scanResponse.copy( + card = this.scanResponse.card.copy( + wallets = scanResponse.card.wallets, + ), + ), + ) + } + private fun deleteWallets(userWalletsIds: List, state: WalletSelectorState) { Analytics.send(MyWallets.Button.DeleteWalletTapped()) @@ -240,7 +271,14 @@ internal class WalletSelectorMiddleware { ) } .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + Timber.e( + error, + """ + Unable to delete user wallets + |- Wallets IDs to delete: $userWalletsIds + |- Current wallets IDs: ${state.wallets.map { it.id }} + """.trimIndent(), + ) } } } @@ -249,7 +287,14 @@ internal class WalletSelectorMiddleware { scope.launch { userWalletsListManager.update(userWalletId) { it.copy(name = newName) } .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + Timber.e( + error, + """ + Unable to rename user wallet + |- Wallet ID: $userWalletId + |- New name: $newName + """.trimIndent(), + ) } } } @@ -257,10 +302,10 @@ internal class WalletSelectorMiddleware { private fun refreshUserWalletsAmounts() { scope.launch { walletStoresManager.updateAmounts( - userWallets = userWalletsListManager.userWallets.first(), + userWallets = userWalletsListManager.userWallets.firstOrNull().orEmpty(), ) .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + Timber.e(error, "Unable to refresh user wallets amounts") } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index 277d3f45dd..e965a29794 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -13,6 +13,7 @@ internal object WalletSelectorReducer { } else state.walletSelectorState } + @Suppress("ComplexMethod") private fun internalReduce(action: WalletSelectorAction, state: WalletSelectorState): WalletSelectorState { return when (action) { is WalletSelectorAction.UserWalletsLoaded -> state.copy( @@ -27,7 +28,6 @@ internal object WalletSelectorReducer { is WalletSelectorAction.BalancesLoaded -> state.copy( wallets = action.userWalletModels, ) - is WalletSelectorAction.HandleError -> state.copy(error = action.error) is WalletSelectorAction.CloseError -> state.copy(error = null) is WalletSelectorAction.UnlockWithBiometry -> state.copy( isUnlockInProgress = true, @@ -95,7 +95,7 @@ internal object WalletSelectorReducer { ) } else { UserWalletModel.Type.SingleCurrency( - blockchainName = scanResponse.getBlockchain().fullName, + blockchainName = scanResponse.cardTypesResolver.getBlockchain().fullName, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index f8bc829bde..30eb4cb1fb 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -25,9 +25,10 @@ import com.tangem.core.ui.fragments.ComposeBottomSheetFragment import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.features.details.ui.cardsettings.resolveReference +import com.tangem.tap.features.walletSelector.ui.components.RemoveWalletDialogContent import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent -import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog +import com.tangem.tap.features.walletSelector.ui.model.DialogModel internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment() { private val viewModel by viewModels() @@ -45,10 +46,10 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment RemoveWalletDialogContent(dialog) + is DialogModel.RenameWalletDialog -> RenameWalletDialogContent(dialog) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorScreenState.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorScreenState.kt index 1831d16240..b2da2e5b5f 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorScreenState.kt @@ -3,8 +3,8 @@ package com.tangem.tap.features.walletSelector.ui import androidx.compose.runtime.Immutable import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.tap.features.walletSelector.ui.model.DialogModel import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem -import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem @Immutable @@ -14,7 +14,7 @@ internal data class WalletSelectorScreenState( val selectedUserWalletId: UserWalletId? = null, val isLocked: Boolean = false, val editingUserWalletsIds: List = listOf(), - val renameWalletDialog: RenameWalletDialog? = null, + val dialog: DialogModel? = null, val showAddCardProgress: Boolean = false, val showUnlockProgress: Boolean = false, val error: TextReference? = null, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index edb923f5ba..21313550f3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -8,7 +8,7 @@ import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction import com.tangem.tap.features.walletSelector.redux.WalletSelectorState -import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog +import com.tangem.tap.features.walletSelector.ui.model.DialogModel import com.tangem.tap.store import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager @@ -67,28 +67,28 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber + onConfirm = { newName -> store.dispatch(WalletSelectorAction.RenameWallet(editedUserWalletId, newName)) stateInternal.update { prevState -> prevState.copy( - renameWalletDialog = null, + dialog = null, editingUserWalletsIds = emptyList(), ) } }, - onCancel = { + onDismiss = { stateInternal.update { prevState -> prevState.copy( - renameWalletDialog = null, + dialog = null, ) } }, @@ -96,7 +96,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber prevState.copy( - renameWalletDialog = dialog, + dialog = dialog, ) } } @@ -105,7 +105,30 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber + prevState.copy( + dialog = null, + editingUserWalletsIds = emptyList(), + ) + } + }, + onDismiss = { + stateInternal.update { prevState -> + prevState.copy( + dialog = null, + ) + } + }, + ) + + stateInternal.update { prevState -> + prevState.copy( + dialog = dialog, + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RemoveWalletDialogContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RemoveWalletDialogContent.kt new file mode 100644 index 0000000000..cedbb48278 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RemoveWalletDialogContent.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.features.walletSelector.ui.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.BasicDialog +import com.tangem.core.ui.components.DialogButton +import com.tangem.tap.features.walletSelector.ui.model.DialogModel +import com.tangem.wallet.R + +@Composable +internal fun RemoveWalletDialogContent(dialog: DialogModel.RemoveWalletDialog) { + BasicDialog( + message = stringResource(id = R.string.user_wallet_list_delete_prompt), + confirmButton = DialogButton( + title = stringResource(id = R.string.common_delete), + warning = true, + onClick = dialog.onConfirm, + ), + dismissButton = DialogButton( + onClick = dialog.onDismiss, + ), + onDismissDialog = dialog.onDismiss, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt index f6aa55d7a0..97d465df54 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt @@ -1,12 +1,6 @@ package com.tangem.tap.features.walletSelector.ui.components -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -16,68 +10,39 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.window.Dialog -import com.tangem.core.ui.components.OutlineTextField -import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerH24 -import com.tangem.core.ui.components.SpacerW8 -import com.tangem.core.ui.components.TextButton +import com.tangem.core.ui.components.AdditionalTextInputDialogParams +import com.tangem.core.ui.components.DialogButton +import com.tangem.core.ui.components.TextInputDialog import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog +import com.tangem.tap.features.walletSelector.ui.model.DialogModel import com.tangem.wallet.R @Composable -internal fun RenameWalletDialogContent( - modifier: Modifier = Modifier, - dialog: RenameWalletDialog, -) { - Dialog(onDismissRequest = dialog.onCancel) { - Column( - modifier = modifier - .background( - shape = TangemTheme.shapes.roundedCornersLarge, - color = TangemTheme.colors.background.plain, - ) - .padding(all = TangemTheme.dimens.spacing24), - ) { - var value by remember { - mutableStateOf(TextFieldValue(text = dialog.currentName)) - } - - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.user_wallet_list_rename_popup_title), - style = TangemTheme.typography.h3, - color = TangemTheme.colors.text.primary1, - ) - SpacerH16() - OutlineTextField( - value = value, - label = stringResource(R.string.user_wallet_list_rename_popup_placeholder), - onValueChange = { newValue -> - value = newValue - }, - ) - SpacerH24() - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - ) { - TextButton( - text = stringResource(id = R.string.common_cancel), - onClick = dialog.onCancel, - ) - SpacerW8() - TextButton( - text = stringResource(id = R.string.common_save), - enabled = value.text.isNotEmpty() && value.text != dialog.currentName, - onClick = { - dialog.onApply(value.text) - }, - ) - } - } +internal fun RenameWalletDialogContent(dialog: DialogModel.RenameWalletDialog) { + var value by remember { + mutableStateOf(TextFieldValue(text = dialog.currentName)) } + + TextInputDialog( + fieldValue = value, + confirmButton = DialogButton( + title = stringResource(id = R.string.common_save), + enabled = value.text.isNotEmpty() && value.text != dialog.currentName, + onClick = { dialog.onConfirm(value.text) }, + ), + onDismissDialog = dialog.onDismiss, + onValueChange = { newValue -> + value = newValue + }, + title = stringResource(R.string.user_wallet_list_rename_popup_title), + dismissButton = DialogButton( + title = stringResource(id = R.string.common_cancel), + onClick = dialog.onDismiss, + ), + textFieldParams = AdditionalTextInputDialogParams( + label = stringResource(R.string.user_wallet_list_rename_popup_placeholder), + ), + ) } // region Preview @@ -86,10 +51,9 @@ private fun RenameWalletDialogContentSample( modifier: Modifier = Modifier, ) { Column( - modifier = modifier - .background(TangemTheme.colors.background.primary), + modifier = modifier, ) { - RenameWalletDialogContent(dialog = RenameWalletDialog("", {}, {})) + RenameWalletDialogContent(dialog = DialogModel.RenameWalletDialog("", {}, {})) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 7897e34ae6..d10ed293f0 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -37,6 +37,7 @@ import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState import com.tangem.wallet.R +@Suppress("LongParameterList") @OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletSelectorScreenContent( @@ -72,8 +73,8 @@ internal fun WalletSelectorScreenContent( wallet = wallet, isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId }, isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds }, - onWalletClick = onWalletClick, - onWalletLongClick = onWalletLongClick, + onWalletClick = { onWalletClick(wallet.id) }, + onWalletLongClick = { onWalletLongClick(wallet.id) }, ) } @@ -89,20 +90,13 @@ internal fun WalletSelectorScreenContent( wallet = wallet, isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId }, isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds }, - onWalletClick = onWalletClick, - onWalletLongClick = onWalletLongClick, + onWalletClick = { onWalletClick(wallet.id) }, + onWalletLongClick = { onWalletLongClick(wallet.id) }, ) } item { Footer( - modifier = Modifier - .padding( - top = dimensionResource(id = R.dimen.spacing24), - bottom = dimensionResource(id = R.dimen.spacing16), - ) - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), isLocked = state.isLocked, showUnlockProgress = state.showUnlockProgress, showAddCardProgress = state.showAddCardProgress, @@ -140,9 +134,6 @@ private fun Header( ) { if (hasEditingWallets) { EditWalletsBar( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), editingWalletsSize = editingWalletsSize, onClearSelectedClick = onClearSelectedClick, onEditSelectedWalletClick = onEditSelectedWalletClick, @@ -174,7 +165,6 @@ private fun WalletsTitle(@StringRes textResId: Int, wallets: List<*>) { @Composable private fun Footer( - modifier: Modifier = Modifier, isLocked: Boolean, showUnlockProgress: Boolean, showAddCardProgress: Boolean, @@ -182,7 +172,13 @@ private fun Footer( onAddCardClick: () -> Unit, ) { Column( - modifier = modifier, + modifier = Modifier + .padding( + top = dimensionResource(id = R.dimen.spacing24), + bottom = dimensionResource(id = R.dimen.spacing16), + ) + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { if (isLocked) { @@ -200,7 +196,7 @@ private fun Footer( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.user_wallet_list_add_button), showProgress = showAddCardProgress, - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), onClick = onAddCardClick, ) } @@ -208,7 +204,6 @@ private fun Footer( @Composable private fun EditWalletsBar( - modifier: Modifier = Modifier, editingWalletsSize: Int, onClearSelectedClick: () -> Unit, onEditSelectedWalletClick: () -> Unit, @@ -219,7 +214,9 @@ private fun EditWalletsBar( } Row( - modifier = modifier, + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { @@ -229,7 +226,7 @@ private fun EditWalletsBar( ) { Icon( modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_close), + painter = painterResource(id = R.drawable.ic_close_24), tint = TangemTheme.colors.icon.primary1, contentDescription = "Unselect wallets", ) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index 6e67669815..2ce322509d 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -28,7 +28,6 @@ import com.tangem.core.ui.components.SpacerH2 import com.tangem.core.ui.components.SpacerW6 import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.res.TangemTheme -import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem @@ -42,16 +41,16 @@ internal fun WalletItem( wallet: UserWalletItem, isSelected: Boolean, isChecked: Boolean, - onWalletClick: (UserWalletId) -> Unit, - onWalletLongClick: (UserWalletId) -> Unit, + onWalletClick: () -> Unit, + onWalletLongClick: () -> Unit, ) { Row( modifier = Modifier .combinedClickable( - onClick = { onWalletClick(wallet.id) }, - onLongClick = { onWalletLongClick(wallet.id) }, + onClick = onWalletClick, + onLongClick = onWalletLongClick, ) - .height(72.dp) + .heightIn(min = TangemTheme.dimens.size72) .padding(all = TangemTheme.dimens.spacing16), verticalAlignment = Alignment.CenterVertically, ) { @@ -61,14 +60,9 @@ internal fun WalletItem( isSelected = isSelected, ) SpacerW8() - WalletInfo( - modifier = Modifier.weight(weight = .6f), - wallet = wallet, - isSelected = isSelected, - ) + WalletInfo(wallet = wallet, isSelected = isSelected) SpacerW6() TokensInfo( - modifier = Modifier.weight(weight = .4f), isLocked = wallet.isLocked, balance = wallet.balance, tokensCount = (wallet as? MultiCurrencyUserWalletItem)?.tokensCount, @@ -78,13 +72,12 @@ internal fun WalletItem( @Composable private fun WalletCardImage( - modifier: Modifier = Modifier, cardImageUrl: String, isChecked: Boolean, isSelected: Boolean, ) { Box( - modifier = modifier + modifier = Modifier .width(TangemTheme.dimens.size62) .height(TangemTheme.dimens.size42), ) { @@ -133,13 +126,12 @@ private fun WalletCardImage( @OptIn(ExperimentalComposeUiApi::class) @Composable -private fun WalletInfo( - modifier: Modifier = Modifier, +private fun RowScope.WalletInfo( wallet: UserWalletItem, isSelected: Boolean, ) { Column( - modifier = modifier, + modifier = Modifier.weight(weight = .6f), verticalArrangement = Arrangement.SpaceAround, ) { Text( @@ -166,14 +158,13 @@ private fun WalletInfo( } @Composable -private fun TokensInfo( - modifier: Modifier = Modifier, +private fun RowScope.TokensInfo( isLocked: Boolean, balance: UserWalletItem.Balance, tokensCount: Int?, ) { Column( - modifier = modifier, + modifier = Modifier.weight(weight = .4f), verticalArrangement = Arrangement.SpaceAround, horizontalAlignment = Alignment.End, ) { @@ -204,12 +195,9 @@ private fun TokensInfo( } @Composable -private fun LoadingTokensInfo( - modifier: Modifier = Modifier, - isMultiCurrencyWallet: Boolean, -) { +private fun LoadingTokensInfo(isMultiCurrencyWallet: Boolean) { Column( - modifier = modifier.shimmer(), + modifier = Modifier.shimmer(), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.SpaceAround, ) { @@ -240,10 +228,10 @@ private fun LoadingTokensInfo( @OptIn(ExperimentalComposeUiApi::class) @Composable private fun LoadedTokensInfo( - modifier: Modifier = Modifier, balanceAmount: String, tokensCount: Int?, showWarning: Boolean, + modifier: Modifier = Modifier, ) { Column( modifier = modifier, @@ -350,7 +338,7 @@ private fun SelectedWalletBadge( contentAlignment = Alignment.Center, ) { Icon( - modifier = Modifier.padding(all = (0.5).dp), + modifier = Modifier.padding(all = 0.5.dp), painter = painterResource(id = R.drawable.ic_check_circle_18), tint = TangemTheme.colors.icon.accent, contentDescription = null, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt new file mode 100644 index 0000000000..1dcfb1a2a5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt @@ -0,0 +1,14 @@ +package com.tangem.tap.features.walletSelector.ui.model + +internal sealed interface DialogModel { + data class RenameWalletDialog( + val currentName: String, + val onConfirm: (newName: String) -> Unit, + val onDismiss: () -> Unit, + ) : DialogModel + + data class RemoveWalletDialog( + val onConfirm: () -> Unit, + val onDismiss: () -> Unit, + ) : DialogModel +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/RenameWalletDialog.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/RenameWalletDialog.kt deleted file mode 100644 index e3969634ce..0000000000 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/RenameWalletDialog.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.tap.features.walletSelector.ui.model - -import androidx.compose.runtime.Immutable - -@Immutable -internal data class RenameWalletDialog( - val currentName: String, - val onApply: (newName: String) -> Unit, - val onCancel: () -> Unit, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt index 681a3cea77..0d248ec7af 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt @@ -15,7 +15,7 @@ internal sealed interface WelcomeAction : Action { data class Error(val error: TangemError) : WelcomeAction } - data class HandleDeepLink(val intent: Intent?) : WelcomeAction + data class HandleIntentIfNeeded(val intent: Intent?) : WelcomeAction object CloseError : WelcomeAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 4c39b5cb3c..4c91a6da7c 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -39,22 +39,24 @@ internal class WelcomeMiddleware { private fun handleAction(action: WelcomeAction, state: WelcomeState) { when (action) { is WelcomeAction.ProceedWithBiometrics -> { - proceedWithBiometry(state) + proceedWithBiometrics(state) } is WelcomeAction.ProceedWithCard -> { proceedWithCard(state) } + is WelcomeAction.HandleIntentIfNeeded -> { + handleInitialIntent(action.intent) + } is WelcomeAction.ProceedWithBiometrics.Error, is WelcomeAction.ProceedWithCard.Error, is WelcomeAction.ProceedWithBiometrics.Success, is WelcomeAction.ProceedWithCard.Success, is WelcomeAction.CloseError, - is WelcomeAction.HandleDeepLink, -> Unit } } - private fun proceedWithBiometry(state: WelcomeState) { + private fun proceedWithBiometrics(state: WelcomeState) { scope.launch { userWalletsListManager.unlockWithBiometry() .doOnFailure { error -> @@ -66,7 +68,7 @@ internal class WelcomeMiddleware { store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) store.onUserWalletSelected(selectedUserWallet) - handleDeepLinkIfNeeded(state.deepLinkIntent) + intentHandler.handleWalletConnectLink(state.intent) } } } @@ -85,13 +87,17 @@ internal class WelcomeMiddleware { store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) store.onUserWalletSelected(userWallet) - handleDeepLinkIfNeeded(state.deepLinkIntent) + intentHandler.handleWalletConnectLink(state.intent) } } } - private fun handleDeepLinkIfNeeded(intent: Intent?) { - intentHandler.handleWalletConnectLink(intent) + private fun handleInitialIntent(intent: Intent?) { + val isBackgroundScanWasHandled = intentHandler.handleBackgroundScan(intent, hasSavedUserWallets = true) + + if (!isBackgroundScanWasHandled) { + store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics) + } } private suspend inline fun scanCardInternal( diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt index 42a6d4d0e1..d7d71b7ce0 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt @@ -12,7 +12,7 @@ internal object WelcomeReducer { private fun internalReduce(action: WelcomeAction, state: WelcomeState): WelcomeState { return when (action) { - is WelcomeAction.HandleDeepLink -> state.copy(deepLinkIntent = action.intent) + is WelcomeAction.HandleIntentIfNeeded -> state.copy(intent = action.intent) is WelcomeAction.ProceedWithBiometrics -> state.copy(isUnlockWithBiometricsInProgress = true) is WelcomeAction.ProceedWithCard -> state.copy(isUnlockWithCardInProgress = true) is WelcomeAction.ProceedWithBiometrics.Error -> state.copy( diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt index f40046600d..a8a55d3984 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt @@ -7,6 +7,6 @@ import org.rekotlin.StateType data class WelcomeState( val isUnlockWithBiometricsInProgress: Boolean = false, val isUnlockWithCardInProgress: Boolean = false, - val deepLinkIntent: Intent? = null, + val intent: Intent? = null, val error: TangemError? = null, ) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt index 70477f5ad1..7cebf15891 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt @@ -42,10 +42,7 @@ internal class WelcomeFragment : ComposeFragment() { } @Composable - override fun ScreenContent( - modifier: Modifier, - state: WelcomeScreenState, - ) { + override fun ScreenContent(state: WelcomeScreenState, modifier: Modifier) { val snackbarHostState = remember { SnackbarHostState() } val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference()) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index c0a780226b..4cf55f6bcd 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -24,18 +24,15 @@ import com.tangem.core.ui.components.SpacerHMax import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R +@Suppress("LongMethod") @Composable internal fun WelcomeScreenContent( - modifier: Modifier = Modifier, showUnlockProgress: Boolean, showScanCardProgress: Boolean, onUnlockClick: () -> Unit, onScanCardClick: () -> Unit, ) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally, - ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { SpacerHMax() Icon( modifier = Modifier.size(TangemTheme.dimens.size96), @@ -85,7 +82,7 @@ internal fun WelcomeScreenContent( .fillMaxWidth(), text = stringResource(R.string.welcome_unlock_card), showProgress = showScanCardProgress, - icon = painterResource(id = R.drawable.ic_tangem), + icon = painterResource(id = R.drawable.ic_tangem_24), onClick = onScanCardClick, ) SpacerH16() diff --git a/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt b/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt index 00c2f87e19..71d0ed9206 100644 --- a/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt +++ b/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt @@ -15,8 +15,8 @@ import java.lang.ref.WeakReference [REDACTED_AUTHOR] */ class NetworkConnectivity( - private val store: Store<*>, - context: Context + private val store: Store<*>, + context: Context, ) { private val wContext: WeakReference = WeakReference(context) @@ -34,19 +34,16 @@ class NetworkConnectivity( store.dispatch(NetworkStateChanged(isOnlineOrConnecting())) } - fun isOnlineOrConnecting(): Boolean { val connectivityManager = getConnectivityManager() ?: return false - return if (Build.VERSION.SDK_INT >= 23) { - val capabilities = - connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) - (capabilities != null) && - (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) || - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) - + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + capabilities != null && + (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) } else { val networkInfo = connectivityManager.activeNetworkInfo networkInfo != null && networkInfo.isConnectedOrConnecting @@ -55,7 +52,7 @@ class NetworkConnectivity( private fun getConnectivityManager(): ConnectivityManager? { return wContext.get()?.applicationContext - ?.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager + ?.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager } companion object { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt index 3f61d82801..671c45af11 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -59,7 +59,7 @@ class CurrencyExchangeManager( blockchain, cryptoCurrencyName, fiatCurrencyName, - walletAddress + walletAddress, ) } @@ -86,7 +86,6 @@ class CurrencyExchangeManager( } } -@Suppress("unused") suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( card: CardDTO, walletManager: EthereumWalletManager, @@ -97,13 +96,15 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( val amountToSend = Amount(walletManager.wallet.blockchain) val destinationAddress = token.contractAddress - val feeResult = walletManager.getFee(amountToSend, - destinationAddress) as? Result.Success ?: return + val feeResult = + walletManager.getFee( + amountToSend, + destinationAddress, + ) as? Result.Success ?: return val fee = feeResult.data[0] - if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) { - return - } + val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO + if (coinValue < fee.value) return val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress) @@ -116,8 +117,8 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( GlobalAction.UpdateWalletSignedHashes( walletSignedHashes = signResponse.totalSignedHashes, walletPublicKey = walletManager.wallet.publicKey.seedKey, - remainingSignatures = signResponse.remainingSignatures - ) + remainingSignatures = signResponse.remainingSignatures, + ), ) } walletManager.send(transaction, signer) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt index 8d6e8e2187..0309ce9ee9 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt @@ -7,11 +7,11 @@ import com.tangem.tap.features.wallet.models.Currency interface Exchanger { fun isBuyAllowed(): Boolean fun isSellAllowed(): Boolean - fun availableForBuy(currency: Currency):Boolean - fun availableForSell(currency: Currency):Boolean + fun availableForBuy(currency: Currency): Boolean + fun availableForSell(currency: Currency): Boolean } -interface ExchangeService: Feature, Exchanger { +interface ExchangeService : Feature, Exchanger { suspend fun update() companion object { @@ -26,7 +26,7 @@ interface ExchangeService: Feature, Exchanger { } } -interface ExchangeRules: Feature, Exchanger { +interface ExchangeRules : Feature, Exchanger { companion object { fun dummy(): ExchangeRules = object : ExchangeRules { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index 9dd2b9746f..48700b18ea 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -33,11 +33,12 @@ class MercuryoService( override fun featureIsSwitchedOn(): Boolean = true + @Suppress("NestedBlockDepth") override suspend fun update() { when (val result = performRequest { api.currencies(apiVersion) }) { is Result.Success -> { val response = result.data - if (response.status == 200) { + if (response.status == RESPONSE_SUCCESS_STATUS_CODE) { // all currencies which can be bought val currenciesAvailableToBy = response.data.crypto // tokens which can be bought only from specific blockchain network @@ -108,7 +109,7 @@ class MercuryoService( blockchain: Blockchain, cryptoCurrencyName: CryptoCurrencyName, fiatCurrencyName: String, - walletAddress: String + walletAddress: String, ): String { if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException() @@ -131,10 +132,9 @@ class MercuryoService( return (address + secret).calculateSha512().toHexString().lowercase() } - override fun getSellCryptoReceiptUrl( action: CurrencyExchangeManager.Action, - transactionId: String + transactionId: String, ): String? = null private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) { @@ -143,4 +143,8 @@ class MercuryoService( "ADA" -> Blockchain.CardanoShelley else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() } } -} + + companion object { + private const val RESPONSE_SUCCESS_STATUS_CODE = 200 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt index 37dbe6185a..27dc8d0c3b 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt @@ -17,7 +17,6 @@ interface MoonPayApi { @Query("apiKey") moonPayApiKey: String, ): List - companion object { const val MOOONPAY_BASE_URL = "https://api.moonpay.com/" const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "v4/ip_address/" @@ -25,7 +24,6 @@ interface MoonPayApi { } } - @JsonClass(generateAdapter = true) data class MoonPayUserStatus( val isBuyAllowed: Boolean, diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 645d5182ed..e6752f1272 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -5,8 +5,8 @@ import android.util.Base64 import com.tangem.blockchain.common.Blockchain import com.tangem.common.services.Result import com.tangem.common.services.performRequest -import com.tangem.domain.common.extensions.withIOContext import com.tangem.datasource.api.common.createRetrofitInstance +import com.tangem.domain.common.extensions.withIOContext import com.tangem.tap.common.extensions.urlEncode import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.features.wallet.models.Currency @@ -130,7 +130,6 @@ class MoonPayService( .appendPath("transaction_receipt") .appendQueryParameter("transactionId", transactionId).build().toString() return url - } private fun createSignature(data: String): String { diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdApi.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdApi.kt deleted file mode 100644 index 8b00ecefd7..0000000000 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdApi.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.tap.network.payid - -import retrofit2.http.GET -import retrofit2.http.POST -import retrofit2.http.Query - -interface PayIdApi { - @GET(API_PAY_ID_TANGEM) - suspend fun getPayId( - @Query("cid") cardId: String, - @Query("key") publicKey: String - ): PayIdResponse - - @POST(API_PAY_ID_TANGEM) - suspend fun setPayId( - @Query("cid") cardId: String, - @Query("key") publicKey: String, - @Query("payid") payId: String, - @Query("address") address: String, - @Query("network") network: String - ): SetPayIdResponse - - companion object { - const val API_PAY_ID_TANGEM = "https://payid.tangem.com/" - } -} diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt deleted file mode 100644 index d9cc63b72a..0000000000 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.tangem.tap.network.payid - -import com.squareup.moshi.JsonClass -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest -import com.tangem.datasource.api.common.createRetrofitInstance -import retrofit2.Retrofit - -class PayIdService { - - private val payIdApi: PayIdApi by lazy { - provideRetrofit() - .create(PayIdApi::class.java) - } - - suspend fun getPayId(cardId: String, publicKey: String): Result { - return performRequest { payIdApi.getPayId(cardId, publicKey) } - } - - suspend fun setPayId( - cardId: String, publicKey: String, payId: String, address: String, network: String - ): Result { - return performRequest { payIdApi.setPayId(cardId, publicKey, payId, address, network) } - } - - private fun provideRetrofit(): Retrofit = createRetrofitInstance( - baseUrl = "https://tangem.com/", - logEnabled = false, - ) - -} - -@JsonClass(generateAdapter = true) -data class PayIdResponse( - val payId: String -) - -@JsonClass(generateAdapter = true) -data class SetPayIdResponse( - val success: Boolean -) diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt index a40231aaf5..8566c89a86 100644 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt @@ -11,34 +11,31 @@ import retrofit2.http.Path interface PayIdVerifyApi { @GET("{user}") suspend fun verifyAddress( - @Path("user") user: String, - @Header("Accept") acceptNetworkHeader: String, - @Header("PayID-Version") payIdVersion: String = "1.0" + @Path("user") user: String, + @Header("Accept") acceptNetworkHeader: String, + @Header("PayID-Version") payIdVersion: String = "1.0", ): VerifyPayIdResponse } - @JsonClass(generateAdapter = true) data class VerifyPayIdResponse( - val addresses: List = mutableListOf(), - val payId: String? = null, + val addresses: List = mutableListOf(), + val payId: String? = null, ) { - fun getAddressDetails(): PayIdAddressDetails? { - return if (addresses.isEmpty()) null - else addresses[0].addressDetails - } + fun getAddressDetails(): PayIdAddressDetails? = + if (addresses.isNotEmpty()) addresses[0].addressDetails else null } @JsonClass(generateAdapter = true) data class PayIdAddress( - var paymentNetwork: String, - var environment: String, - var addressDetailsType: String, - var addressDetails: PayIdAddressDetails + var paymentNetwork: String, + var environment: String, + var addressDetailsType: String, + var addressDetails: PayIdAddressDetails, ) @JsonClass(generateAdapter = true) data class PayIdAddressDetails( - var address: String, - var tag: String? = null + var address: String, + var tag: String? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt b/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt new file mode 100644 index 0000000000..0385071ff3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.persistence + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import com.tangem.tap.common.analytics.events.AnalyticsParam + +class CardBalanceStateAdapter { + + @ToJson + fun toJson(src: AnalyticsParam.CardBalanceState): String = src.value + + @FromJson + fun fromJson(json: String): AnalyticsParam.CardBalanceState { + return when (json) { + AnalyticsParam.CardBalanceState.Empty.value -> AnalyticsParam.CardBalanceState.Empty + AnalyticsParam.CardBalanceState.Full.value -> AnalyticsParam.CardBalanceState.Full + else -> error("CardBalanceState not found") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt index 59c500f9a7..a52f38f854 100644 --- a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt @@ -3,7 +3,7 @@ package com.tangem.tap.persistence import android.content.SharedPreferences import androidx.core.content.edit import com.tangem.common.json.MoshiJsonConverter -import com.tangem.datasource.api.tangemTech.CurrenciesResponse +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.tap.common.entities.FiatCurrency /** diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index d419aafffc..a6ff52c139 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -4,29 +4,35 @@ import android.app.Application import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit -import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.datasource.api.common.BigDecimalAdapter import java.util.* class PreferencesStorage(applicationContext: Application) { - private val preferences: SharedPreferences = - applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) - val appRatingLaunchObserver: AppRatingLaunchObserver val usedCardsPrefStorage: UsedCardsPrefStorage val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage val disclaimerPrefStorage: DisclaimerPrefStorage val toppedUpWalletStorage: ToppedUpWalletStorage + private val preferences: SharedPreferences = + applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + + private val moshiConverter = MoshiJsonConverter( + adapters = listOf(BigDecimalAdapter(), CardBalanceStateAdapter()) + MoshiJsonConverter.getTangemSdkAdapters(), + typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(), + ) + init { incrementLaunchCounter() appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches()) - usedCardsPrefStorage = UsedCardsPrefStorage(preferences, MoshiConverter.INSTANCE) + usedCardsPrefStorage = UsedCardsPrefStorage(preferences, moshiConverter) usedCardsPrefStorage.migrate() - fiatCurrenciesPrefStorage = FiatCurrenciesPrefStorage(preferences, MoshiConverter.INSTANCE) + fiatCurrenciesPrefStorage = FiatCurrenciesPrefStorage(preferences, moshiConverter) fiatCurrenciesPrefStorage.migrate() disclaimerPrefStorage = DisclaimerPrefStorage(preferences) - toppedUpWalletStorage = ToppedUpWalletStorage(preferences, MoshiConverter.INSTANCE) + toppedUpWalletStorage = ToppedUpWalletStorage(preferences, moshiConverter) } var chatFirstLaunchTime: Long? @@ -95,11 +101,7 @@ class AppRatingLaunchObserver( private val preferences: SharedPreferences, private val launchCounts: Int, ) { - private val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount" - private val K_FUNDS_FOUND_DATE = "fundsFoundDate" - private val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating" - private val FUNDS_FOUND_DATE_UNDEFINED = -1L private val deferShowing = 20 private val firstShowing = 3 private var fundsFoundDate: Calendar? = null @@ -121,6 +123,7 @@ class AppRatingLaunchObserver( } } + @Suppress("MagicNumber") fun isReadyToShow(): Boolean { val fundsDate = fundsFoundDate ?: return false @@ -138,6 +141,7 @@ class AppRatingLaunchObserver( updateNextShowing(launchCounts + deferShowing) } + @Suppress("MagicNumber") fun setNeverToShow() { updateNextShowing(999999999) } @@ -149,6 +153,16 @@ class AppRatingLaunchObserver( editor.apply() } - private fun userWasInteractWithRating(): Boolean = preferences.getBoolean(K_USER_WAS_INTERACT_WITH_RATING, false) - private fun getCounterOfNextShowing(): Int = preferences.getInt(K_SHOW_RATING_AT_LAUNCH_COUNT, firstShowing) + private fun userWasInteractWithRating(): Boolean = + preferences.getBoolean(K_USER_WAS_INTERACT_WITH_RATING, false) + + private fun getCounterOfNextShowing(): Int = + preferences.getInt(K_SHOW_RATING_AT_LAUNCH_COUNT, firstShowing) + + companion object { + private const val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount" + private const val K_FUNDS_FOUND_DATE = "fundsFoundDate" + private const val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating" + private const val FUNDS_FOUND_DATE_UNDEFINED = -1L + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt b/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt index e270b2b820..75759b8d75 100644 --- a/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt @@ -2,10 +2,7 @@ package com.tangem.tap.persistence import android.content.SharedPreferences import androidx.core.content.edit -import com.squareup.moshi.FromJson -import com.squareup.moshi.ToJson import com.tangem.common.json.MoshiJsonConverter -import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.filters.BasicTopUpFilter import timber.log.Timber @@ -59,18 +56,4 @@ class ToppedUpWalletStorage( companion object { private const val KEY = "userWalletsInfo" } -} - -class CardBalanceStateAdapter { - @ToJson - fun toJson(src: AnalyticsParam.CardBalanceState): String = src.value - - @FromJson - fun fromJson(json: String): AnalyticsParam.CardBalanceState { - return when (json) { - AnalyticsParam.CardBalanceState.Empty.value -> AnalyticsParam.CardBalanceState.Empty - AnalyticsParam.CardBalanceState.Full.value -> AnalyticsParam.CardBalanceState.Full - else -> throw IllegalArgumentException() - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index cfe9b9cb17..ed21a083df 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -1,7 +1,9 @@ package com.tangem.tap.proxy +import com.tangem.TangemSdk import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.tokens.UserTokensRepository @@ -9,20 +11,23 @@ import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.walletStores.WalletStoresManager import com.tangem.tap.features.wallet.redux.WalletState import org.rekotlin.Store +import javax.inject.Inject /** * Holds objects from old modules, that missing in DI graph * Object sets manually to use in new modules and [AppStateHolder] proxies its to DI */ -class AppStateHolder { +class AppStateHolder @Inject constructor() { var scanResponse: ScanResponse? = null var walletState: WalletState? = null var userTokensRepository: UserTokensRepository? = null var mainStore: Store? = null var tangemSdkManager: TangemSdkManager? = null + var tangemSdk: TangemSdk? = null var walletStoresManager: WalletStoresManager? = null var userWalletsListManager: UserWalletsListManager? = null + var appFiatCurrency: FiatCurrency = FiatCurrency.Default fun getActualCard(): CardDTO? { return scanResponse?.card diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index 265b0aa182..246d1ac726 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -12,7 +12,7 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.NonNativeToken +import com.tangem.lib.crypto.models.Currency.NonNativeToken import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.extensions.dispatchDebugErrorNotification diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt new file mode 100644 index 0000000000..9ad9faf3a0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -0,0 +1,294 @@ +package com.tangem.tap.proxy + +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tangem.Message +import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras +import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.TransactionExtras +import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.extensions.Result +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.hexToBytes +import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.lib.crypto.TransactionManager +import com.tangem.lib.crypto.models.Currency +import com.tangem.lib.crypto.models.ProxyAmount +import com.tangem.lib.crypto.models.ProxyNetworkInfo +import com.tangem.lib.crypto.models.transactions.SendTxResult +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.TangemSigner +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.store +import com.tangem.tap.tangemSdk +import java.math.BigDecimal + +class TransactionManagerImpl( + private val appStateHolder: AppStateHolder, +) : TransactionManager { + + override suspend fun sendApproveTransaction( + networkId: String, + feeAmount: BigDecimal, + estimatedGas: Int, + destinationAddress: String, + dataToSign: String, + ): SendTxResult { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getActualWalletManager(blockchain) + walletManager.update() + val amount = Amount(value = BigDecimal.ZERO, blockchain = blockchain) + return sendTransactionInternal( + walletManager = walletManager, + amount = amount, + blockchain = blockchain, + feeAmount = feeAmount, + estimatedGas = estimatedGas, + destinationAddress = destinationAddress, + dataToSign = dataToSign, + ) + } + + override suspend fun sendTransaction( + networkId: String, + amountToSend: BigDecimal, + feeAmount: BigDecimal, + estimatedGas: Int, + destinationAddress: String, + dataToSign: String, + isSwap: Boolean, + currencyToSend: Currency, + ): SendTxResult { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getActualWalletManager(blockchain) + walletManager.update() + val amount = if (isSwap) { + createAmountForSwap(amountToSend, currencyToSend, blockchain) + } else { + createAmount(amountToSend, currencyToSend, blockchain) + } + return sendTransactionInternal( + walletManager = walletManager, + amount = amount, + blockchain = blockchain, + feeAmount = feeAmount, + estimatedGas = estimatedGas, + destinationAddress = destinationAddress, + dataToSign = dataToSign, + ) + } + + @Suppress("LongParameterList") + private suspend fun sendTransactionInternal( + walletManager: WalletManager, + amount: Amount, + blockchain: Blockchain, + feeAmount: BigDecimal, + estimatedGas: Int, + destinationAddress: String, + dataToSign: String, + ): SendTxResult { + val txData = walletManager.createTransaction( + amount = amount, + fee = Amount(value = feeAmount, blockchain = blockchain), + destination = destinationAddress, + ).copy(hash = dataToSign, extras = createExtras(walletManager, estimatedGas, dataToSign)) + + val signer = transactionSigner(walletManager) + + val sendResult = try { + (walletManager as TransactionSender).send(txData, signer) + } catch (ex: Exception) { + FirebaseCrashlytics.getInstance().recordException(ex) + return SendTxResult.UnknownError(ex) + } + return handleSendResult(sendResult) + } + + override fun getExplorerTransactionLink(networkId: String, txAddress: String): String { + val blockchain = Blockchain.fromNetworkId(networkId) ?: error("blockchain not found") + return blockchain.getExploreTxUrl(txAddress) + } + + override fun getNativeTokenDecimals(networkId: String): Int { + return Blockchain.fromNetworkId(networkId)?.decimals() ?: error("blockchain not found") + } + + override suspend fun updateWalletManager(networkId: String) { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + getActualWalletManager(blockchain).update() + } + + override fun calculateFee(networkId: String, gasPrice: String, estimatedGas: Int): BigDecimal { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val gasPriceValue = requireNotNull(gasPrice.toLongOrNull()) { "gasprice should be Long" } + return (gasPriceValue * estimatedGas).toBigDecimal().movePointLeft(blockchain.decimals()) + } + + @Throws(IllegalStateException::class) + override suspend fun getFee( + networkId: String, + amountToSend: BigDecimal, + currencyToSend: Currency, + destinationAddress: String, + ): ProxyAmount { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getActualWalletManager(blockchain) + val fee = (walletManager as TransactionSender).getFee( + amount = createAmount(amountToSend, currencyToSend, blockchain), + destination = destinationAddress, + ) + when (fee) { + is Result.Success -> { + return convertToProxyAmount(fee.data.firstOrNull() ?: error("no fee found")) + } + is Result.Failure -> { + error(fee.error.message ?: fee.error.customMessage) + } + } + } + + override fun getBlockchainInfo(networkId: String): ProxyNetworkInfo { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + return ProxyNetworkInfo( + name = blockchain.fullName, + blockchainId = blockchain.id, + blockchainCurrency = blockchain.currency, + ) + } + + private fun handleSendResult(result: SimpleResult): SendTxResult { + when (result) { + is SimpleResult.Success -> return SendTxResult.Success + is SimpleResult.Failure -> { + val error = result.error as? BlockchainSdkError ?: return SendTxResult.UnknownError() + when (error) { + is BlockchainSdkError.WrappedTangemError -> { + val tangemSdkError = error.tangemError as? TangemSdkError ?: return SendTxResult.UnknownError() + if (tangemSdkError is TangemSdkError.UserCancelled) return SendTxResult.UserCancelledError + return SendTxResult.TangemSdkError(tangemSdkError.code, tangemSdkError.cause) + } + else -> { + return SendTxResult.TangemSdkError(error.code, error.cause) + } + } + } + } + } + + private fun transactionSigner(walletManager: WalletManager): TransactionSigner { + val actualCard = requireNotNull(appStateHolder.getActualCard()) { "no card found" } + return TangemSigner( + card = actualCard, + tangemSdk = tangemSdk, + initialMessage = Message(), + ) { signResponse -> + store.dispatch( + GlobalAction.UpdateWalletSignedHashes( + walletSignedHashes = signResponse.totalSignedHashes, + walletPublicKey = walletManager.wallet.publicKey.seedKey, + remainingSignatures = signResponse.remainingSignatures, + ), + ) + } + } + + private fun getActualWalletManager(blockchain: Blockchain): WalletManager { + val card = appStateHolder.getActualCard() + if (card != null) { + val blockchainNetwork = BlockchainNetwork(blockchain, card) + val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) + if (walletManager != null) { + return walletManager + } else { + error("no wallet manager found") + } + } else { + error("card not found") + } + } + + private fun createExtras( + walletManager: WalletManager, + estimatedGas: Int, + transactionHash: String, + ): TransactionExtras? { + return when (walletManager) { + is EthereumWalletManager -> { + return EthereumTransactionExtras( + data = transactionHash.removePrefix(HEX_PREFIX).hexToBytes(), + gasLimit = estimatedGas.toBigInteger(), + ) + } + else -> { + null + } + } + } + + private fun createAmount( + amount: BigDecimal, + currency: Currency, + blockchain: Blockchain, + ): Amount { + return when (currency) { + is Currency.NativeToken -> { + Amount(value = amount, blockchain = blockchain) + } + is Currency.NonNativeToken -> { + Amount(convertNonNativeToken(currency), amount) + } + } + } + + private fun createAmountForSwap( + amount: BigDecimal, + currency: Currency?, + blockchain: Blockchain, + ): Amount { + return when (currency) { + is Currency.NativeToken, + null, + -> { + Amount(value = amount, blockchain = blockchain) + } + is Currency.NonNativeToken -> { + // 1. when creates swap amount for NonNativeToken, amount should be ZERO + // 2. Amount has .Coin type, as workaround to use destinationAddress in bsdk, not contractAddress + Amount( + currencySymbol = currency.symbol, + value = BigDecimal.ZERO, + decimals = currency.decimalCount, + ) + } + } + } + + private fun convertNonNativeToken(token: Currency.NonNativeToken): Token { + return Token( + name = token.name, + symbol = token.symbol, + contractAddress = token.contractAddress, + decimals = token.decimalCount, + id = token.id, + ) + } + + private fun convertToProxyAmount(amount: Amount): ProxyAmount { + return ProxyAmount( + currencySymbol = amount.currencySymbol, + value = amount.value ?: BigDecimal.ZERO, + decimals = amount.decimals, + ) + } + + companion object { + private const val HEX_PREFIX = "0x" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index e12ca9b9be..bb87e54ed9 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -1,35 +1,50 @@ package com.tangem.tap.proxy +import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationParams import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.common.extensions.toNetworkId import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.NativeToken -import com.tangem.lib.crypto.models.NonNativeToken +import com.tangem.lib.crypto.models.Currency.NativeToken +import com.tangem.lib.crypto.models.Currency.NonNativeToken +import com.tangem.lib.crypto.models.ProxyAmount +import com.tangem.lib.crypto.models.ProxyFiatCurrency +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import org.rekotlin.Action +import java.math.BigDecimal class UserWalletManagerImpl( private val appStateHolder: AppStateHolder, private val walletManagerFactory: WalletManagerFactory, ) : UserWalletManager { - override suspend fun getUserTokens(): List { + override suspend fun getUserTokens(networkId: String, isExcludeCustom: Boolean): List { val card = appStateHolder.getActualCard() val userTokensRepository = requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" } - if (card != null) { + return if (card != null) { userTokensRepository.getUserTokens(card) - .filter { it.isToken() } + .filter { + val checkCustom = if (isExcludeCustom) { + !it.isCustomCurrency(null) + } else { + true + } + it.blockchain.toNetworkId() == networkId && checkCustom + } .map { if (it is com.tangem.tap.features.wallet.models.Currency.Token) { NonNativeToken( @@ -49,8 +64,19 @@ class UserWalletManagerImpl( ) } } + } else { + emptyList() } - return emptyList() + } + + override fun getNativeTokenForNetwork(networkId: String): Currency { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + return NativeToken( + id = blockchain.toCoinId(), + name = blockchain.fullName, + symbol = blockchain.currency, + networkId = networkId, + ) } override fun getWalletId(): String { @@ -68,90 +94,155 @@ class UserWalletManagerImpl( requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" } if (card != null) { return userTokensRepository.getUserTokens(card) - .any { it.coinId.equals(currency.id) } //todo ensure that its the same ids + .any { it.coinId.equals(currency.id) } // todo ensure that its the same ids } return false } - //todo check is it sync operation + // todo check is it sync operation override fun addToken(currency: Currency) { + val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" } + val blockchain = requireNotNull(Blockchain.fromNetworkId(currency.networkId)) { "blockchain not found" } val action = if (currency is NativeToken) { - val card = appStateHolder.getActualCard() - if (card != null) { - addNativeTokenToWalletAction(currency, card) - } else { - throw IllegalStateException("card not found") - } + addNativeTokenToWalletAction(card, blockchain) } else { - addNonNativeTokenToWalletAction(currency as NonNativeToken) + addNonNativeTokenToWalletAction(currency as NonNativeToken, card, blockchain) } val mainStore = requireNotNull(appStateHolder.mainStore) { "mainStore is null" } - mainStore.dispatch(action) + mainStore.dispatchOnMain(action) } - override fun getWalletAddress(currency: Currency): String { - val blockchain = Blockchain.fromNetworkId(currency.networkId) - val card = appStateHolder.getActualCard() - if (blockchain != null && card != null) { - val blockchainNetwork = BlockchainNetwork(blockchain, card) - val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) - if (walletManager != null) { - return walletManager.wallet.address - } else { - error("no wallet manager found") - } + override fun getWalletAddress(networkId: String): String { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" } + val blockchainNetwork = BlockchainNetwork(blockchain, card) + val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) + if (walletManager != null) { + return walletManager.wallet.address } else { - error("no blockchain or card found") + error("no wallet manager found") } } - private fun addNativeTokenToWalletAction(token: NativeToken, card: CardDTO): Action { - val blockchain = Blockchain.fromNetworkId(token.networkId) - val scanResponse = appStateHolder.scanResponse - if (blockchain != null && scanResponse != null) { - val blockchainNetwork = BlockchainNetwork(blockchain, card) - var walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) - if (walletManager == null) { - walletManager = walletManagerFactory.makeWalletManagerForApp( - scanResponse = scanResponse, - blockchain = blockchain, - derivationParams = createDerivationParams(card.derivationStyle), + override fun getLastTransactionHash(networkId: String): String? { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" } + val blockchainNetwork = BlockchainNetwork(blockchain, card) + val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) + return walletManager?.wallet?.recentTransactions?.lastOrNull()?.hash?.let { HEX_PREFIX + it } + } + + override suspend fun getCurrentWalletTokensBalance( + networkId: String, + extraTokens: List, + ): Map { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getActualWalletManager(blockchain) + + // workaround for get balance for tokens that doesn't exist in wallet + val extraTokensToLoadBalance = extraTokens + .filterIsInstance() + .map { + Token( + symbol = it.symbol, + contractAddress = it.contractAddress, + decimals = it.decimalCount, ) } - return WalletAction.MultiWallet.AddBlockchain( - blockchain = blockchainNetwork, - walletManager = walletManager, - save = true, + .filter { + !walletManager.cardTokens.contains(it) + } + walletManager.addTokens(extraTokensToLoadBalance) + walletManager.update() + val balances = walletManager.wallet.amounts.map { entry -> + val amount = entry.value + amount.currencySymbol to ProxyAmount( + amount.currencySymbol, + amount.value ?: BigDecimal.ZERO, + amount.decimals, + ) + }.toMap() + extraTokensToLoadBalance.forEach { walletManager.removeToken(it) } + return balances + } + + override fun getNativeTokenBalance(networkId: String): ProxyAmount? { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getActualWalletManager(blockchain) + return walletManager.wallet.amounts.firstNotNullOfOrNull { + it.takeIf { it.key is AmountType.Coin } + }?.value?.let { + ProxyAmount( + it.currencySymbol, + it.value ?: BigDecimal.ZERO, + it.decimals, ) - } else { - error("blockchain is not supported") } } - private fun addNonNativeTokenToWalletAction(token: NonNativeToken): Action { - val card = appStateHolder.getActualCard() - val blockchain = Blockchain.fromNetworkId(token.networkId) - if (card != null && blockchain != null) { - return WalletAction.MultiWallet.AddToken( - token = com.tangem.blockchain.common.Token( - id = token.id, - name = token.name, - symbol = token.symbol, - contractAddress = token.contractAddress, - decimals = token.decimalCount, - ), - blockchain = BlockchainNetwork( - blockchain, - card, - ), - save = true, + override fun getNetworkCurrency(networkId: String): String { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + return blockchain.currency + } + + override fun getUserAppCurrency(): ProxyFiatCurrency { + val appCurrency = appStateHolder.appFiatCurrency + return ProxyFiatCurrency( + code = appCurrency.code, + name = appCurrency.name, + symbol = appCurrency.symbol, + ) + } + + private fun addNativeTokenToWalletAction(card: CardDTO, blockchain: Blockchain): Action { + val scanResponse = requireNotNull(appStateHolder.scanResponse) { "scanResponse not found" } + val blockchainNetwork = BlockchainNetwork(blockchain, card) + var walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) + if (walletManager == null) { + walletManager = walletManagerFactory.makeWalletManagerForApp( + scanResponse = scanResponse, + blockchain = blockchain, + derivationParams = createDerivationParams(card.derivationStyle), ) - } else { - error("no card or blockchain found") } + return WalletAction.MultiWallet.AddBlockchain( + blockchain = blockchainNetwork, + walletManager = walletManager, + save = true, + ) + } + + private fun addNonNativeTokenToWalletAction(token: NonNativeToken, card: CardDTO, blockchain: Blockchain): Action { + return WalletAction.MultiWallet.AddToken( + token = Token( + id = token.id, + name = token.name, + symbol = token.symbol, + contractAddress = token.contractAddress, + decimals = token.decimalCount, + ), + blockchain = BlockchainNetwork( + blockchain, + card, + ), + save = true, + ) } private fun createDerivationParams(derivationStyle: DerivationStyle?): DerivationParams? { - return derivationStyle?.let { DerivationParams.Default(derivationStyle) } //todo clarify if its need to add Custom + // todo clarify if its need to add Custom + return derivationStyle?.let { DerivationParams.Default(derivationStyle) } + } + + private fun getActualWalletManager(blockchain: Blockchain): WalletManager { + val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" } + val blockchainNetwork = BlockchainNetwork(blockchain, card) + return requireNotNull(appStateHolder.walletState?.getWalletManager(blockchainNetwork)) { + "No wallet manager found" + } + } + + companion object { + private const val HEX_PREFIX = "0x" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index 07d3fb1e8d..1a65b1ddd0 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -2,9 +2,11 @@ package com.tangem.tap.proxy.di import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.lib.crypto.DerivationManager +import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.DerivationManagerImpl +import com.tangem.tap.proxy.TransactionManagerImpl import com.tangem.tap.proxy.UserWalletManagerImpl import dagger.Module import dagger.Provides @@ -31,6 +33,12 @@ class ProxyModule { ) } + @Provides + @Singleton + fun provideTransactionManager(appStateHolder: AppStateHolder): TransactionManager { + return TransactionManagerImpl(appStateHolder) + } + @Provides @Singleton fun provideDerivationManager(appStateHolder: AppStateHolder): DerivationManager { diff --git a/app/src/main/res/color/btn_tap_text_color.xml b/app/src/main/res/color/btn_tap_text_color.xml deleted file mode 100644 index 01e0752f01..0000000000 --- a/app/src/main/res/color/btn_tap_text_color.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/selector_btn_green.xml b/app/src/main/res/color/selector_btn_green.xml index 32bd8b9847..0af5088cd4 100644 --- a/app/src/main/res/color/selector_btn_green.xml +++ b/app/src/main/res/color/selector_btn_green.xml @@ -1,6 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_btn_primary.xml b/app/src/main/res/color/selector_btn_primary.xml new file mode 100644 index 0000000000..6e8aab4c79 --- /dev/null +++ b/app/src/main/res/color/selector_btn_primary.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/selector_btn_secondary.xml b/app/src/main/res/color/selector_btn_secondary.xml new file mode 100644 index 0000000000..47a88e3645 --- /dev/null +++ b/app/src/main/res/color/selector_btn_secondary.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/selector_btn_text_primary.xml b/app/src/main/res/color/selector_btn_text_primary.xml new file mode 100644 index 0000000000..70f6aded83 --- /dev/null +++ b/app/src/main/res/color/selector_btn_text_primary.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/selector_btn_text_secondary.xml b/app/src/main/res/color/selector_btn_text_secondary.xml new file mode 100644 index 0000000000..41aa7c843c --- /dev/null +++ b/app/src/main/res/color/selector_btn_text_secondary.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arbitrum_round.xml b/app/src/main/res/drawable/ic_arbitrum_round.xml deleted file mode 100644 index 632ea33dae..0000000000 --- a/app/src/main/res/drawable/ic_arbitrum_round.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_avalanche_round.xml b/app/src/main/res/drawable/ic_avalanche_round.xml deleted file mode 100644 index ac2c5623a1..0000000000 --- a/app/src/main/res/drawable/ic_avalanche_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_binance_round.xml b/app/src/main/res/drawable/ic_binance_round.xml deleted file mode 100644 index 6b399342e0..0000000000 --- a/app/src/main/res/drawable/ic_binance_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_bitcoin_cash_round.xml b/app/src/main/res/drawable/ic_bitcoin_cash_round.xml deleted file mode 100644 index 704b9c7c96..0000000000 --- a/app/src/main/res/drawable/ic_bitcoin_cash_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_bitcoin_round.xml b/app/src/main/res/drawable/ic_bitcoin_round.xml deleted file mode 100644 index ab0c448c23..0000000000 --- a/app/src/main/res/drawable/ic_bitcoin_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_bsc_round.xml b/app/src/main/res/drawable/ic_bsc_round.xml deleted file mode 100644 index cca406ef7b..0000000000 --- a/app/src/main/res/drawable/ic_bsc_round.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_cardano_round.xml b/app/src/main/res/drawable/ic_cardano_round.xml deleted file mode 100644 index 6764bbb9e8..0000000000 --- a/app/src/main/res/drawable/ic_cardano_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml deleted file mode 100644 index d276f8584c..0000000000 --- a/app/src/main/res/drawable/ic_close.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_dogecoin_round.xml b/app/src/main/res/drawable/ic_dogecoin_round.xml deleted file mode 100644 index 8a89a70dcf..0000000000 --- a/app/src/main/res/drawable/ic_dogecoin_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_eth_round.xml b/app/src/main/res/drawable/ic_eth_round.xml deleted file mode 100644 index b62152ede8..0000000000 --- a/app/src/main/res/drawable/ic_eth_round.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_ethc_round.xml b/app/src/main/res/drawable/ic_ethc_round.xml deleted file mode 100644 index 31f3dbacd8..0000000000 --- a/app/src/main/res/drawable/ic_ethc_round.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_fantom_round.xml b/app/src/main/res/drawable/ic_fantom_round.xml deleted file mode 100644 index e970acee86..0000000000 --- a/app/src/main/res/drawable/ic_fantom_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_gnosis_round.xml b/app/src/main/res/drawable/ic_gnosis_round.xml deleted file mode 100644 index 8734b7177b..0000000000 --- a/app/src/main/res/drawable/ic_gnosis_round.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_kusama_round.xml b/app/src/main/res/drawable/ic_kusama_round.xml deleted file mode 100644 index f620ae3130..0000000000 --- a/app/src/main/res/drawable/ic_kusama_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_litecoin_round.xml b/app/src/main/res/drawable/ic_litecoin_round.xml deleted file mode 100644 index 018f6f35bc..0000000000 --- a/app/src/main/res/drawable/ic_litecoin_round.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml deleted file mode 100644 index 2ea52e3dbc..0000000000 --- a/app/src/main/res/drawable/ic_more.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_optimism.xml b/app/src/main/res/drawable/ic_optimism.xml deleted file mode 100644 index fb8ce845a1..0000000000 --- a/app/src/main/res/drawable/ic_optimism.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_polkadot_round.xml b/app/src/main/res/drawable/ic_polkadot_round.xml deleted file mode 100644 index 346f130d82..0000000000 --- a/app/src/main/res/drawable/ic_polkadot_round.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_polygon_round.xml b/app/src/main/res/drawable/ic_polygon_round.xml deleted file mode 100644 index d8e57f8f97..0000000000 --- a/app/src/main/res/drawable/ic_polygon_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_rsk_round.xml b/app/src/main/res/drawable/ic_rsk_round.xml deleted file mode 100644 index f4a6e92bde..0000000000 --- a/app/src/main/res/drawable/ic_rsk_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_solana_round.xml b/app/src/main/res/drawable/ic_solana_round.xml deleted file mode 100644 index 49659278ae..0000000000 --- a/app/src/main/res/drawable/ic_solana_round.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_stellar_round.xml b/app/src/main/res/drawable/ic_stellar_round.xml deleted file mode 100644 index f7ae98231f..0000000000 --- a/app/src/main/res/drawable/ic_stellar_round.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_tezos_round.xml b/app/src/main/res/drawable/ic_tezos_round.xml deleted file mode 100644 index 4976e634e3..0000000000 --- a/app/src/main/res/drawable/ic_tezos_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_tron_round.xml b/app/src/main/res/drawable/ic_tron_round.xml deleted file mode 100644 index 9eaf6cfc9b..0000000000 --- a/app/src/main/res/drawable/ic_tron_round.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_xrp_round.xml b/app/src/main/res/drawable/ic_xrp_round.xml deleted file mode 100644 index 6e29010df6..0000000000 --- a/app/src/main/res/drawable/ic_xrp_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_reset_background.xml b/app/src/main/res/drawable/ill_reset_background.xml similarity index 100% rename from app/src/main/res/drawable/ic_reset_background.xml rename to app/src/main/res/drawable/ill_reset_background.xml diff --git a/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml index d3cd156604..b098f5f0ae 100644 --- a/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml +++ b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml @@ -74,7 +74,7 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml b/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml index 862e6809ee..34b57671aa 100644 --- a/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml +++ b/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml @@ -48,7 +48,7 @@ @@ -84,4 +84,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/dialog_russians_cardholders_warning.xml b/app/src/main/res/layout/dialog_russians_cardholders_warning.xml index 8f2196c2d5..29bf1c052b 100644 --- a/app/src/main/res/layout/dialog_russians_cardholders_warning.xml +++ b/app/src/main/res/layout/dialog_russians_cardholders_warning.xml @@ -81,7 +81,7 @@ + app:drawableStartCompat="@drawable/ic_arrow_up_24" /> + app:drawableStartCompat="@drawable/ic_arrow_down_24" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details_confirm.xml b/app/src/main/res/layout/fragment_details_confirm.xml index 8a56bf635b..c4daafda8d 100644 --- a/app/src/main/res/layout/fragment_details_confirm.xml +++ b/app/src/main/res/layout/fragment_details_confirm.xml @@ -78,7 +78,7 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_disclaimer.xml b/app/src/main/res/layout/fragment_disclaimer.xml index 5840a19ed9..e243e2e94a 100644 --- a/app/src/main/res/layout/fragment_disclaimer.xml +++ b/app/src/main/res/layout/fragment_disclaimer.xml @@ -91,7 +91,7 @@