Updated on 2026-08-14
This commit is contained in:
commit
369ed25ac6
515 changed files with 6100 additions and 12756 deletions
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.models.scan.ProductType
|
|||
import com.tangem.screens.DisclaimerTestScreen
|
||||
import com.tangem.screens.MainTestScreen
|
||||
import com.tangem.screens.StoriesTestScreen
|
||||
import com.tangem.screens.TestTopBar
|
||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
|
||||
|
|
@ -34,5 +35,10 @@ class OpenMainScreenScenario(
|
|||
assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<TestTopBar>(testRule) {
|
||||
step("Close Markets tooltip"){
|
||||
performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"isTopUpEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": true
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"isTopUpEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": true
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
[
|
||||
{
|
||||
"fw":"1.24r",
|
||||
"challenge":"00000000000000000000000000000001",
|
||||
"sha-256":[
|
||||
{"block":0,"count":1,"digest":"9B091DF443FE951D27AA8843517096DC421433F5603EBC566B11891F867E3D18"},
|
||||
{"block":1,"count":1,"digest":"B4F723EE79075C8C00FA17D037BF10528BB0C7CF421539EE15F4A397204C87AF"},
|
||||
{"block":2,"count":1,"digest":"1258585B438B6BC8F41FC22AFF383C86690E984E84E96AA752C9A54954DF731A"},
|
||||
{"block":3,"count":1,"digest":"14082C75D6AA533A06E9B764642F97B5783FED91BD1173EFADC3CF57D8769AAE"},
|
||||
{"block":4,"count":1,"digest":"A8F6CB02D56933361127072BB0D89999020CE196AE9718E9CF019DCCC9437E85"},
|
||||
{"block":5,"count":1,"digest":"491604BA2A2F6EE03A2B4812958F0356D745678FB0ABABF0109A9E8F59099D9D"},
|
||||
{"block":6,"count":1,"digest":"B2F057FF8B83343BC40058CA459F3C63B9670ABCD059730BE70223AEBDC88D77"},
|
||||
{"block":7,"count":1,"digest":"73F4391010345E27745A1AFEC93C02B2CA460D9EAB437605F82A18281DF17DCE"},
|
||||
{"block":8,"count":1,"digest":"74588EBB6704DD3DB9ECDFE6B85EC2872BD6A1F7C73E080E57697D3FF8DDD470"},
|
||||
{"block":9,"count":1,"digest":"3B382C35ADBBF206B0A6B216EE95AF2321EE34148E93BBFECCF275AB13ED08A2"},
|
||||
{"block":11500,"count":1,"digest":"21DDEC9788E3EA04663297AD0208F2714F139434E3540EBB7C96150EE1D320C9"},
|
||||
{"block":11501,"count":1,"digest":"C545C5D092D1EBC4BA266C139CE0F2A399E4D23E74EB4EBD0E53019AB58CB486"},
|
||||
{"block":0,"count":0,"digest":"C689C50352E9F6E41BF2E6EC852D8466814A51018F40BE32BAADEE9C2A9E6B6D"}
|
||||
],
|
||||
"crc-16":[
|
||||
{"block":0,"count":1,"digest":"1BFC"},
|
||||
{"block":1,"count":1,"digest":"D58D"},
|
||||
{"block":2,"count":1,"digest":"7E3E"},
|
||||
{"block":3,"count":1,"digest":"14EA"},
|
||||
{"block":4,"count":1,"digest":"014F"},
|
||||
{"block":5,"count":1,"digest":"5FF0"},
|
||||
{"block":6,"count":1,"digest":"7D3A"},
|
||||
{"block":7,"count":1,"digest":"702C"},
|
||||
{"block":8,"count":1,"digest":"CC22"},
|
||||
{"block":9,"count":1,"digest":"392C"},
|
||||
{"block":11500,"count":1,"digest":"5FCB"},
|
||||
{"block":11501,"count":1,"digest":"FF77"},
|
||||
{"block":0,"count":0,"digest":"D07B"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fw":"1.24d SDK",
|
||||
"challenge":"00000000000000000000000000000001",
|
||||
"sha-256":[
|
||||
{"block":0,"count":1,"digest":"6B360B47FFB16641EF9CC778746FE6B1010AB60C69281CE431A46ECC29A72304"},
|
||||
{"block":1,"count":1,"digest":"648F50D44E9A31CB4C2682EAD7B8E2E570E5F252035849CD9B08BD4F9106B432"},
|
||||
{"block":2,"count":1,"digest":"BAA31588F599E2DBC8AC06CFBF4E710B9B0E3FEA0201AE67E5A9E605DB50D1ED"},
|
||||
{"block":3,"count":1,"digest":"48CFA13758F0B8018A97831C11BB1668A39CC76B141C90038551D950F83A43BA"},
|
||||
{"block":4,"count":1,"digest":"8932B8E9D93A28EEEAC8FD3477F38A8A384AC895B06E09B8396A7806C3DCFC09"},
|
||||
{"block":5,"count":1,"digest":"A3E7F2911AB380B366E47EB9E7DCC0C3D8667EEE3CF355FCCD5B113918873EFC"},
|
||||
{"block":6,"count":1,"digest":"32CFCDC12C3AAA6C8FE4935C76B795A81206141AE7C7AB941A4B5F74EB02D844"},
|
||||
{"block":7,"count":1,"digest":"1BA7B6ED5F28A0744E177843F2A960CB757B27FF6052C17F9E57B266AEC4F564"},
|
||||
{"block":8,"count":1,"digest":"7C1A2C88529DA038C3A124CC049B9A5CD73089C2CD9C66B15DF91020E981B94B"},
|
||||
{"block":9,"count":1,"digest":"5F14E10CBE14230EEF9B8896C84998197E56738952A1ECF22202801B9AE64340"},
|
||||
{"block":10,"count":1,"digest":"8FF4F496E7C68A654E574F13517EFA4F2058FDFC9E949421DA5ACC855C876F63"},
|
||||
{"block":11491,"count":1,"digest":"E45CFEA35B1C4C217A55B78B0D78C841978F1008C3C1917D228EECB0324A0942"},
|
||||
{"block":0,"count":0,"digest":"F4199A74F2D4BAF2E282A670989A50B9186665D8EC453D1CED02F4E4019E1C0F"}
|
||||
],
|
||||
"crc-16":[
|
||||
{"block":0,"count":1,"digest":"22C3"},
|
||||
{"block":1,"count":1,"digest":"ECB2"},
|
||||
{"block":2,"count":1,"digest":"4701"},
|
||||
{"block":3,"count":1,"digest":"2DD5"},
|
||||
{"block":4,"count":1,"digest":"3870"},
|
||||
{"block":5,"count":1,"digest":"66CF"},
|
||||
{"block":6,"count":1,"digest":"4405"},
|
||||
{"block":7,"count":1,"digest":"4913"},
|
||||
{"block":8,"count":1,"digest":"F51D"},
|
||||
{"block":9,"count":1,"digest":"0013"},
|
||||
{"block":10,"count":1,"digest":"5E68"},
|
||||
{"block":11491,"count":1,"digest":"4078"},
|
||||
{"block":0,"count":0,"digest":"75C3"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fw":"1.28r",
|
||||
"challenge":"00000000000000000000000000000001",
|
||||
"sha-256":[
|
||||
{"block":0,"count":1,"digest":"A52D552A4CCA2979122300C7C8686031FBB216C187971C739305D493284DD262"},
|
||||
{"block":1,"count":1,"digest":"AF8D5DDC8FD0BA9B58707BC66940A50298AAB6FFE74D243874BAA25CCD53C7C3"},
|
||||
{"block":2,"count":1,"digest":"03612CB50531C2A2B7825B6A72C14528522A926A781391E8FE17027943543387"},
|
||||
{"block":3,"count":1,"digest":"88A984179C97654B7833824DF43A13133BC6E627C17C9A65BEEDFB671CD78D2A"},
|
||||
{"block":4,"count":1,"digest":"A3DB1DD3F876ED972287FEC903C2112F6B99A09BCF686E2B65A6E19EA9CD9ECB"},
|
||||
{"block":5,"count":1,"digest":"CA7685FCD6BE9D8310BBD58AA7DED1390925BADA47B6611DB010625CA6BA7542"},
|
||||
{"block":6,"count":1,"digest":"192E10BF1D4AA426676918AAC7312FF4914A4781A2E6372986356AC26917078B"},
|
||||
{"block":7,"count":1,"digest":"00B657206A8120DE159F3C16B4F036B7D2D292603067FCB6B33E3753421D9D15"},
|
||||
{"block":8,"count":1,"digest":"2F875EE18AD37A3A4666810E7E0942F7370CAD365C510B7952280EC43BE12ADE"},
|
||||
{"block":9,"count":1,"digest":"1CFAF013B43CAF9F6627DAC871335B09279375A9DE8644AEB2FBBA105C73C05A"},
|
||||
{"block":10,"count":1,"digest":"AD33E62400EFE078EB9256C3A6F9B9299A4B56F4A7E63E78C9BC34F7DCA2FE10"},
|
||||
{"block":11497,"count":1,"digest":"FBB56C858CF485012A08343B73C46CDA7423500D52FBE5E4F28326F2515C7150"},
|
||||
{"block":0,"count":0,"digest":"7582258B2AF9FDCD83BCDEA05FA0310216F7CFF4FB27C699461397EF4D189567"}
|
||||
],
|
||||
"crc-16":[
|
||||
{"block":0,"count":1,"digest":"B42D"},
|
||||
{"block":1,"count":1,"digest":"7A5C"},
|
||||
{"block":2,"count":1,"digest":"D1EF"},
|
||||
{"block":3,"count":1,"digest":"BB3B"},
|
||||
{"block":4,"count":1,"digest":"AE9E"},
|
||||
{"block":5,"count":1,"digest":"F021"},
|
||||
{"block":6,"count":1,"digest":"D2EB"},
|
||||
{"block":7,"count":1,"digest":"DFFD"},
|
||||
{"block":8,"count":1,"digest":"63F3"},
|
||||
{"block":9,"count":1,"digest":"96FD"},
|
||||
{"block":10,"count":1,"digest":"C886"},
|
||||
{"block":11497,"count":1,"digest":"E4B0"},
|
||||
{"block":0,"count":0,"digest":"0143"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fw":"1.28d SDK",
|
||||
"challenge":"00000000000000000000000000000001",
|
||||
"sha-256":[
|
||||
{"block":0,"count":1,"digest":"2F9DB9B70A20E28A97A6A58919E71ABF757BA73E693D9DC9CEEE1B0BCA8DAB8E"},
|
||||
{"block":1,"count":1,"digest":"6B6F75ECF7E9CB76E570C290E7BEEAEF44E4B24C46BA4AA115D5BBAE3209869F"},
|
||||
{"block":2,"count":1,"digest":"4D584DDC73B940C7E508456BF0C52BC6BE018CD448A5886B54BEBF1D26D31AD5"},
|
||||
{"block":3,"count":1,"digest":"A9D5929A34793F5BCC623234AE34DEE246A95ED34D75D51508B9DC2694F28CD6"},
|
||||
{"block":4,"count":1,"digest":"094D5F3E03262B6D07E58F795E17FFF2D83D39ABCF86B3B04A856A37BB076AD0"},
|
||||
{"block":5,"count":1,"digest":"40F5586223406B2BD790629AB5D8D1AB97B6EA9151B69BF4D55C4A1EF5E2A1BB"},
|
||||
{"block":6,"count":1,"digest":"B56C71A3E6DFE98B1666825825844372EEE5811EEA743A40760FA7AE2D66CADD"},
|
||||
{"block":7,"count":1,"digest":"4331EB4B7415883536DDBA03D75F0408BE1F23F3DEE4459227755C12CBF98A8D"},
|
||||
{"block":8,"count":1,"digest":"7383EDD57D266591760CD74E50073D28973CB2DAFD1410E45030B33588768DEE"},
|
||||
{"block":9,"count":1,"digest":"893F5CF5E368E82CABE7F48D5DAA54AFAFA740B28496F303E845E680B7C1436C"},
|
||||
{"block":10,"count":1,"digest":"8DEEB29CD6C764FB7AB53A056BC3C860DBD8E121AB7C1E6E4DF3C273A503C405"},
|
||||
{"block":11497,"count":1,"digest":"6F6166E13672B12BD947CDFEC3158064A14C5A259AD0B3550738E30E90BC20C5"},
|
||||
{"block":0,"count":0,"digest":"E475661F4D0948ACBDF104EAD718260A73FB99448691F0EB68AED85D189B6F3C"}
|
||||
],
|
||||
"crc-16":[
|
||||
{"block":0,"count":1,"digest":"3506"},
|
||||
{"block":1,"count":1,"digest":"FB77"},
|
||||
{"block":2,"count":1,"digest":"50C4"},
|
||||
{"block":3,"count":1,"digest":"3A10"},
|
||||
{"block":4,"count":1,"digest":"2FB5"},
|
||||
{"block":5,"count":1,"digest":"710A"},
|
||||
{"block":6,"count":1,"digest":"53C0"},
|
||||
{"block":7,"count":1,"digest":"5ED6"},
|
||||
{"block":8,"count":1,"digest":"E2D8"},
|
||||
{"block":9,"count":1,"digest":"17D6"},
|
||||
{"block":10,"count":1,"digest":"49AD"},
|
||||
{"block":11497,"count":1,"digest":"659B"},
|
||||
{"block":0,"count":0,"digest":"1893"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -718,6 +718,36 @@
|
|||
"networkId": "sei-network/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "energy-web-chain",
|
||||
"name": "Energy Web Chain",
|
||||
"symbol": "EWT",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "energy-web-chain/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "energy-web-x",
|
||||
"name": "Energy Web X",
|
||||
"symbol": "EWT",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "energy-web-x/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "core",
|
||||
"name": "Core",
|
||||
"symbol": "CORE",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "core/test"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -9,8 +9,8 @@ import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
|||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
|
|
@ -18,24 +18,23 @@ import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
|||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -47,7 +46,7 @@ import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
|||
@Suppress("TooManyFunctions")
|
||||
interface ApplicationEntryPoint {
|
||||
|
||||
fun getConfigManager(): ConfigManager
|
||||
fun getEnvironmentConfigStorage(): EnvironmentConfigStorage
|
||||
|
||||
fun getAppStateHolder(): AppStateHolder
|
||||
|
||||
|
|
@ -57,7 +56,7 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getNetworkConnectionManager(): NetworkConnectionManager
|
||||
|
||||
fun getCustomTokenFeatureToggles(): CustomTokenFeatureToggles
|
||||
fun getCardScanningFeatureToggles(): CardScanningFeatureToggles
|
||||
|
||||
fun getWalletConnect2Repository(): WalletConnect2Repository
|
||||
|
||||
|
|
@ -95,20 +94,16 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getCardRepository(): CardRepository
|
||||
|
||||
fun getFeedbackManagerFeatureToggles(): FeedbackManagerFeatureToggles
|
||||
|
||||
fun getTangemSdkLogger(): TangemSdkLogger
|
||||
|
||||
fun getSettingsRepository(): SettingsRepository
|
||||
|
||||
fun getBlockchainSDKFactory(): BlockchainSDKFactory
|
||||
|
||||
fun getGetFeedbackEmailUseCase(): GetFeedbackEmailUseCase
|
||||
fun getSendFeedbackEmailUseCase(): SendFeedbackEmailUseCase
|
||||
|
||||
fun getSaveBlockchainErrorUseCase(): SaveBlockchainErrorUseCase
|
||||
|
||||
fun getDetailsFeatureToggles(): DetailsFeatureToggles
|
||||
|
||||
fun getGetCardInfoUseCase(): GetCardInfoUseCase
|
||||
|
||||
fun getUrlOpener(): UrlOpener
|
||||
|
|
@ -117,9 +112,11 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getAppRouter(): AppRouter
|
||||
|
||||
fun getPushNotificationsFeatureToggles(): PushNotificationsFeatureToggles
|
||||
|
||||
fun getTangemAppLogger(): TangemAppLoggerInitializer
|
||||
|
||||
fun getTransactionSignerFactory(): TransactionSignerFactory
|
||||
|
||||
fun getHomeFeatureToggles(): HomeFeatureToggles
|
||||
|
||||
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
|
||||
}
|
||||
|
|
@ -67,7 +67,6 @@ import com.tangem.tap.common.OnActivityResultCallback
|
|||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss
|
||||
import com.tangem.tap.common.redux.NotificationsHandler
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
|
|
@ -80,7 +79,6 @@ import com.tangem.tap.features.main.model.Toast
|
|||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.routing.RoutingComponent
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||
|
|
@ -550,11 +548,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
} else {
|
||||
lifecycleScope.launch {
|
||||
val toggles = store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles)
|
||||
val isPushPermissionEnabled = toggles.isPushNotificationsEnabled
|
||||
val shouldShowTos = !cardRepository.isTangemTOSAccepted() && isPushPermissionEnabled
|
||||
val wasPushInitiallyAsked = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false }
|
||||
val shouldShowInitialPush = wasPushInitiallyAsked && isPushPermissionEnabled
|
||||
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
|
||||
val shouldShowInitialPush = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false }
|
||||
|
||||
val route = when {
|
||||
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.tangem.Log
|
||||
import com.tangem.LogFormat
|
||||
import com.tangem.TangemSdkLogger
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
|
|
@ -24,10 +21,9 @@ import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
|||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
|
|
@ -36,36 +32,32 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
|||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
|
||||
import com.tangem.tap.common.feedback.LegacyFeedbackManager
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||
import com.tangem.tap.common.log.TangemLogCollector
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.appReducer
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.tasks.product.DerivationsFinder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
|
|
@ -90,8 +82,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val appStateHolder: AppStateHolder
|
||||
get() = entryPoint.getAppStateHolder()
|
||||
|
||||
private val configManager: ConfigManager
|
||||
get() = entryPoint.getConfigManager()
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage
|
||||
get() = entryPoint.getEnvironmentConfigStorage()
|
||||
|
||||
private val assetLoader: AssetLoader
|
||||
get() = entryPoint.getAssetLoader()
|
||||
|
|
@ -102,8 +94,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val networkConnectionManager: NetworkConnectionManager
|
||||
get() = entryPoint.getNetworkConnectionManager()
|
||||
|
||||
private val customTokenFeatureToggles: CustomTokenFeatureToggles
|
||||
get() = entryPoint.getCustomTokenFeatureToggles()
|
||||
private val cardScanningFeatureToggles: CardScanningFeatureToggles
|
||||
get() = entryPoint.getCardScanningFeatureToggles()
|
||||
|
||||
private val walletConnect2Repository: WalletConnect2Repository
|
||||
get() = entryPoint.getWalletConnect2Repository()
|
||||
|
|
@ -159,9 +151,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val cardRepository: CardRepository
|
||||
get() = entryPoint.getCardRepository()
|
||||
|
||||
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles
|
||||
get() = entryPoint.getFeedbackManagerFeatureToggles()
|
||||
|
||||
private val tangemSdkLogger: TangemSdkLogger
|
||||
get() = entryPoint.getTangemSdkLogger()
|
||||
|
||||
|
|
@ -171,8 +160,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val blockchainSDKFactory: BlockchainSDKFactory
|
||||
get() = entryPoint.getBlockchainSDKFactory()
|
||||
|
||||
private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase
|
||||
get() = entryPoint.getGetFeedbackEmailUseCase()
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase
|
||||
get() = entryPoint.getSendFeedbackEmailUseCase()
|
||||
|
||||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase
|
||||
get() = entryPoint.getSaveBlockchainErrorUseCase()
|
||||
|
|
@ -180,9 +169,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val getCardInfoUseCase: GetCardInfoUseCase
|
||||
get() = entryPoint.getGetCardInfoUseCase()
|
||||
|
||||
private val detailsFeatureToggles: DetailsFeatureToggles
|
||||
get() = entryPoint.getDetailsFeatureToggles()
|
||||
|
||||
private val urlOpener
|
||||
get() = entryPoint.getUrlOpener()
|
||||
|
||||
|
|
@ -192,14 +178,17 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val appRouter: AppRouter
|
||||
get() = entryPoint.getAppRouter()
|
||||
|
||||
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles
|
||||
get() = entryPoint.getPushNotificationsFeatureToggles()
|
||||
|
||||
private val tangemAppLoggerInitializer: TangemAppLoggerInitializer
|
||||
get() = entryPoint.getTangemAppLogger()
|
||||
|
||||
private val transactionSignerFactory: TransactionSignerFactory
|
||||
get() = entryPoint.getTransactionSignerFactory()
|
||||
|
||||
private val homeFeatureToggles: HomeFeatureToggles
|
||||
get() = entryPoint.getHomeFeatureToggles()
|
||||
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase
|
||||
get() = entryPoint.getGetUserCountryCodeUseCase()
|
||||
// endregion
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -221,10 +210,11 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
// [REDACTED_JIRA]
|
||||
runBlocking {
|
||||
featureTogglesManager.init()
|
||||
initConfigManager(
|
||||
loader = FeaturesLocalLoader(assetLoader, BuildConfig.ENVIRONMENT),
|
||||
onComplete = ::initWithConfigDependency,
|
||||
)
|
||||
|
||||
val config = environmentConfigStorage.initialize()
|
||||
store.dispatch(GlobalAction.SetConfigManager(environmentConfigStorage))
|
||||
|
||||
initWithConfigDependency(environmentConfig = config)
|
||||
}
|
||||
|
||||
loadNativeLibraries()
|
||||
|
|
@ -242,7 +232,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
)
|
||||
appStateHolder.mainStore = store
|
||||
|
||||
walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId)
|
||||
walletConnect2Repository.init(projectId = environmentConfigStorage.getConfigSync().walletConnectProjectId)
|
||||
}
|
||||
|
||||
private fun createReduxStore(): Store<AppState> {
|
||||
|
|
@ -252,7 +242,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
state = AppState(
|
||||
daggerGraphState = DaggerGraphState(
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
customTokenFeatureToggles = customTokenFeatureToggles,
|
||||
cardScanningFeatureToggles = cardScanningFeatureToggles,
|
||||
walletConnectRepository = walletConnect2Repository,
|
||||
walletConnectSessionsRepository = walletConnectSessionsRepository,
|
||||
scanCardProcessor = scanCardProcessor,
|
||||
|
|
@ -269,20 +259,19 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
|
||||
generateWalletNameUseCase = generateWalletNameUseCase,
|
||||
cardRepository = cardRepository,
|
||||
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
|
||||
tangemSdkLogger = tangemSdkLogger,
|
||||
settingsRepository = settingsRepository,
|
||||
blockchainSDKFactory = blockchainSDKFactory,
|
||||
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
|
||||
getFeedbackEmailUseCase = getFeedbackEmailUseCase,
|
||||
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
|
||||
getCardInfoUseCase = getCardInfoUseCase,
|
||||
assetLoader = assetLoader,
|
||||
detailsFeatureToggles = detailsFeatureToggles,
|
||||
urlOpener = urlOpener,
|
||||
shareManager = shareManager,
|
||||
appRouter = appRouter,
|
||||
pushNotificationsFeatureToggles = pushNotificationsFeatureToggles,
|
||||
transactionSignerFactory = transactionSignerFactory,
|
||||
homeFeatureToggles = homeFeatureToggles,
|
||||
getUserCountryUseCase = getUserCountryUseCase,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -299,19 +288,12 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
System.loadLibrary("TrustWalletCore")
|
||||
}
|
||||
|
||||
private suspend fun initConfigManager(loader: FeaturesLocalLoader, onComplete: (Config) -> Unit) {
|
||||
configManager.load(loader) { config ->
|
||||
store.dispatch(GlobalAction.SetConfigManager(configManager))
|
||||
onComplete(config)
|
||||
}
|
||||
private fun initWithConfigDependency(environmentConfig: EnvironmentConfig) {
|
||||
initAnalytics(this, environmentConfig)
|
||||
Log.addLogger(logger = tangemSdkLogger)
|
||||
}
|
||||
|
||||
private fun initWithConfigDependency(config: Config) {
|
||||
initAnalytics(this, config)
|
||||
initFeedbackManager(this, store)
|
||||
}
|
||||
|
||||
private fun initAnalytics(application: Application, config: Config) {
|
||||
private fun initAnalytics(application: Application, environmentConfig: EnvironmentConfig) {
|
||||
val factory = AnalyticsFactory()
|
||||
factory.addHandlerBuilder(AmplitudeAnalyticsHandler.Builder())
|
||||
factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder())
|
||||
|
|
@ -320,7 +302,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
val buildData = AnalyticsHandlerBuilder.Data(
|
||||
application = application,
|
||||
config = config,
|
||||
config = environmentConfig,
|
||||
isDebug = BuildConfig.DEBUG,
|
||||
logConfig = LogConfig.analyticsHandlers,
|
||||
jsonConverter = MoshiConverter.sdkMoshiConverter,
|
||||
|
|
@ -347,51 +329,4 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
factory.build(Analytics, buildData)
|
||||
// ExceptionHandler.append(blockchainExceptionHandler) TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
private fun initFeedbackManager(context: Context, store: Store<AppState>) {
|
||||
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo {
|
||||
return AdditionalFeedbackInfo().apply {
|
||||
appVersion = try {
|
||||
// TODO don't use deprecated method
|
||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
pInfo.versionName
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
"x.y.z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initTangemLogCollector(): TangemLogCollector {
|
||||
val logLevels = listOf(
|
||||
Log.Level.ApduCommand,
|
||||
Log.Level.Apdu,
|
||||
Log.Level.Tlv,
|
||||
Log.Level.Nfc,
|
||||
Log.Level.Command,
|
||||
Log.Level.Session,
|
||||
Log.Level.View,
|
||||
Log.Level.Network,
|
||||
Log.Level.Error,
|
||||
Log.Level.Biometric,
|
||||
Log.Level.Info,
|
||||
)
|
||||
return TangemLogCollector(logLevels, LogFormat.StairsFormatter())
|
||||
}
|
||||
|
||||
val additionalFeedbackInfo = initAdditionalFeedbackInfo(context)
|
||||
val tangemLogCollector = initTangemLogCollector()
|
||||
|
||||
Log.addLogger(
|
||||
logger = if (feedbackManagerFeatureToggles.isLocalLogsEnabled) tangemSdkLogger else tangemLogCollector,
|
||||
)
|
||||
|
||||
val feedbackManager = LegacyFeedbackManager(
|
||||
infoHolder = additionalFeedbackInfo,
|
||||
logCollector = tangemLogCollector,
|
||||
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
|
||||
getFeedbackEmailUseCase = getFeedbackEmailUseCase,
|
||||
)
|
||||
store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager))
|
||||
}
|
||||
}
|
||||
|
|
@ -3,15 +3,15 @@ package com.tangem.tap.common.analytics.api
|
|||
import android.app.Application
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.core.analytics.api.AnalyticsHandler
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.domain.common.AnalyticsHandlersLogConfig
|
||||
import com.tangem.datasource.config.models.Config
|
||||
|
||||
interface AnalyticsHandlerBuilder {
|
||||
fun build(data: Data): AnalyticsHandler?
|
||||
|
||||
data class Data(
|
||||
val application: Application,
|
||||
val config: Config,
|
||||
val config: EnvironmentConfig,
|
||||
val isDebug: Boolean,
|
||||
val logConfig: AnalyticsHandlersLogConfig,
|
||||
val jsonConverter: MoshiJsonConverter,
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class ManageTokens(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Manage Tokens", event, params) {
|
||||
|
||||
class ScreenOpened : ManageTokens("Manage Tokens Screen Opened")
|
||||
class TokenSearched : ManageTokens("Token Searched")
|
||||
|
||||
class TokenSwitcherChanged(
|
||||
type: AnalyticsParam.CurrencyType,
|
||||
state: AnalyticsParam.OnOffState,
|
||||
) : ManageTokens(
|
||||
"Token Switcher Changed",
|
||||
params = mapOf(
|
||||
"Token" to type.value,
|
||||
"State" to state.value,
|
||||
),
|
||||
)
|
||||
|
||||
class ButtonSaveChanges : ManageTokens("Button - Save Changes")
|
||||
class ButtonCustomToken : ManageTokens("Button - Custom Token")
|
||||
|
||||
sealed class CustomToken(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : ManageTokens(event, params) {
|
||||
|
||||
object ScreenOpened : ManageTokens(event = "Custom Token Screen Opened")
|
||||
|
||||
// TODO("Get rid of strong binding (CustomCurrency)
|
||||
open class TokenWasAdded(customCurrency: CustomCurrency) : ManageTokens(
|
||||
event = "Custom Token Was Added",
|
||||
params = convertToParam(customCurrency),
|
||||
) {
|
||||
|
||||
data class Token(
|
||||
val symbol: String,
|
||||
val derivationPath: String?,
|
||||
val blockchain: com.tangem.blockchain.common.Blockchain,
|
||||
val contractAddress: String,
|
||||
) : ManageTokens(
|
||||
event = "Custom Token Was Added",
|
||||
params = mapOf(
|
||||
"Token" to symbol,
|
||||
"Derivation Path" to derivationPath,
|
||||
"Network Id" to blockchain.currency,
|
||||
"Contract Address" to contractAddress,
|
||||
).filterNotNull(),
|
||||
)
|
||||
|
||||
data class Blockchain(
|
||||
val derivationPath: String?,
|
||||
val blockchain: com.tangem.blockchain.common.Blockchain,
|
||||
) : ManageTokens(
|
||||
event = "Custom Token Was Added",
|
||||
params = mapOf(
|
||||
"Token" to blockchain.currency,
|
||||
"Derivation Path" to derivationPath,
|
||||
).filterNotNull(),
|
||||
)
|
||||
|
||||
companion object {
|
||||
private fun convertToParam(customCurrency: CustomCurrency): Map<String, String> = with(customCurrency) {
|
||||
return when (this) {
|
||||
is CustomCurrency.CustomBlockchain -> mapOf(
|
||||
"Token" to network.currency,
|
||||
"Derivation Path" to derivationPath?.rawPath,
|
||||
).filterNotNull()
|
||||
|
||||
is CustomCurrency.CustomToken -> mapOf(
|
||||
"Token" to token.symbol,
|
||||
"Derivation Path" to derivationPath?.rawPath,
|
||||
"Network Id" to network.toNetworkId(),
|
||||
"Contract Address" to token.contractAddress,
|
||||
).filterNotNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.features.details.ui.details.SocialNetwork
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -14,20 +13,8 @@ sealed class Settings(
|
|||
) : AnalyticsEvent(category, event, params, error) {
|
||||
|
||||
class ScreenOpened : Settings(event = "Settings Screen Opened")
|
||||
class ButtonChat : Settings(event = "Button - Chat")
|
||||
class ButtonSendFeedback : Settings(event = "Button - Send Feedback")
|
||||
class ButtonStartWalletConnectSession : Settings(event = "Button - Start Wallet Connect Session")
|
||||
class ButtonStopWalletConnectSession : Settings(event = "Button - Stop Wallet Connect Session")
|
||||
class ButtonCardSettings : Settings(event = "Button - Card Settings")
|
||||
class ButtonAppSettings : Settings(event = "Button - App Settings")
|
||||
class ButtonCreateBackup : Settings(event = "Button - Create Backup")
|
||||
class ButtonWalletConnect : Settings(event = "Button - Wallet Connect")
|
||||
object ScanNewCard : Settings(event = "Button - Scan New Card")
|
||||
|
||||
class ButtonSocialNetwork(network: SocialNetwork) : Settings(
|
||||
event = "Button - Social Network",
|
||||
params = mapOf("Network" to network.id),
|
||||
)
|
||||
|
||||
sealed class CardSettings(
|
||||
event: String,
|
||||
|
|
@ -63,8 +50,6 @@ sealed class Settings(
|
|||
error = error,
|
||||
)
|
||||
|
||||
class AccessCodeRecoveryButton : CardSettings("Button - Access Code Recovery")
|
||||
|
||||
class AccessCodeRecoveryChanged(status: AnalyticsParam.AccessCodeRecoveryStatus) : CardSettings(
|
||||
event = "Access Code Recovery Changed",
|
||||
params = mapOf(status.key to status.value),
|
||||
|
|
|
|||
|
|
@ -21,7 +21,3 @@ fun BigDecimal.toFormattedString(
|
|||
df.roundingMode = roundingMode
|
||||
return df.format(this)
|
||||
}
|
||||
|
||||
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
|
||||
|
||||
fun BigDecimal.isPositive(): Boolean = this.signum() == 1
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
package com.tangem.tap.common.feedback
|
||||
|
||||
import android.os.Build
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.Address
|
||||
import com.tangem.crypto.NetworkType
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class AdditionalFeedbackInfo {
|
||||
|
||||
class EmailWalletInfo(
|
||||
val blockchain: Blockchain = Blockchain.Unknown,
|
||||
val derivationPath: String = "",
|
||||
val outputsCount: String? = null,
|
||||
val host: String = "",
|
||||
val addresses: String = "",
|
||||
val explorerLink: String = "",
|
||||
val tokens: List<EmailTokenInfo> = emptyList(),
|
||||
)
|
||||
|
||||
class EmailTokenInfo(
|
||||
val id: String?,
|
||||
val name: String,
|
||||
val contractAddress: String,
|
||||
)
|
||||
|
||||
var appVersion: String = ""
|
||||
|
||||
// card
|
||||
var cardId: String = ""
|
||||
var cardFirmwareVersion: String = ""
|
||||
var cardIssuer: String = ""
|
||||
var cardBlockchain: String = ""
|
||||
var userWalletId: String = ""
|
||||
var extendedPublicKey: String = ""
|
||||
|
||||
// wallets
|
||||
val walletsInfo = CopyOnWriteArrayList<EmailWalletInfo>()
|
||||
var onSendErrorWalletInfo: EmailWalletInfo? = null
|
||||
private set
|
||||
var signedHashesCount: String = ""
|
||||
private set
|
||||
|
||||
// device
|
||||
var phoneModel: String = Build.MODEL
|
||||
private set
|
||||
var osVersion: String = Build.VERSION.SDK_INT.toString()
|
||||
private set
|
||||
|
||||
// send error
|
||||
var destinationAddress: String = ""
|
||||
private set
|
||||
var amount: String = ""
|
||||
private set
|
||||
var fee: String = ""
|
||||
private set
|
||||
var token: String = ""
|
||||
private set
|
||||
|
||||
private val Address.name: String
|
||||
get() = type.javaClass.simpleName
|
||||
|
||||
@Deprecated("Don't use it directly")
|
||||
fun setCardInfo(data: ScanResponse) {
|
||||
cardId = data.card.cardId
|
||||
cardBlockchain = data.walletData?.blockchain ?: ""
|
||||
cardFirmwareVersion = data.card.firmwareVersion.stringValue
|
||||
cardIssuer = data.card.issuer.name
|
||||
signedHashesCount = formatSignedHashes(data.card.wallets)
|
||||
userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: ""
|
||||
extendedPublicKey = runCatching {
|
||||
data.card.wallets.firstOrNull { it.extendedPublicKey != null }
|
||||
?.extendedPublicKey
|
||||
?.serialize(networkType = NetworkType.Mainnet).orEmpty()
|
||||
}.getOrDefault("couldn't retrieve extended public key")
|
||||
}
|
||||
|
||||
@Deprecated("Don't use it directly")
|
||||
fun setWalletsInfo(walletManagers: List<WalletManager>) {
|
||||
synchronized(walletsInfo) {
|
||||
walletsInfo.clear()
|
||||
walletsInfo.addAll(elements = walletManagers.map(::createEmailWalletInfo))
|
||||
}
|
||||
}
|
||||
|
||||
fun updateOnSendError(
|
||||
walletManager: WalletManager,
|
||||
amountToSend: Amount?,
|
||||
feeAmount: Amount?,
|
||||
destinationAddress: String?,
|
||||
) {
|
||||
onSendErrorWalletInfo = createEmailWalletInfo(walletManager)
|
||||
this.destinationAddress = destinationAddress ?: "0"
|
||||
amount = amountToSend?.value?.stripZeroPlainString() ?: "0"
|
||||
fee = if (feeAmount != null) {
|
||||
feeAmount.value?.stripZeroPlainString() ?: "0"
|
||||
} else {
|
||||
"Unable to receive"
|
||||
}
|
||||
token = if (amountToSend?.type is AmountType.Token) amountToSend.currencySymbol else ""
|
||||
}
|
||||
|
||||
private fun createEmailWalletInfo(walletManager: WalletManager): EmailWalletInfo {
|
||||
return EmailWalletInfo(
|
||||
blockchain = walletManager.wallet.blockchain,
|
||||
derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath ?: "",
|
||||
outputsCount = walletManager.outputsCount?.toString(),
|
||||
host = walletManager.currentHost,
|
||||
addresses = formatAddresses(walletManager.wallet),
|
||||
explorerLink = formatExploreUrls(walletManager.wallet),
|
||||
tokens = walletManager.cardTokens.map { token ->
|
||||
EmailTokenInfo(token.id, token.name, token.contractAddress)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatSignedHashes(wallets: List<CardDTO.Wallet>): String {
|
||||
return wallets.joinToString("\n") { "Signed hashes: ${it.curve.curve} - ${it.totalSignedHashes}" }
|
||||
}
|
||||
|
||||
private fun formatAddresses(wallet: Wallet): String {
|
||||
return wallet.formatAddressWith("Multiple address:") {
|
||||
"${it.name} - ${it.value}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatExploreUrls(wallet: Wallet): String {
|
||||
return wallet.formatAddressWith("Multiple explorers links:") {
|
||||
"${it.name} - ${wallet.getExploreUrl(it.value)}"
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String {
|
||||
return if (addresses.size == 1) {
|
||||
getExploreUrl(address)
|
||||
} else {
|
||||
addresses.map { mapAddress(it) }.toMutableList()
|
||||
.apply { add(0, with) }
|
||||
.joinToString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.tap.common.feedback
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
|
||||
internal class DefaultFeedbackManagerFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : FeedbackManagerFeatureToggles {
|
||||
|
||||
override val isLocalLogsEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "LOCAL_USER_LOGS_ENABLED")
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
package com.tangem.tap.common.feedback
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.domain.common.TapWorkarounds
|
||||
import com.tangem.tap.common.extensions.breakLine
|
||||
import com.tangem.wallet.R
|
||||
|
||||
interface FeedbackData {
|
||||
val subjectResId: Int
|
||||
val mainMessageResId: Int
|
||||
|
||||
fun getDataCollectionMessageResId(): Int = R.string.feedback_data_collection_message
|
||||
|
||||
fun prepare(infoHolder: AdditionalFeedbackInfo) {}
|
||||
|
||||
fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String {
|
||||
return StringBuilder().apply {
|
||||
append(context.getString(mainMessageResId))
|
||||
breakLine(3)
|
||||
append(context.getString(getDataCollectionMessageResId()))
|
||||
breakLine()
|
||||
append(createOptionalMessage(infoHolder))
|
||||
}.toString()
|
||||
}
|
||||
}
|
||||
|
||||
class RateCanBeBetterEmail : FeedbackData {
|
||||
override val subjectResId: Int = R.string.feedback_subject_rate_negative
|
||||
override val mainMessageResId: Int = R.string.feedback_preface_rate_negative
|
||||
|
||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
|
||||
.appendCardInfo()
|
||||
.breakLine()
|
||||
.appendPhoneInfo()
|
||||
.build()
|
||||
}
|
||||
|
||||
class ScanFailsEmail : FeedbackData {
|
||||
|
||||
override val subjectResId: Int = R.string.feedback_subject_scan_failed
|
||||
override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String = StringBuilder().apply {
|
||||
append(context.getString(mainMessageResId))
|
||||
breakLine(4)
|
||||
append(createOptionalMessage(infoHolder))
|
||||
}.toString()
|
||||
|
||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
|
||||
.appendPhoneInfo()
|
||||
.build()
|
||||
}
|
||||
|
||||
class SendTransactionFailedEmail(
|
||||
val error: String,
|
||||
) : FeedbackData {
|
||||
|
||||
override val subjectResId: Int = R.string.feedback_subject_tx_failed
|
||||
override val mainMessageResId: Int = R.string.feedback_preface_tx_failed
|
||||
|
||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
|
||||
.appendCardInfo()
|
||||
.appendDelimiter()
|
||||
.appendTxFailedBlockchainInfo(error)
|
||||
.breakLine()
|
||||
.appendPhoneInfo()
|
||||
.build()
|
||||
}
|
||||
|
||||
class FeedbackEmail : FeedbackData {
|
||||
override val subjectResId: Int
|
||||
get() = if (isS2CCard) R.string.feedback_subject_support else R.string.feedback_subject_support_tangem
|
||||
|
||||
override val mainMessageResId: Int = R.string.feedback_preface_support
|
||||
|
||||
private var isS2CCard = false
|
||||
|
||||
override fun prepare(infoHolder: AdditionalFeedbackInfo) {
|
||||
isS2CCard = TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)
|
||||
}
|
||||
|
||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
|
||||
.appendCardInfo()
|
||||
.appendWalletsInfo()
|
||||
.breakLine()
|
||||
.appendPhoneInfo()
|
||||
.build()
|
||||
}
|
||||
|
||||
class SupportInfo : FeedbackData {
|
||||
override val subjectResId: Int = R.string.details_chat
|
||||
override val mainMessageResId: Int = R.string.details_chat
|
||||
|
||||
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String {
|
||||
return FeedbackDataBuilder(infoHolder)
|
||||
.appendCardInfo()
|
||||
.appendDelimiter()
|
||||
.appendPhoneInfo()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
package com.tangem.tap.common.feedback
|
||||
|
||||
import com.tangem.tap.common.extensions.breakLine
|
||||
|
||||
class FeedbackDataBuilder(
|
||||
private val infoHolder: AdditionalFeedbackInfo,
|
||||
) {
|
||||
val builder = StringBuilder()
|
||||
|
||||
fun appendDelimiter(): FeedbackDataBuilder {
|
||||
builder.appendDelimiter()
|
||||
return this
|
||||
}
|
||||
|
||||
fun breakLine(count: Int = 1): FeedbackDataBuilder {
|
||||
builder.breakLine(count)
|
||||
return this
|
||||
}
|
||||
|
||||
fun appendCardInfo(): FeedbackDataBuilder {
|
||||
builder.appendKeyValue("Card ID", infoHolder.cardId)
|
||||
builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
|
||||
builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain)
|
||||
builder.appendKeyValue("", infoHolder.signedHashesCount)
|
||||
builder.appendKeyValue("User Wallet ID", infoHolder.userWalletId)
|
||||
return this
|
||||
}
|
||||
|
||||
fun appendWalletsInfo(): FeedbackDataBuilder {
|
||||
infoHolder.walletsInfo.forEach { walletInfo ->
|
||||
builder.appendDelimiter()
|
||||
builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
|
||||
builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
|
||||
builder.appendKeyValue("Outputs count", walletInfo.outputsCount)
|
||||
|
||||
if (walletInfo.tokens.isNotEmpty()) {
|
||||
builder.append("Tokens:")
|
||||
breakLine()
|
||||
walletInfo.tokens.forEach { token ->
|
||||
builder.appendKeyValue("ID", token.id ?: "[custom token]")
|
||||
builder.appendKeyValue("Name", token.name)
|
||||
builder.appendKeyValue("Contract address", token.contractAddress)
|
||||
}
|
||||
}
|
||||
|
||||
builder.appendKeyValue("Host", walletInfo.host)
|
||||
builder.appendKeyValue("Wallet address", walletInfo.addresses)
|
||||
builder.appendKeyValue("Explorer link", walletInfo.explorerLink)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun appendTxFailedBlockchainInfo(error: String): FeedbackDataBuilder {
|
||||
val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalFeedbackInfo.EmailWalletInfo()
|
||||
builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
|
||||
builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
|
||||
builder.appendKeyValue("Host", walletInfo.host)
|
||||
builder.appendKeyValue("Token", infoHolder.token)
|
||||
builder.appendKeyValue("Error", error)
|
||||
builder.appendDelimiter()
|
||||
builder.appendKeyValue("Source address", walletInfo.addresses)
|
||||
builder.appendKeyValue("Destination address", infoHolder.destinationAddress)
|
||||
builder.appendKeyValue("Amount", infoHolder.amount)
|
||||
builder.appendKeyValue("Fee", infoHolder.fee)
|
||||
return this
|
||||
}
|
||||
|
||||
fun appendPhoneInfo(): FeedbackDataBuilder {
|
||||
builder.appendKeyValue("Phone model", infoHolder.phoneModel)
|
||||
builder.appendKeyValue("OS version", infoHolder.osVersion)
|
||||
builder.appendKeyValue("App version", infoHolder.appVersion)
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): String = builder.toString()
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendKeyValue(key: String, value: String?): StringBuilder = when {
|
||||
value.isNullOrBlank() -> this
|
||||
key.isBlank() -> this.append("$value\n")
|
||||
else -> this.append("$key: $value\n")
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n")
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
package com.tangem.tap.common.feedback
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.domain.common.TapWorkarounds
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.sendEmail
|
||||
import com.tangem.tap.common.log.TangemLogCollector
|
||||
import com.tangem.tap.foregroundActivityObserver
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.withForegroundActivity
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class LegacyFeedbackManager(
|
||||
val infoHolder: AdditionalFeedbackInfo,
|
||||
private val logCollector: TangemLogCollector,
|
||||
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
|
||||
private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase,
|
||||
) {
|
||||
|
||||
private var sessionLogsFile: File? = null
|
||||
|
||||
fun sendEmail(feedbackData: FeedbackData, scanResponse: ScanResponse?) {
|
||||
if (feedbackManagerFeatureToggles.isLocalLogsEnabled) {
|
||||
scope.launch {
|
||||
val getCardInfo = suspend {
|
||||
scanResponse ?: error("ScanResponse must be not null")
|
||||
store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()
|
||||
?: error("CardInfo must be not null")
|
||||
}
|
||||
|
||||
val email = getFeedbackEmailUseCase(
|
||||
type = when (feedbackData) {
|
||||
is FeedbackEmail -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
|
||||
is RateCanBeBetterEmail -> FeedbackEmailType.RateCanBeBetter(cardInfo = getCardInfo())
|
||||
is ScanFailsEmail -> FeedbackEmailType.ScanningProblem
|
||||
is SendTransactionFailedEmail -> {
|
||||
FeedbackEmailType.TransactionSendingProblem(cardInfo = getCardInfo())
|
||||
}
|
||||
else -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
|
||||
},
|
||||
)
|
||||
|
||||
store.inject(DaggerGraphState::emailSender).send(
|
||||
email = EmailSender.Email(
|
||||
address = email.address,
|
||||
subject = email.subject,
|
||||
message = email.message,
|
||||
attachment = email.file,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
feedbackData.prepare(infoHolder)
|
||||
foregroundActivityObserver.withForegroundActivity { activity ->
|
||||
activity.sendEmail(
|
||||
email = getSupportEmail(),
|
||||
subject = activity.getString(feedbackData.subjectResId),
|
||||
message = feedbackData.joinTogether(activity, infoHolder),
|
||||
file = getLogFile(activity),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendEmail(type: FeedbackEmailType) {
|
||||
if (!feedbackManagerFeatureToggles.isLocalLogsEnabled) error("LOCAL_LOGS feature toggle must be enabled")
|
||||
|
||||
scope.launch {
|
||||
val email = getFeedbackEmailUseCase(type = type)
|
||||
|
||||
store.inject(DaggerGraphState::emailSender).send(
|
||||
email = EmailSender.Email(
|
||||
address = email.address,
|
||||
subject = email.subject,
|
||||
message = email.message,
|
||||
attachment = email.file,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLogFile(context: Context): File? {
|
||||
return try {
|
||||
if (sessionLogsFile != null) {
|
||||
return sessionLogsFile
|
||||
}
|
||||
val file = File(context.filesDir, LOGS_FILE)
|
||||
file.delete()
|
||||
file.createNewFile()
|
||||
|
||||
val stringWriter = StringWriter()
|
||||
logCollector.getLogs().forEach { stringWriter.append(it) }
|
||||
val fileWriter = FileWriter(file)
|
||||
fileWriter.write(stringWriter.toString())
|
||||
fileWriter.close()
|
||||
logCollector.clearLogs()
|
||||
if (file.exists()) {
|
||||
sessionLogsFile = file
|
||||
sessionLogsFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Can't create the logs file")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSupportEmail(): String {
|
||||
return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) {
|
||||
S2C_SUPPORT_EMAIL
|
||||
} else {
|
||||
DEFAULT_SUPPORT_EMAIL
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
|
||||
const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
|
||||
const val LOGS_FILE = "logs.txt"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.common.feedback
|
||||
|
||||
import com.tangem.domain.feedback.FeedbackManager
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
|
||||
internal class ProxyFeedbackManager : FeedbackManager {
|
||||
|
||||
override fun sendEmail(type: FeedbackEmailType) {
|
||||
val manager = store.state.globalState.feedbackManager
|
||||
|
||||
if (manager == null) {
|
||||
Timber.e("Feedback manager is not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
manager.sendEmail(type)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.common.log
|
||||
|
||||
import com.tangem.Log
|
||||
import com.tangem.LogFormat
|
||||
import com.tangem.TangemSdkLogger
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class TangemLogCollector(
|
||||
private val levels: List<Log.Level>,
|
||||
private val messageFormatter: LogFormat,
|
||||
) : TangemSdkLogger {
|
||||
|
||||
private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS")
|
||||
private val logs = mutableListOf<String>()
|
||||
private val mutex = Object()
|
||||
|
||||
override fun log(message: () -> String, level: Log.Level) {
|
||||
if (!levels.contains(level)) return
|
||||
|
||||
synchronized(mutex) {
|
||||
val formattedMessage = messageFormatter.format(message, level)
|
||||
val logMessage = "${dateFormatter.format(Date())}: $formattedMessage"
|
||||
logs.add("$logMessage\n")
|
||||
}
|
||||
}
|
||||
|
||||
fun getLogs(): List<String> = synchronized(mutex) { logs.toList() }
|
||||
|
||||
fun clearLogs() = synchronized(mutex) { logs.clear() }
|
||||
}
|
||||
|
|
@ -3,14 +3,12 @@ package com.tangem.tap.common.redux
|
|||
import com.tangem.tap.common.redux.global.globalReducer
|
||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
|
||||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
|
|
@ -28,8 +26,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
|||
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
|
||||
twinCardsState = TwinCardsReducer.reduce(action, state),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
disclaimerState = DisclaimerReducer.reduce(action, state),
|
||||
tokensState = TokensReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
saveWalletState = SaveWalletReducer.reduce(action, state),
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import com.tangem.tap.features.details.redux.DetailsMiddleware
|
|||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerMiddleware
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware
|
||||
|
|
@ -25,7 +23,6 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWallet
|
|||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensState
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
|
|
@ -43,8 +40,6 @@ data class AppState(
|
|||
val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
|
||||
val twinCardsState: TwinCardsState = TwinCardsState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val disclaimerState: DisclaimerState = DisclaimerState(),
|
||||
val tokensState: TokensState = TokensState(),
|
||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
val saveWalletState: SaveWalletState = SaveWalletState(),
|
||||
|
|
@ -75,7 +70,6 @@ data class AppState(
|
|||
OnboardingOtherCardsMiddleware.handler,
|
||||
TwinCardsMiddleware.handler,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
DisclaimerMiddleware().disclaimerMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
WelcomeMiddleware().middleware,
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@ package com.tangem.tap.common.redux.global
|
|||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.common.feedback.FeedbackData
|
||||
import com.tangem.tap.common.feedback.LegacyFeedbackManager
|
||||
import com.tangem.tap.common.redux.DebugErrorAction
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class GlobalAction : Action {
|
||||
|
|
@ -31,7 +30,11 @@ sealed class GlobalAction : Action {
|
|||
* For resuming unfinished backup of standard Wallet see
|
||||
* BackupAction.CheckForUnfinishedBackup, GlobalAction.Onboarding.StartForUnfinishedBackup
|
||||
*/
|
||||
data class Start(val scanResponse: ScanResponse, val canSkipBackup: Boolean = true) : Onboarding()
|
||||
data class Start(
|
||||
val scanResponse: ScanResponse,
|
||||
val source: BackupStartedSource,
|
||||
val canSkipBackup: Boolean = true,
|
||||
) : Onboarding()
|
||||
|
||||
/**
|
||||
* Initiate resuming of unfinished backup for standard Wallet.
|
||||
|
|
@ -71,10 +74,7 @@ sealed class GlobalAction : Action {
|
|||
|
||||
data class IsSignWithRing(val isSignWithRing: Boolean) : GlobalAction()
|
||||
|
||||
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
|
||||
data class SetFeedbackManager(val feedbackManager: LegacyFeedbackManager) : GlobalAction()
|
||||
|
||||
data class SendEmail(val feedbackData: FeedbackData, val scanResponse: ScanResponse?) : GlobalAction()
|
||||
data class SetConfigManager(val environmentConfigStorage: EnvironmentConfigStorage) : GlobalAction()
|
||||
|
||||
object ExchangeManager : GlobalAction() {
|
||||
object Init : GlobalAction() {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
@ -55,15 +55,9 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
is GlobalAction.RestoreAppCurrency -> {
|
||||
restoreAppCurrency()
|
||||
}
|
||||
is GlobalAction.SendEmail -> {
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
feedbackData = action.feedbackData,
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is GlobalAction.ExchangeManager.Init -> {
|
||||
val appStateSafe = appState() ?: return
|
||||
val config = appStateSafe.globalState.configManager?.config ?: return
|
||||
val config = appStateSafe.globalState.environmentConfigStorage?.getConfigSync() ?: return
|
||||
|
||||
scope.launch {
|
||||
val scanResponseProvider: () -> ScanResponse? = {
|
||||
|
|
@ -92,8 +86,10 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
scope.launch { exchangeManager.update() }
|
||||
}
|
||||
is GlobalAction.FetchUserCountry -> {
|
||||
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
|
||||
|
||||
if (!homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
|
||||
scope.launch {
|
||||
// TODO("After adding DI") get dependencies by DI
|
||||
runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() }
|
||||
.onSuccess {
|
||||
store.dispatchOnMain(
|
||||
|
|
@ -110,6 +106,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFailureChooseBehaviour(
|
||||
|
|
@ -143,21 +140,24 @@ private fun restoreAppCurrency() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun makeSellExchangeService(config: Config): ExchangeService {
|
||||
private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
|
||||
return MoonPayService(
|
||||
apiKey = config.moonPayApiKey,
|
||||
secretKey = config.moonPayApiSecretKey,
|
||||
apiKey = environmentConfig.moonPayApiKey,
|
||||
secretKey = environmentConfig.moonPayApiSecretKey,
|
||||
logEnabled = LogConfig.network.moonPayService,
|
||||
)
|
||||
}
|
||||
|
||||
private fun makeBuyExchangeService(config: Config): ExchangeService {
|
||||
private fun makeBuyExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
|
||||
return BuyExchangeService(
|
||||
mercuryoService = makeMercuryoExchangeService(config),
|
||||
mercuryoService = makeMercuryoExchangeService(environmentConfig),
|
||||
)
|
||||
}
|
||||
|
||||
private fun makeMercuryoExchangeService(config: Config): MercuryoService {
|
||||
val mercuryoEnvironment = MercuryoEnvironment.prod(config.mercuryoWidgetId, config.mercuryoSecret)
|
||||
private fun makeMercuryoExchangeService(environmentConfig: EnvironmentConfig): MercuryoService {
|
||||
val mercuryoEnvironment = MercuryoEnvironment.prod(
|
||||
environmentConfig.mercuryoWidgetId,
|
||||
environmentConfig.mercuryoSecret,
|
||||
)
|
||||
return MercuryoService(mercuryoEnvironment)
|
||||
}
|
||||
|
|
@ -2,12 +2,9 @@ package com.tangem.tap.common.redux.global
|
|||
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.domain.redux.global.DomainGlobalAction
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.extensions.replaceBy
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -51,7 +48,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
|||
globalState.copy(appCurrency = action.appCurrency)
|
||||
}
|
||||
is GlobalAction.SetConfigManager -> {
|
||||
globalState.copy(configManager = action.configManager)
|
||||
globalState.copy(environmentConfigStorage = action.environmentConfigStorage)
|
||||
}
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
val card = globalState.scanResponse?.card ?: return globalState
|
||||
|
|
@ -72,9 +69,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
|||
globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance))
|
||||
}
|
||||
is GlobalAction.IsSignWithRing -> globalState.copy(isLastSignWithRing = action.isSignWithRing)
|
||||
is GlobalAction.SetFeedbackManager -> {
|
||||
globalState.copy(feedbackManager = action.feedbackManager)
|
||||
}
|
||||
is GlobalAction.ShowDialog -> {
|
||||
globalState.copy(dialog = action.stateDialog)
|
||||
}
|
||||
|
|
@ -87,10 +81,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
|||
is GlobalAction.SetIfCardVerifiedOnline ->
|
||||
globalState.copy(cardVerifiedOnline = action.verified)
|
||||
is GlobalAction.FetchUserCountry.Success -> {
|
||||
store.dispatchOnMain(HomeAction.UpdateCountryCode(action.countryCode))
|
||||
globalState.copy(
|
||||
userCountryCode = action.countryCode,
|
||||
)
|
||||
globalState.copy(userCountryCode = action.countryCode)
|
||||
}
|
||||
else -> globalState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.common.feedback.LegacyFeedbackManager
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
|
|
@ -17,12 +16,12 @@ data class GlobalState(
|
|||
val onboardingState: OnboardingState = OnboardingState(),
|
||||
val cardVerifiedOnline: Boolean = false,
|
||||
val tapWalletManager: TapWalletManager = TapWalletManager(),
|
||||
val configManager: ConfigManager? = null,
|
||||
val feedbackManager: LegacyFeedbackManager? = null,
|
||||
val environmentConfigStorage: EnvironmentConfigStorage? = null,
|
||||
val appCurrency: AppCurrency = AppCurrency.Default,
|
||||
val scanCardFailsCounter: Int = 0,
|
||||
val dialog: StateDialog? = null,
|
||||
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
|
||||
@Deprecated("Use GetUserCountryCodeUseCase instead")
|
||||
val userCountryCode: String? = null,
|
||||
val appThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
|
||||
val isLastSignWithRing: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,12 @@
|
|||
package com.tangem.tap.common.redux.legacy
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.redux.LegacyAction
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
import com.tangem.tap.common.feedback.RateCanBeBetterEmail
|
||||
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -23,7 +17,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
internal object LegacyMiddleware {
|
||||
|
|
@ -33,68 +26,14 @@ internal object LegacyMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is LegacyAction.SendEmailRateCanBeBetter -> {
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
feedbackData = RateCanBeBetterEmail(),
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is LegacyAction.SendEmailSupport -> {
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
feedbackData = FeedbackEmail(),
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is LegacyAction.StartOnboardingProcess -> {
|
||||
store.dispatch(
|
||||
GlobalAction.Onboarding.Start(action.scanResponse, canSkipBackup = action.canSkipBackup),
|
||||
)
|
||||
}
|
||||
is LegacyAction.SendEmailTransactionFailed -> {
|
||||
if (store.inject(DaggerGraphState::feedbackManagerFeatureToggles).isLocalLogsEnabled) {
|
||||
|
||||
val amount = action.amount?.convertToSdkAmount(action.cryptoCurrency)
|
||||
store.inject(DaggerGraphState::saveBlockchainErrorUseCase).invoke(
|
||||
error = BlockchainErrorInfo(
|
||||
errorMessage = action.errorMessage,
|
||||
blockchainId = action.cryptoCurrency.network.id.value,
|
||||
derivationPath = action.cryptoCurrency.network.derivationPath.value,
|
||||
destinationAddress = action.destinationAddress.orEmpty(),
|
||||
tokenSymbol = if (amount?.type is AmountType.Token) {
|
||||
amount.currencySymbol
|
||||
} else {
|
||||
""
|
||||
},
|
||||
amount = amount?.value?.stripZeroPlainString() ?: "unknown",
|
||||
fee = action.fee?.convertToSdkAmount(action.cryptoCurrency)
|
||||
?.value?.stripZeroPlainString() ?: "unknown",
|
||||
GlobalAction.Onboarding.Start(
|
||||
scanResponse = action.scanResponse,
|
||||
source = BackupStartedSource.CreateBackup,
|
||||
canSkipBackup = action.canSkipBackup,
|
||||
),
|
||||
)
|
||||
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
feedbackData = SendTransactionFailedEmail(action.errorMessage),
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
} else {
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::walletManagersFacade)
|
||||
.getOrCreateWalletManager(
|
||||
userWalletId = action.userWalletId,
|
||||
network = action.cryptoCurrency.network,
|
||||
)?.let { walletManager ->
|
||||
store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
|
||||
walletManager = walletManager,
|
||||
amountToSend = action.amount?.convertToSdkAmount(action.cryptoCurrency),
|
||||
feeAmount = action.fee?.convertToSdkAmount(action.cryptoCurrency),
|
||||
destinationAddress = action.destinationAddress,
|
||||
)
|
||||
}
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
feedbackData = SendTransactionFailedEmail(action.errorMessage),
|
||||
scanResponse = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is LegacyAction.PrepareDetailsScreen -> {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
|
|
|||
|
|
@ -8,16 +8,19 @@ import androidx.core.view.isVisible
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.feedback.ScanFailsEmail
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -65,7 +68,11 @@ internal object ScanFailsDialog {
|
|||
}
|
||||
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
|
||||
Analytics.send(Basic.ButtonSupport(sourceAnalytics))
|
||||
store.dispatch(GlobalAction.SendEmail(feedbackData = ScanFailsEmail(), scanResponse = null))
|
||||
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase)
|
||||
.invoke(type = FeedbackEmailType.ScanningProblem)
|
||||
}
|
||||
}
|
||||
customView.findViewById<TextView>(R.id.cancel_button)?.setOnClickListener {
|
||||
store.dispatchDialogHide()
|
||||
|
|
|
|||
|
|
@ -1,27 +1,34 @@
|
|||
package com.tangem.core.navigation.email
|
||||
package com.tangem.tap.core.navigation.email
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.tap.foregroundActivityObserver
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Implementation of email sender for Android
|
||||
*
|
||||
* @property context context
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AndroidEmailSender(private val context: Context) : EmailSender {
|
||||
internal class AndroidEmailSender : EmailSender {
|
||||
|
||||
override fun send(email: EmailSender.Email, onFail: ((Exception) -> Unit)?) {
|
||||
val originalIntent = createEmailShareIntent(email)
|
||||
val activity = foregroundActivityObserver.foregroundActivity
|
||||
|
||||
if (activity == null) {
|
||||
Timber.e("Foreground activity not found")
|
||||
return
|
||||
}
|
||||
|
||||
val originalIntent = createEmailShareIntent(activity, email)
|
||||
val emailFilterIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"))
|
||||
|
||||
val packageManager = context.packageManager
|
||||
val packageManager = activity.packageManager
|
||||
val originalIntentResults = packageManager.queryIntentActivities(originalIntent, 0)
|
||||
val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0)
|
||||
|
||||
|
|
@ -32,7 +39,7 @@ internal class AndroidEmailSender(private val context: Context) : EmailSender {
|
|||
}
|
||||
}
|
||||
.map {
|
||||
createEmailShareIntent(email).apply { setPackage(it.activityInfo.packageName) }
|
||||
createEmailShareIntent(activity, email).apply { setPackage(it.activityInfo.packageName) }
|
||||
}
|
||||
.toMutableList()
|
||||
|
||||
|
|
@ -41,14 +48,14 @@ internal class AndroidEmailSender(private val context: Context) : EmailSender {
|
|||
putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
|
||||
}
|
||||
|
||||
ContextCompat.startActivity(context, chooserIntent, null)
|
||||
ContextCompat.startActivity(activity, chooserIntent, null)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e("Failed to send email: $ex")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEmailShareIntent(email: EmailSender.Email): Intent {
|
||||
val builder = ShareCompat.IntentBuilder(context)
|
||||
private fun createEmailShareIntent(activity: AppCompatActivity, email: EmailSender.Email): Intent {
|
||||
val builder = ShareCompat.IntentBuilder(activity)
|
||||
.setType("message/rfc822")
|
||||
.setEmailTo(arrayOf(email.address))
|
||||
.setSubject(email.subject)
|
||||
|
|
@ -56,7 +63,7 @@ internal class AndroidEmailSender(private val context: Context) : EmailSender {
|
|||
|
||||
email.attachment?.let {
|
||||
builder.setStream(
|
||||
FileProvider.getUriForFile(context, "${context.packageName}.provider", it),
|
||||
FileProvider.getUriForFile(activity, "${activity.packageName}.provider", it),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.tap.common.feedback.DefaultFeedbackManagerFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object FeedbackManagerFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeedbackManagerFeatureToggles(
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): FeedbackManagerFeatureToggles {
|
||||
return DefaultFeedbackManagerFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,6 @@ import com.tangem.core.navigation.finisher.AppFinisher
|
|||
import com.tangem.core.navigation.settings.SettingsManager
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.feedback.ProxyFeedbackManager
|
||||
import com.tangem.tap.common.finisher.AndroidAppFinisher
|
||||
import com.tangem.tap.common.settings.IntentSettingsManager
|
||||
import com.tangem.tap.common.share.IntentShareManager
|
||||
|
|
@ -30,10 +28,6 @@ internal object UtilsModule {
|
|||
@Singleton
|
||||
fun provideUrlOpener(): UrlOpener = CustomTabsUrlOpener()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeedbackManager(): FeedbackManager = ProxyFeedbackManager()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppFinisher(@ApplicationContext context: Context): AppFinisher = AndroidAppFinisher(context)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.di.core.navigation.email
|
||||
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.tap.core.navigation.email.AndroidEmailSender
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object EmailSenderModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEmailSender(): EmailSender = AndroidEmailSender()
|
||||
}
|
||||
|
|
@ -22,14 +22,6 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object CardDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetBiometricsStatusUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
): GetBiometricsStatusUseCase {
|
||||
return GetBiometricsStatusUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSetAccessCodeRequestPolicyUseCase(
|
||||
|
|
@ -38,12 +30,6 @@ internal object CardDomainModule {
|
|||
return SetAccessCodeRequestPolicyUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWasWalletAlreadySignedHashesConfirmedUseCase(cardRepository: CardRepository): WasCardScannedUseCase {
|
||||
return WasCardScannedUseCase(cardRepository = cardRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSetCardWasScannedUseCase(cardRepository: CardRepository): SetCardWasScannedUseCase {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.card.*
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -14,6 +16,12 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object CardLegacyDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCardScanningFeatureToggles(featureTogglesManager: FeatureTogglesManager): CardScanningFeatureToggles {
|
||||
return CardScanningFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideScanCardProcessor(): ScanCardProcessor = DefaultScanCardProcessor()
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import android.content.Context
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -27,8 +27,8 @@ internal object FeedbackDomainModule {
|
|||
fun provideGetFeedbackEmailUseCase(
|
||||
feedbackRepository: FeedbackRepository,
|
||||
@ApplicationContext context: Context,
|
||||
): GetFeedbackEmailUseCase {
|
||||
return GetFeedbackEmailUseCase(feedbackRepository = feedbackRepository, resources = context.resources)
|
||||
): SendFeedbackEmailUseCase {
|
||||
return SendFeedbackEmailUseCase(feedbackRepository = feedbackRepository, resources = context.resources)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -63,4 +63,10 @@ object MarketsDomainModule {
|
|||
networksRepository = networksRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTokenExchangesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenExchangesUseCase {
|
||||
return GetTokenExchangesUseCase(marketsTokenRepository = marketsTokenRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ import com.tangem.domain.settings.repositories.AppRatingRepository
|
|||
import com.tangem.domain.settings.repositories.PermissionRepository
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
|
||||
import dagger.Module
|
||||
|
|
@ -220,4 +222,16 @@ internal object SettingsDomainModule {
|
|||
return ShouldSaveAccessCodesUseCase(settingsRepository = settingsRepository)
|
||||
}
|
||||
// endregion
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchUserCountryCodeUseCase(settingsRepository: SettingsRepository): FetchUserCountryUseCase {
|
||||
return FetchUserCountryUseCase(settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetUserCountryCodeUseCase(settingsRepository: SettingsRepository): GetUserCountryUseCase {
|
||||
return GetUserCountryUseCase(settingsRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -206,4 +206,10 @@ internal object StakingDomainModule {
|
|||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetStakingIntegrationIdUseCase(stakingRepository: StakingRepository): GetStakingIntegrationIdUseCase {
|
||||
return GetStakingIntegrationIdUseCase(stakingRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -287,12 +287,6 @@ internal object TokensDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
|
||||
return GetCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsCryptoCurrencyCoinCouldHideUseCase(
|
||||
|
|
@ -313,68 +307,6 @@ internal object TokensDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideHasMissedAddressesCryptoCurrenciesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): GetMissedAddressesCryptoCurrenciesUseCase {
|
||||
return GetMissedAddressesCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetGlobalTokenListUseCase(tokensListRepository: TokensListRepository): GetGlobalTokenListUseCase {
|
||||
return GetGlobalTokenListUseCase(repository = tokensListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCheckTokenCompatibilityUseCase(
|
||||
networksCompatibilityRepository: NetworksCompatibilityRepository,
|
||||
): CheckCurrencyCompatibilityUseCase {
|
||||
return CheckCurrencyCompatibilityUseCase(networksCompatibilityRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNeedHardenedDerivationUseCase(
|
||||
networksCompatibilityRepository: NetworksCompatibilityRepository,
|
||||
): RequiresHardenedDerivationOnlyUseCase {
|
||||
return RequiresHardenedDerivationOnlyUseCase(networksCompatibilityRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFindTokenByContractAddressUseCase(
|
||||
tokensListRepository: TokensListRepository,
|
||||
): FindTokenByContractAddressUseCase {
|
||||
return FindTokenByContractAddressUseCase(repository = tokensListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideValidateContractAddressUseCase(
|
||||
tokensListRepository: TokensListRepository,
|
||||
): ValidateContractAddressUseCase {
|
||||
return ValidateContractAddressUseCase(tokensListRepository = tokensListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAreTokensSupportedByNetworkUseCase(
|
||||
repository: NetworksCompatibilityRepository,
|
||||
): AreTokensSupportedByNetworkUseCase {
|
||||
return AreTokensSupportedByNetworkUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNetworksSupportedByWallet(
|
||||
repository: NetworksCompatibilityRepository,
|
||||
): GetNetworksSupportedByWallet {
|
||||
return GetNetworksSupportedByWallet(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetBalanceNotEnoughForFeeWarningUseCase(
|
||||
|
|
|
|||
|
|
@ -42,21 +42,6 @@ internal object TransactionDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendMultipleTransactionUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
transactionRepository: TransactionRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): SendMultipleTransactionUseCase {
|
||||
return SendMultipleTransactionUseCase(
|
||||
demoConfig = DemoConfig(),
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
transactionRepository = transactionRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAssociateAssetUseCase(
|
||||
|
|
|
|||
|
|
@ -4,19 +4,12 @@ import com.tangem.blockchain.common.Token
|
|||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
|
|
@ -54,30 +47,13 @@ class TapWalletManager(
|
|||
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
|
||||
|
||||
val featureToggles = store.inject(DaggerGraphState::feedbackManagerFeatureToggles)
|
||||
if (!featureToggles.isLocalLogsEnabled) {
|
||||
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse)
|
||||
}
|
||||
|
||||
updateConfigManager(scanResponse)
|
||||
withMainContext {
|
||||
// Order is important
|
||||
store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer()))
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
|
||||
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
|
||||
}
|
||||
}
|
||||
|
||||
fun updateConfigManager(data: ScanResponse) {
|
||||
val configManager = store.state.globalState.configManager
|
||||
|
||||
if (data.cardTypesResolver.isStart2Coin()) {
|
||||
configManager?.turnOff(ConfigManager.IS_TOP_UP_ENABLED)
|
||||
} else {
|
||||
configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Wallet.getFirstToken(): Token? = getTokens().toList().getOrNull(index = 0)
|
||||
|
|
@ -12,8 +12,8 @@ import com.tangem.common.extensions.toMapKey
|
|||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.card.BackendId
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -21,6 +21,7 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
|
@ -47,7 +48,7 @@ internal class DefaultDerivationsRepository(
|
|||
getNetwork(
|
||||
blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -73,17 +74,17 @@ internal class DefaultDerivationsRepository(
|
|||
|
||||
override suspend fun hasMissedDerivations(
|
||||
userWalletId: UserWalletId,
|
||||
networksWithDerivationPath: Map<Network.ID, String?>,
|
||||
networksWithDerivationPath: Map<BackendId, String?>,
|
||||
): Boolean {
|
||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||
|
||||
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
|
||||
.findByNetworks(
|
||||
networksWithDerivationPath.mapNotNull { (networkId, extraDerivationPath) ->
|
||||
networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) ->
|
||||
getNetwork(
|
||||
blockchain = Blockchain.fromNetworkId(networkId.value) ?: return@mapNotNull null,
|
||||
blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -92,8 +93,13 @@ internal class DefaultDerivationsRepository(
|
|||
}
|
||||
|
||||
override suspend fun derivePublicKeys(userWalletId: UserWalletId, derivations: Derivations): DerivedKeys {
|
||||
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
|
||||
.doOnSuccess { response ->
|
||||
// todo replace it in task [REDACTED_JIRA]
|
||||
val preflightReadFilter = UserWalletIdPreflightReadFilter(userWalletId)
|
||||
tangemSdkManager.derivePublicKeys(
|
||||
cardId = null,
|
||||
derivations = derivations,
|
||||
preflightReadFilter = preflightReadFilter,
|
||||
).doOnSuccess { response ->
|
||||
updatePublicKeys(userWalletId = userWalletId, keys = response.entries)
|
||||
.doOnSuccess {
|
||||
validateDerivations(scanResponse = it.scanResponse, derivations = derivations)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.tap.domain.scanCard
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
/**
|
||||
* Add custom token feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CardScanningFeatureToggles internal constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) {
|
||||
|
||||
val isNewCardScanningEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "NEW_CARD_SCANNING_ENABLED")
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ import com.tangem.tap.store
|
|||
// TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead
|
||||
internal class DefaultScanCardProcessor : ScanCardProcessor {
|
||||
private val isNewCardScanningEnabled: Boolean
|
||||
get() = store.inject(DaggerGraphState::customTokenFeatureToggles).isNewCardScanningEnabled
|
||||
get() = store.inject(DaggerGraphState::cardScanningFeatureToggles).isNewCardScanningEnabled
|
||||
|
||||
override suspend fun scan(
|
||||
cardId: String?,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.domain.scanCard
|
|||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.routing.AppRoute
|
||||
|
|
@ -18,12 +17,10 @@ import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
|
|||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -102,6 +99,8 @@ internal object LegacyScanProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private suspend inline fun showDisclaimerIfNeed(
|
||||
scanResponse: ScanResponse,
|
||||
crossinline disclaimerWillShow: () -> Unit = {},
|
||||
|
|
@ -109,7 +108,6 @@ internal object LegacyScanProcessor {
|
|||
crossinline onFailure: suspend (error: TangemError) -> Unit,
|
||||
) {
|
||||
val disclaimer = scanResponse.card.createDisclaimer()
|
||||
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
|
||||
|
||||
if (disclaimer.isAccepted()) {
|
||||
nextHandler(scanResponse)
|
||||
|
|
@ -120,23 +118,9 @@ internal object LegacyScanProcessor {
|
|||
withContext(Dispatchers.Main.immediate) {
|
||||
disclaimerWillShow()
|
||||
|
||||
store.dispatch(
|
||||
DisclaimerAction.Show(
|
||||
from = DisclaimerSource.Home,
|
||||
callback = DisclaimerCallback(
|
||||
onAccept = {
|
||||
scope.launch(Dispatchers.Main.immediate) {
|
||||
nextHandler(scanResponse)
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.Disclaimer(isTosAccepted = false))
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
scope.launch(Dispatchers.Main.immediate) {
|
||||
onFailure(TangemSdkError.UserCancelled())
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -149,10 +133,6 @@ internal object LegacyScanProcessor {
|
|||
crossinline onWalletNotCreated: suspend () -> Unit,
|
||||
crossinline onSuccess: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
val globalState = store.state.globalState
|
||||
val tapWalletManager = globalState.tapWalletManager
|
||||
tapWalletManager.updateConfigManager(scanResponse)
|
||||
|
||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
|
||||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
|
|
@ -162,6 +142,7 @@ internal object LegacyScanProcessor {
|
|||
store.dispatchOnMain(
|
||||
GlobalAction.Onboarding.Start(
|
||||
scanResponse = scanResponse,
|
||||
source = BackupStartedSource.Onboarding,
|
||||
canSkipBackup = scanResponse.card.canSkipBackup,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -59,12 +59,8 @@ internal object UseCaseScanProcessor {
|
|||
),
|
||||
)
|
||||
add(AnalyticsChain(Basic.CardWasScanned(analyticsSource)))
|
||||
val pushNotificationsToggles =
|
||||
store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles)
|
||||
if (pushNotificationsToggles.isPushNotificationsEnabled) {
|
||||
add(DisclaimerChain(store, disclaimerWillShow))
|
||||
}
|
||||
add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager))
|
||||
add(CheckForOnboardingChain(store))
|
||||
}
|
||||
|
||||
scanCardUseCase(cardId, afterScanChains = chains).fold(
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ import com.tangem.tap.common.extensions.inject
|
|||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -32,17 +32,14 @@ import org.rekotlin.Store
|
|||
* - [ScanChainException.OnboardingNeeded] if onboarding required.
|
||||
*
|
||||
* @param store the [Store] that holds the state of the app.
|
||||
* @param tapWalletManager manager responsible for handling operations related to the Wallet.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
class CheckForOnboardingChain(
|
||||
private val store: Store<AppState>,
|
||||
private val tapWalletManager: TapWalletManager,
|
||||
) : ResultChain<ScanCardException, ScanResponse>() {
|
||||
|
||||
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
|
||||
tapWalletManager.updateConfigManager(previousChainResult)
|
||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult))
|
||||
|
||||
return when {
|
||||
|
|
@ -52,6 +49,7 @@ class CheckForOnboardingChain(
|
|||
store.dispatchOnMain(
|
||||
GlobalAction.Onboarding.Start(
|
||||
scanResponse = previousChainResult,
|
||||
source = BackupStartedSource.Onboarding,
|
||||
canSkipBackup = previousChainResult.card.canSkipBackup,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,15 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.core.chain.ResultChain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.rekotlin.Store
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
/**
|
||||
* Handles disclaimer display after the card scanning operation.
|
||||
|
|
@ -40,31 +34,13 @@ internal class DisclaimerChain(
|
|||
previousChainResult.right()
|
||||
} else {
|
||||
disclaimerWillShow()
|
||||
showDisclaimer(disclaimer, previousChainResult)
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
store.dispatchNavigationAction {
|
||||
push(route = AppRoute.Disclaimer(isTosAccepted = false))
|
||||
}
|
||||
|
||||
private suspend fun showDisclaimer(disclaimer: Disclaimer, response: ScanResponse): ScanChainResult {
|
||||
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
|
||||
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
store.dispatchOnMain(
|
||||
DisclaimerAction.Show(
|
||||
from = DisclaimerSource.Home,
|
||||
callback = DisclaimerCallback(
|
||||
onAccept = {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(response.right())
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(ScanChainException.DisclaimerWasCanceled.left())
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||
|
||||
|
|
@ -59,6 +60,7 @@ interface TangemSdkManager {
|
|||
suspend fun derivePublicKeys(
|
||||
cardId: String?,
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>,
|
||||
preflightReadFilter: PreflightReadFilter?,
|
||||
): CompletionResult<DerivationTaskResponse>
|
||||
|
||||
suspend fun deriveExtendedPublicKey(
|
||||
|
|
@ -99,6 +101,7 @@ interface TangemSdkManager {
|
|||
)
|
||||
suspend fun <T> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>,
|
||||
preflightReadFilter: PreflightReadFilter?,
|
||||
cardId: String? = null,
|
||||
initialMessage: Message? = null,
|
||||
accessCode: String? = null,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.tangem.operations.derivation.DerivationTaskResponse
|
|||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.tap.derivationsFinder
|
||||
|
|
@ -157,6 +158,7 @@ class DefaultTangemSdkManager(
|
|||
Message(resources.getString(R.string.initial_message_create_wallet_body))
|
||||
},
|
||||
iconScanRes = if (scanResponse.cardTypesResolver.isRing()) R.drawable.img_hand_scan_ring else null,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
.doOnResult { tangemSdk.config.setupForProduct(ProductType.ANY) }
|
||||
}
|
||||
|
|
@ -195,6 +197,7 @@ class DefaultTangemSdkManager(
|
|||
} else {
|
||||
Message(resources.getString(R.string.initial_message_create_wallet_body))
|
||||
},
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
.doOnResult { tangemSdk.config.setupForProduct(ProductType.ANY) }
|
||||
}
|
||||
|
|
@ -210,8 +213,13 @@ class DefaultTangemSdkManager(
|
|||
override suspend fun derivePublicKeys(
|
||||
cardId: String?,
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>,
|
||||
preflightReadFilter: PreflightReadFilter?,
|
||||
): CompletionResult<DerivationTaskResponse> {
|
||||
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = DeriveMultipleWalletPublicKeysTask(derivations),
|
||||
cardId = cardId,
|
||||
preflightReadFilter = preflightReadFilter,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun deriveExtendedPublicKey(
|
||||
|
|
@ -317,13 +325,21 @@ class DefaultTangemSdkManager(
|
|||
|
||||
override suspend fun <T> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>,
|
||||
preflightReadFilter: PreflightReadFilter?,
|
||||
cardId: String?,
|
||||
initialMessage: Message?,
|
||||
accessCode: String?,
|
||||
@DrawableRes iconScanRes: Int?,
|
||||
): CompletionResult<T> = withContext(Dispatchers.Main) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode, iconScanRes) { result ->
|
||||
tangemSdk.startSessionWithRunnable(
|
||||
runnable = runnable,
|
||||
cardId = cardId,
|
||||
initialMessage = initialMessage,
|
||||
accessCode = accessCode,
|
||||
preflightReadFilter = preflightReadFilter,
|
||||
iconScanRes = iconScanRes,
|
||||
) { result ->
|
||||
if (continuation.isActive) continuation.resume(result)
|
||||
}
|
||||
}
|
||||
|
|
@ -333,8 +349,14 @@ class DefaultTangemSdkManager(
|
|||
runnable: CardSessionRunnable<T>,
|
||||
cardId: String? = null,
|
||||
initialMessage: Message? = null,
|
||||
preflightReadFilter: PreflightReadFilter? = null,
|
||||
): CompletionResult<T> {
|
||||
val result = runTaskAsync(runnable, cardId, initialMessage)
|
||||
val result = runTaskAsync(
|
||||
runnable = runnable,
|
||||
cardId = cardId,
|
||||
initialMessage = initialMessage,
|
||||
preflightReadFilter = preflightReadFilter,
|
||||
)
|
||||
return withContext(Dispatchers.Main) { result }
|
||||
}
|
||||
|
||||
|
|
@ -386,6 +408,7 @@ class DefaultTangemSdkManager(
|
|||
runnable = CreateFirstTwinWalletTask(cardId),
|
||||
cardId = cardId,
|
||||
initialMessage = initialMessage,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -404,7 +427,7 @@ class DefaultTangemSdkManager(
|
|||
preparingMessage = preparingMessage,
|
||||
creatingWalletMessage = creatingWalletMessage,
|
||||
)
|
||||
return runTaskAsync(task, null, initialMessage)
|
||||
return runTaskAsync(runnable = task, cardId = null, initialMessage = initialMessage, preflightReadFilter = null)
|
||||
}
|
||||
|
||||
override fun changeProductType(isRing: Boolean) {
|
||||
|
|
@ -427,6 +450,7 @@ class DefaultTangemSdkManager(
|
|||
runnable = FinalizeTwinTask(secondCardPublicKey, issuerKeyPair),
|
||||
cardId = cardId,
|
||||
initialMessage = initialMessage,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
|
|
@ -72,6 +73,7 @@ class MockTangemSdkManager(
|
|||
override suspend fun derivePublicKeys(
|
||||
cardId: String?,
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>,
|
||||
preflightReadFilter: PreflightReadFilter?,
|
||||
): CompletionResult<DerivationTaskResponse> {
|
||||
return MockProvider.getDerivationTaskResponse()
|
||||
}
|
||||
|
|
@ -135,6 +137,7 @@ class MockTangemSdkManager(
|
|||
|
||||
override suspend fun <T> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>,
|
||||
preflightReadFilter: PreflightReadFilter?,
|
||||
cardId: String?,
|
||||
initialMessage: Message?,
|
||||
accessCode: String?,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO remove it after test after resolve [REDACTED_JIRA]
|
||||
internal class ResetBackupCardTask(
|
||||
private val userWalletId: UserWalletId,
|
||||
) : CardSessionRunnable<Boolean> {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import com.github.salomonbrys.kotson.registerTypeAdapter
|
|||
import com.google.gson.GsonBuilder
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.tradeOrderSerializer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.TradeData
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import timber.log.Timber
|
||||
|
||||
internal object BnbHelper {
|
||||
|
|
|
|||
|
|
@ -241,16 +241,22 @@ class WalletConnectSdkHelper {
|
|||
private suspend fun signTransaction(data: WcTransactionData, cardId: String?): String? {
|
||||
val dataToSign = EthereumUtils.buildTransactionToSign(
|
||||
transactionData = data.transaction,
|
||||
nonce = null,
|
||||
blockchain = data.walletManager.wallet.blockchain,
|
||||
) ?: return null
|
||||
)
|
||||
|
||||
val command = SignHashCommand(
|
||||
hash = dataToSign.hash,
|
||||
walletPublicKey = data.walletManager.wallet.publicKey.seedKey,
|
||||
derivationPath = data.walletManager.wallet.publicKey.derivationPath,
|
||||
)
|
||||
return when (val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId)) {
|
||||
return when (
|
||||
val result = tangemSdkManager.runTaskAsync(
|
||||
runnable = command,
|
||||
initialMessage = Message(),
|
||||
cardId = cardId,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val hash = EthereumUtils.prepareTransactionToSend(
|
||||
signature = result.data.signature,
|
||||
|
|
@ -293,7 +299,14 @@ class WalletConnectSdkHelper {
|
|||
walletPublicKey = wallet.publicKey.seedKey,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
)
|
||||
return when (val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId)) {
|
||||
return when (
|
||||
val result = tangemSdkManager.runTaskAsync(
|
||||
runnable = command,
|
||||
initialMessage = Message(),
|
||||
cardId = cardId,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val key = wallet.publicKey.blockchainKey.toDecompressedPublicKey()
|
||||
getBnbResultString(
|
||||
|
|
@ -387,7 +400,13 @@ class WalletConnectSdkHelper {
|
|||
walletPublicKey = wallet.publicKey.seedKey,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
)
|
||||
return when (val result = tangemSdkManager.runTaskAsync(command, cardId)) {
|
||||
return when (
|
||||
val result = tangemSdkManager.runTaskAsync(
|
||||
runnable = command,
|
||||
cardId = cardId,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val signedHash = result.data.signature
|
||||
|
||||
|
|
@ -422,7 +441,13 @@ class WalletConnectSdkHelper {
|
|||
walletPublicKey = wallet.publicKey.seedKey,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
)
|
||||
return when (val result = tangemSdkManager.runTaskAsync(command, cardId)) {
|
||||
return when (
|
||||
val result = tangemSdkManager.runTaskAsync(
|
||||
runnable = command,
|
||||
cardId = cardId,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val signedHash = result.data.signature
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.api.featuretoggles
|
||||
|
||||
/**
|
||||
* Add custom token feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface CustomTokenFeatureToggles {
|
||||
|
||||
val isNewCardScanningEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.data
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.tap.features.customtoken.impl.data.converters.FoundTokenConverter
|
||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenRepository
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Default implementation of custom token repository
|
||||
*
|
||||
* @property tangemTechApi TangemTech API
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
* @property reduxStateHolder redux state holder
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DefaultCustomTokenRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val reduxStateHolder: AppStateHolder,
|
||||
) : CustomTokenRepository {
|
||||
|
||||
override suspend fun findToken(address: String, networkId: String?): FoundToken {
|
||||
val scanResponse = requireNotNull(reduxStateHolder.scanResponse)
|
||||
val supportedTokenNetworkIds = requireNotNull(scanResponse.card)
|
||||
.supportedBlockchains(scanResponse.cardTypesResolver)
|
||||
.filter(Blockchain::canHandleTokens)
|
||||
.map(Blockchain::toNetworkId)
|
||||
|
||||
return withContext(dispatchers.io) {
|
||||
val foundCoin = tangemTechApi.getCoins(
|
||||
contractAddress = address,
|
||||
networkIds = networkId ?: supportedTokenNetworkIds.joinToString(separator = ","),
|
||||
)
|
||||
.getOrThrow()
|
||||
.coins.firstNotNullOfOrNull { coin ->
|
||||
val networksWithTheSameAddress = coin.networks.filter { network ->
|
||||
(network.contractAddress != null || network.decimalCount != null) &&
|
||||
network.contractAddress?.equals(address, ignoreCase = true) == true &&
|
||||
supportedTokenNetworkIds.contains(network.networkId)
|
||||
}
|
||||
|
||||
if (networksWithTheSameAddress.isNotEmpty()) {
|
||||
coin.copy(networks = networksWithTheSameAddress)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
foundCoin?.let(FoundTokenConverter::convert) ?: error("Token not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.data.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converter between data model [CoinsResponse.Coin] and domain model [FoundToken]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object FoundTokenConverter : Converter<CoinsResponse.Coin, FoundToken> {
|
||||
|
||||
override fun convert(value: CoinsResponse.Coin): FoundToken {
|
||||
return FoundToken(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
isActive = value.active,
|
||||
network = value.networks.firstOrNull()?.let { network ->
|
||||
FoundToken.Network(
|
||||
id = network.networkId,
|
||||
contractAddress = requireNotNull(network.contractAddress),
|
||||
decimalCount = requireNotNull(network.decimalCount).toString(),
|
||||
)
|
||||
} ?: error("Found token networks is empty"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.customtoken.impl.featuretoggles.DefaultCustomTokenFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CustomTokenFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesCustomTokenFeatureToggles(featureTogglesManager: FeatureTogglesManager): CustomTokenFeatureToggles {
|
||||
return DefaultCustomTokenFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.di
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.features.customtoken.impl.data.DefaultCustomTokenRepository
|
||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
|
||||
import com.tangem.tap.features.customtoken.impl.domain.DefaultCustomTokenInteractor
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ViewModelComponent
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(ViewModelComponent::class)
|
||||
internal object CustomTokenInteractorModule {
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideCustomTokenInteractor(
|
||||
tangemTechApi: TangemTechApi,
|
||||
appCoroutineDispatcherProvider: AppCoroutineDispatcherProvider,
|
||||
reduxStateHolder: AppStateHolder,
|
||||
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
): CustomTokenInteractor {
|
||||
return DefaultCustomTokenInteractor(
|
||||
featureRepository = DefaultCustomTokenRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
dispatchers = appCoroutineDispatcherProvider,
|
||||
reduxStateHolder = reduxStateHolder,
|
||||
),
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
derivePublicKeysUseCase = derivePublicKeysUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.di
|
||||
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.routers.CustomTokenRouter
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.routers.DefaultCustomTokenRouter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ViewModelComponent
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(ViewModelComponent::class)
|
||||
internal object CustomTokenRouterModule {
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideAddCustomTokenRouter(): CustomTokenRouter = DefaultCustomTokenRouter()
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
|
||||
/**
|
||||
* Custom token interactor
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface CustomTokenInteractor {
|
||||
|
||||
/** Find token by [address] and [blockchain] */
|
||||
suspend fun findToken(address: String, blockchain: Blockchain): FoundToken
|
||||
|
||||
/** Save token [customCurrency] */
|
||||
@Throws(TangemError::class)
|
||||
suspend fun saveToken(customCurrency: CustomCurrency): Result<Unit>
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.domain
|
||||
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
|
||||
/**
|
||||
* Custom token repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface CustomTokenRepository {
|
||||
|
||||
/**
|
||||
* Find token by [address] and [networkId]
|
||||
*
|
||||
* @throws com.tangem.datasource.api.common.response.ApiResponseError
|
||||
* */
|
||||
suspend fun findToken(address: String, networkId: String?): FoundToken
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.domain
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.result
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
* Default implementation of custom token interactor
|
||||
*
|
||||
* @property featureRepository feature repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DefaultCustomTokenInteractor(
|
||||
private val featureRepository: CustomTokenRepository,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
) : CustomTokenInteractor {
|
||||
|
||||
// TODO: Move to DI
|
||||
private val addCryptoCurrenciesUseCase by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val currenciesRepository = store.inject(DaggerGraphState::currenciesRepository)
|
||||
val networksRepository = store.inject(DaggerGraphState::networksRepository)
|
||||
|
||||
AddCryptoCurrenciesUseCase(currenciesRepository, networksRepository)
|
||||
}
|
||||
|
||||
override suspend fun findToken(address: String, blockchain: Blockchain): FoundToken {
|
||||
return featureRepository.findToken(
|
||||
address = address,
|
||||
networkId = if (blockchain != Blockchain.Unknown) blockchain.toNetworkId() else null,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun saveToken(customCurrency: CustomCurrency): Result<Unit> {
|
||||
return result {
|
||||
val userWallet = getSelectedWalletSyncUseCase().getOrElse {
|
||||
error("Failed to get selected wallet: $it")
|
||||
}
|
||||
|
||||
val currency = Currency.fromCustomCurrency(customCurrency)
|
||||
val currencies = listOfNotNull(element = currency.toCryptoCurrency(userWallet.scanResponse))
|
||||
|
||||
derivePublicKeysUseCase(userWalletId = userWallet.walletId, currencies = currencies).bind()
|
||||
addCryptoCurrenciesUseCase(userWalletId = userWallet.walletId, currencies = currencies).bind()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Currency.toCryptoCurrency(scanResponse: ScanResponse): CryptoCurrency? {
|
||||
val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
||||
|
||||
return when (this) {
|
||||
is Currency.Blockchain -> {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = derivationPath,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
is Currency.Token -> {
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = derivationPath,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.domain.models
|
||||
|
||||
/**
|
||||
* Found token model
|
||||
*
|
||||
* @property id id
|
||||
* @property name name
|
||||
* @property symbol symbol
|
||||
* @property isActive flag that determines status of token
|
||||
* @property network network
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class FoundToken(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val isActive: Boolean,
|
||||
val network: Network,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Found token network
|
||||
*
|
||||
* @property id id
|
||||
* @property contractAddress address
|
||||
* @property decimalCount decimal count
|
||||
*/
|
||||
data class Network(val id: String, val contractAddress: String, val decimalCount: String)
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
|
||||
/**
|
||||
* Default implementation of CustomToken feature toggles
|
||||
*
|
||||
* @property featureTogglesManager manager for getting information about the availability of feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultCustomTokenFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : CustomTokenFeatureToggles {
|
||||
|
||||
override val isNewCardScanningEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "NEW_CARD_SCANNING_ENABLED")
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenScreen
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.viewmodels.AddCustomTokenViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Add custom token screen
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
internal class AddCustomTokenFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val viewModel = hiltViewModel<AddCustomTokenViewModel>().apply {
|
||||
LocalLifecycleOwner.current.lifecycle.addObserver(this)
|
||||
}
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
AddCustomTokenScreen(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.systemBarsPadding(),
|
||||
stateHolder = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,324 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.models
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
* Toolbar model of add custom token screen
|
||||
*
|
||||
* @property title title
|
||||
* @property onBackButtonClick lambda be invoked when back button is been pressed
|
||||
*/
|
||||
internal data class AddCustomTokensToolbar(val title: TextReference, val onBackButtonClick: () -> Unit)
|
||||
|
||||
/**
|
||||
* Model of block with fields for testing
|
||||
*
|
||||
* @property chooseTokenButtonText choose token button text
|
||||
* @property clearButtonText clear button text
|
||||
* @property resetButtonText reset button text
|
||||
* @property onClearAddressButtonClick lambda be invoked when clear address button is been pressed
|
||||
* @property onResetButtonClick lambda be invoked when reset form fields button is been pressed
|
||||
*/
|
||||
internal data class AddCustomTokenTestBlock(
|
||||
val chooseTokenButtonText: String,
|
||||
val clearButtonText: String,
|
||||
val resetButtonText: String,
|
||||
val onClearAddressButtonClick: () -> Unit,
|
||||
val onResetButtonClick: () -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Bottom sheet model for choose custom token
|
||||
*
|
||||
* @property categoriesBlocks tokens categories
|
||||
* @property onTestTokenClick lambda be invoked when token is been pressed
|
||||
*/
|
||||
internal data class AddCustomTokenChooseTokenBottomSheet(
|
||||
val categoriesBlocks: List<TokensCategoryBlock>,
|
||||
val onTestTokenClick: (String) -> Unit,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Tokens category model
|
||||
*
|
||||
* @property name category name
|
||||
* @property items category items
|
||||
*/
|
||||
data class TokensCategoryBlock(val name: String, val items: List<TestTokenItem>)
|
||||
|
||||
/**
|
||||
* Test token model
|
||||
*
|
||||
* @property name token name
|
||||
* @property address token address
|
||||
*/
|
||||
data class TestTokenItem(val name: String, val address: String)
|
||||
}
|
||||
|
||||
/**
|
||||
* Form with fields model of add custom token screen
|
||||
*
|
||||
* @property contractAddressInputField input field to enter the contract address
|
||||
* @property networkSelectorField selector field to select the token network
|
||||
* @property tokenNameInputField input field to enter the token name
|
||||
* @property tokenSymbolInputField input field to enter the token symbol
|
||||
* @property decimalsInputField input field to enter the token decimals
|
||||
* @property derivationPathSelectorField selector field to select the derivation path
|
||||
* @property derivationPathInputField input field for a custom derivation path
|
||||
* @property showTokenFields if token fields should be shown
|
||||
*/
|
||||
internal data class AddCustomTokenForm(
|
||||
val contractAddressInputField: AddCustomTokenInputField.ContactAddress,
|
||||
val networkSelectorField: AddCustomTokenSelectorField.Network,
|
||||
val tokenNameInputField: AddCustomTokenInputField.TokenName,
|
||||
val tokenSymbolInputField: AddCustomTokenInputField.TokenSymbol,
|
||||
val decimalsInputField: AddCustomTokenInputField.Decimals,
|
||||
val derivationPathSelectorField: AddCustomTokenSelectorField.DerivationPath?,
|
||||
val derivationPathInputField: AddCustomTokenInputField.DerivationPath?,
|
||||
val showTokenFields: Boolean = false,
|
||||
)
|
||||
|
||||
/** Base input field model of add custom token screen */
|
||||
internal sealed interface AddCustomTokenInputField {
|
||||
|
||||
/** Current value */
|
||||
val value: String
|
||||
|
||||
/** Lambda be invoked when value is been changed */
|
||||
val onValueChange: (String) -> Unit
|
||||
|
||||
/** Keyboard options */
|
||||
val keyboardOptions: KeyboardOptions
|
||||
|
||||
/** Label */
|
||||
val label: TextReference
|
||||
|
||||
/** Placeholder (hint) */
|
||||
val placeholder: TextReference
|
||||
|
||||
/**
|
||||
* Input field model to enter the contract address
|
||||
*
|
||||
* @property value current value
|
||||
* @property onValueChange lambda be invoked when value is been changed
|
||||
* @property keyboardOptions keyboard options
|
||||
* @property label label
|
||||
* @property placeholder placeholder (hint)
|
||||
* @property isLoading flag that determine the processing of current value
|
||||
* @property isError flag that determine if current value has error
|
||||
* @property error error description
|
||||
*/
|
||||
data class ContactAddress(
|
||||
override val value: String,
|
||||
override val onValueChange: (String) -> Unit,
|
||||
override val keyboardOptions: KeyboardOptions,
|
||||
override val label: TextReference,
|
||||
override val placeholder: TextReference,
|
||||
val isLoading: Boolean,
|
||||
val isError: Boolean,
|
||||
val error: TextReference? = null,
|
||||
) : AddCustomTokenInputField
|
||||
|
||||
/**
|
||||
* Input field model to enter the token name
|
||||
*
|
||||
* @property value current value
|
||||
* @property onValueChange lambda be invoked when value is been changed
|
||||
* @property keyboardOptions keyboard options
|
||||
* @property label label
|
||||
* @property placeholder placeholder (hint)
|
||||
* @property isEnabled input availability
|
||||
*/
|
||||
data class TokenName(
|
||||
override val value: String,
|
||||
override val onValueChange: (String) -> Unit,
|
||||
override val keyboardOptions: KeyboardOptions,
|
||||
override val label: TextReference,
|
||||
override val placeholder: TextReference,
|
||||
val isEnabled: Boolean,
|
||||
) : AddCustomTokenInputField
|
||||
|
||||
/**
|
||||
* Input field model to enter the token symbol
|
||||
*
|
||||
* @property value current value
|
||||
* @property onValueChange lambda be invoked when value is been changed
|
||||
* @property keyboardOptions keyboard options
|
||||
* @property label label
|
||||
* @property placeholder placeholder (hint)
|
||||
* @property isEnabled input availability
|
||||
*/
|
||||
data class TokenSymbol(
|
||||
override val value: String,
|
||||
override val onValueChange: (String) -> Unit,
|
||||
override val keyboardOptions: KeyboardOptions,
|
||||
override val label: TextReference,
|
||||
override val placeholder: TextReference,
|
||||
val isEnabled: Boolean,
|
||||
) : AddCustomTokenInputField
|
||||
|
||||
/**
|
||||
* Input field model to enter the token decimals
|
||||
*
|
||||
* @property value current value
|
||||
* @property onValueChange lambda be invoked when value is been changed
|
||||
* @property keyboardOptions keyboard options
|
||||
* @property label label
|
||||
* @property placeholder placeholder (hint)
|
||||
* @property isEnabled input availability
|
||||
*/
|
||||
data class Decimals(
|
||||
override val value: String,
|
||||
override val onValueChange: (String) -> Unit,
|
||||
override val keyboardOptions: KeyboardOptions,
|
||||
override val label: TextReference,
|
||||
override val placeholder: TextReference,
|
||||
val isEnabled: Boolean,
|
||||
) : AddCustomTokenInputField
|
||||
|
||||
/**
|
||||
* Input field model to enter a custom derivation path
|
||||
*
|
||||
* @property value current value
|
||||
* @property onValueChange lambda be invoked when value is been changed
|
||||
* @property keyboardOptions keyboard options
|
||||
* @property label label
|
||||
* @property placeholder placeholder (hint)
|
||||
* @property showField whether the field should be shown
|
||||
*/
|
||||
data class DerivationPath(
|
||||
override val value: String,
|
||||
override val onValueChange: (String) -> Unit,
|
||||
override val keyboardOptions: KeyboardOptions,
|
||||
override val label: TextReference,
|
||||
override val placeholder: TextReference,
|
||||
val showField: Boolean = false,
|
||||
) : AddCustomTokenInputField
|
||||
}
|
||||
|
||||
/** Base selector field model of add custom token screen */
|
||||
internal sealed interface AddCustomTokenSelectorField {
|
||||
|
||||
/** Label */
|
||||
val label: TextReference
|
||||
|
||||
/** Selected menu item */
|
||||
val selectedItem: SelectorItem
|
||||
|
||||
/** Menu items */
|
||||
val items: List<SelectorItem>
|
||||
|
||||
/** Lambda be invoked when menu item is been selected */
|
||||
val onMenuItemClick: (Int) -> Unit
|
||||
|
||||
/**
|
||||
* Network selector model
|
||||
*
|
||||
* @property label label
|
||||
* @property selectedItem selected menu item
|
||||
* @property items menu items
|
||||
* @property onMenuItemClick lambda be invoked when menu item is been selected
|
||||
*/
|
||||
data class Network(
|
||||
override val label: TextReference,
|
||||
override val selectedItem: SelectorItem.Title,
|
||||
override val items: List<SelectorItem.Title>,
|
||||
override val onMenuItemClick: (Int) -> Unit,
|
||||
) : AddCustomTokenSelectorField
|
||||
|
||||
/**
|
||||
* Derivation path selector model
|
||||
*
|
||||
* @property label label
|
||||
* @property selectedItem selected menu item
|
||||
* @property items menu items
|
||||
* @property onMenuItemClick lambda be invoked when menu item is been selected
|
||||
* @property isEnabled selection availability
|
||||
*/
|
||||
data class DerivationPath(
|
||||
override val label: TextReference,
|
||||
override val selectedItem: SelectorItem.TitleWithSubtitle,
|
||||
override val items: List<SelectorItem.TitleWithSubtitle>,
|
||||
override val onMenuItemClick: (Int) -> Unit,
|
||||
val isEnabled: Boolean,
|
||||
) : AddCustomTokenSelectorField
|
||||
|
||||
/** Base menu item model */
|
||||
sealed interface SelectorItem {
|
||||
|
||||
/** Title */
|
||||
val title: TextReference
|
||||
|
||||
/** Blockchain */
|
||||
val blockchain: Blockchain
|
||||
|
||||
/**
|
||||
* Menu item with title
|
||||
*
|
||||
* @property title title text
|
||||
* @property blockchain blockchain
|
||||
*/
|
||||
data class Title(override val title: TextReference, override val blockchain: Blockchain) : SelectorItem
|
||||
|
||||
/**
|
||||
* Menu item with title ans subtitle
|
||||
*
|
||||
* @property title title text
|
||||
* @property blockchain blockchain
|
||||
* @property subtitle subtitle text
|
||||
*/
|
||||
data class TitleWithSubtitle(
|
||||
override val title: TextReference,
|
||||
override val blockchain: Blockchain,
|
||||
val subtitle: TextReference,
|
||||
val type: DerivationPathSelectorType = DerivationPathSelectorType.BLOCKCHAIN,
|
||||
) : SelectorItem
|
||||
}
|
||||
}
|
||||
|
||||
enum class DerivationPathSelectorType {
|
||||
DEFAULT, CUSTOM, BLOCKCHAIN
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning model of add custom token screen
|
||||
*
|
||||
* @property description warning description
|
||||
*/
|
||||
internal sealed class AddCustomTokenWarning(val description: TextReference) {
|
||||
|
||||
/** Potential scam warning */
|
||||
data object PotentialScamToken : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.custom_token_validation_error_not_found),
|
||||
)
|
||||
|
||||
/** Token already added warning */
|
||||
data object TokenAlreadyAdded : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.custom_token_validation_error_already_added),
|
||||
)
|
||||
|
||||
/** Unsupported token warning */
|
||||
data class UnsupportedToken(val networkName: String) : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.alert_manage_tokens_unsupported_message, networkName),
|
||||
)
|
||||
|
||||
data object WrongDerivationPath : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.custom_token_invalid_derivation_path),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Floating button of add custom token screen
|
||||
*
|
||||
* @property isEnabled button availability
|
||||
* @property showProgress whether circle progress indication is enabled
|
||||
* @property onClick lambda be invoked when button is been pressed
|
||||
*/
|
||||
internal data class AddCustomTokenFloatingButton(
|
||||
val isEnabled: Boolean,
|
||||
val showProgress: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.models
|
||||
|
||||
/** Custom token type */
|
||||
enum class CustomTokenType { TOKEN, BLOCKCHAIN }
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.models
|
||||
|
||||
internal enum class SupportBlockchainType {
|
||||
|
||||
SUPPORTED, UNSUPPORTED, UNABLE_TO_DETERMINE
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.routers
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
/**
|
||||
* Custom token feature router
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal interface CustomTokenRouter {
|
||||
|
||||
/** Return to last screen */
|
||||
fun popBackStack()
|
||||
|
||||
/** Open wallet (main) screen */
|
||||
fun openWalletScreen()
|
||||
|
||||
/** Open alert if solana network is unsupported
|
||||
*
|
||||
* @param blockchain blockchain to show alert
|
||||
*/
|
||||
fun openUnsupportedNetworkAlert(blockchain: Blockchain)
|
||||
|
||||
fun showGenericErrorAlertAndPopBack()
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.routers
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/** Default implementation of custom token feature router */
|
||||
internal class DefaultCustomTokenRouter : CustomTokenRouter {
|
||||
|
||||
override fun popBackStack() {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
|
||||
override fun openWalletScreen() {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
}
|
||||
|
||||
override fun openUnsupportedNetworkAlert(blockchain: Blockchain) {
|
||||
val alert = AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_warning,
|
||||
messageId = R.string.alert_manage_tokens_unsupported_curve_message,
|
||||
args = listOf(blockchain.getNetworkName()),
|
||||
)
|
||||
store.dispatchDialogShow(alert)
|
||||
}
|
||||
|
||||
override fun showGenericErrorAlertAndPopBack() {
|
||||
val alert = AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_error,
|
||||
messageId = R.string.common_unknown_error,
|
||||
onOk = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
)
|
||||
store.dispatchDialogShow(alert)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.states
|
||||
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
|
||||
|
||||
/**
|
||||
* State holder of add custom token screen
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal sealed interface AddCustomTokenStateHolder {
|
||||
|
||||
/** Lambda be invoked when system back action is been called */
|
||||
val onBackButtonClick: () -> Unit
|
||||
|
||||
/** Toolbar model */
|
||||
val toolbar: AddCustomTokensToolbar
|
||||
|
||||
/** Form model */
|
||||
val form: AddCustomTokenForm
|
||||
|
||||
/** Warnings */
|
||||
val warnings: Set<AddCustomTokenWarning>
|
||||
|
||||
/** Floating button model */
|
||||
val floatingButton: AddCustomTokenFloatingButton
|
||||
|
||||
/**
|
||||
* Util function that allow to make a copy
|
||||
*
|
||||
* @param onBackButtonClick lambda be invoked when system back action is been called
|
||||
* @param toolbar toolbar model
|
||||
* @param form form model
|
||||
* @param warnings warnings
|
||||
* @param floatingButton floating button model
|
||||
*/
|
||||
fun copySealed(
|
||||
onBackButtonClick: () -> Unit = this.onBackButtonClick,
|
||||
toolbar: AddCustomTokensToolbar = this.toolbar,
|
||||
form: AddCustomTokenForm = this.form,
|
||||
warnings: Set<AddCustomTokenWarning> = this.warnings,
|
||||
floatingButton: AddCustomTokenFloatingButton = this.floatingButton,
|
||||
): AddCustomTokenStateHolder {
|
||||
return when (this) {
|
||||
is Content -> copy(onBackButtonClick, toolbar, form, warnings, floatingButton)
|
||||
is TestContent -> copy(onBackButtonClick, toolbar, form, warnings, floatingButton)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Content state
|
||||
*
|
||||
* @property onBackButtonClick lambda be invoked when system back action is been called
|
||||
* @property toolbar toolbar model
|
||||
* @property form form model
|
||||
* @property warnings warnings
|
||||
* @property floatingButton floating button model
|
||||
*/
|
||||
data class Content(
|
||||
override val onBackButtonClick: () -> Unit,
|
||||
override val toolbar: AddCustomTokensToolbar,
|
||||
override val form: AddCustomTokenForm,
|
||||
override val warnings: Set<AddCustomTokenWarning>,
|
||||
override val floatingButton: AddCustomTokenFloatingButton,
|
||||
) : AddCustomTokenStateHolder
|
||||
|
||||
/**
|
||||
* Content state with fields for testing
|
||||
*
|
||||
* @property onBackButtonClick lambda be invoked when system back action is been called
|
||||
* @property toolbar toolbar model
|
||||
* @property form form model
|
||||
* @property warnings warnings
|
||||
* @property floatingButton floating button model
|
||||
* @property testBlock test block model
|
||||
* @property bottomSheet bottom sheet model
|
||||
*/
|
||||
data class TestContent(
|
||||
override val onBackButtonClick: () -> Unit,
|
||||
override val toolbar: AddCustomTokensToolbar,
|
||||
override val form: AddCustomTokenForm,
|
||||
override val warnings: Set<AddCustomTokenWarning>,
|
||||
override val floatingButton: AddCustomTokenFloatingButton,
|
||||
val testBlock: AddCustomTokenTestBlock,
|
||||
val bottomSheet: AddCustomTokenChooseTokenBottomSheet,
|
||||
) : AddCustomTokenStateHolder
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.FabPosition
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
|
||||
|
||||
/**
|
||||
* Add custom token content
|
||||
*
|
||||
* @param state screen state
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content, modifier: Modifier = Modifier) {
|
||||
BackHandler(onBack = state.onBackButtonClick)
|
||||
|
||||
var floatingButtonHeight by remember { mutableStateOf(0.dp) }
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
AddCustomTokenToolbar(
|
||||
title = state.toolbar.title,
|
||||
onBackButtonClick = state.toolbar.onBackButtonClick,
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
val density = LocalDensity.current
|
||||
val verticalPadding = TangemTheme.dimens.spacing32
|
||||
AddCustomTokenFloatingButton(
|
||||
model = state.floatingButton,
|
||||
modifier = Modifier.onSizeChanged {
|
||||
floatingButtonHeight = with(density) { it.height.toDp() + verticalPadding }
|
||||
},
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(paddingValues = it)
|
||||
.padding(bottom = floatingButtonHeight)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
AddCustomTokenForm(model = state.form)
|
||||
|
||||
AddCustomTokenWarnings(warnings = state.warnings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenContent() {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenContent(state = AddCustomTokenPreviewData.createContent())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.*
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object AddCustomTokenPreviewData {
|
||||
|
||||
fun createWarnings(): Set<AddCustomTokenWarning> {
|
||||
return setOf(
|
||||
AddCustomTokenWarning.PotentialScamToken,
|
||||
AddCustomTokenWarning.TokenAlreadyAdded,
|
||||
AddCustomTokenWarning.UnsupportedToken(networkName = "Solana"),
|
||||
)
|
||||
}
|
||||
|
||||
fun createDefaultForm(): AddCustomTokenForm {
|
||||
return AddCustomTokenForm(
|
||||
contractAddressInputField = AddCustomTokenInputField.ContactAddress(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
label = TextReference.Res(R.string.custom_token_contract_address_input_title),
|
||||
placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000"),
|
||||
isLoading = false,
|
||||
isError = false,
|
||||
error = null,
|
||||
),
|
||||
networkSelectorField = AddCustomTokenSelectorField.Network(
|
||||
label = TextReference.Res(R.string.custom_token_network_input_title),
|
||||
selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
|
||||
title = TextReference.Res(R.string.custom_token_network_input_not_selected),
|
||||
blockchain = Blockchain.Unknown,
|
||||
),
|
||||
items = emptyList(),
|
||||
onMenuItemClick = {},
|
||||
),
|
||||
tokenNameInputField = AddCustomTokenInputField.TokenName(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
label = TextReference.Res(R.string.custom_token_name_input_title),
|
||||
placeholder = TextReference.Res(id = R.string.custom_token_name_input_placeholder),
|
||||
isEnabled = false,
|
||||
),
|
||||
tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
label = TextReference.Res(R.string.custom_token_token_symbol_input_title_old),
|
||||
placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder),
|
||||
isEnabled = false,
|
||||
),
|
||||
decimalsInputField = AddCustomTokenInputField.Decimals(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next),
|
||||
label = TextReference.Res(R.string.custom_token_decimals_input_title),
|
||||
placeholder = TextReference.Str(value = "8"),
|
||||
isEnabled = false,
|
||||
),
|
||||
derivationPathSelectorField = AddCustomTokenSelectorField.DerivationPath(
|
||||
label = TextReference.Res(R.string.custom_token_derivation_path_input_title),
|
||||
selectedItem = AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle(
|
||||
title = TextReference.Res(R.string.custom_token_derivation_path_default),
|
||||
subtitle = TextReference.Res(R.string.custom_token_derivation_path_default),
|
||||
blockchain = Blockchain.Unknown,
|
||||
),
|
||||
items = emptyList(),
|
||||
onMenuItemClick = {},
|
||||
isEnabled = true,
|
||||
),
|
||||
derivationPathInputField = null,
|
||||
)
|
||||
}
|
||||
|
||||
fun createTestContent(): AddCustomTokenStateHolder.TestContent {
|
||||
return AddCustomTokenStateHolder.TestContent(
|
||||
onBackButtonClick = {},
|
||||
toolbar = AddCustomTokensToolbar(
|
||||
title = TextReference.Res(R.string.add_custom_token_title),
|
||||
onBackButtonClick = {},
|
||||
),
|
||||
form = createDefaultForm(),
|
||||
warnings = createWarnings(),
|
||||
floatingButton = AddCustomTokenFloatingButton(
|
||||
isEnabled = false,
|
||||
showProgress = false,
|
||||
onClick = {},
|
||||
),
|
||||
testBlock = AddCustomTokenTestBlock(
|
||||
chooseTokenButtonText = "Choose token",
|
||||
clearButtonText = "Clear address",
|
||||
resetButtonText = "Reset",
|
||||
onClearAddressButtonClick = {},
|
||||
onResetButtonClick = {},
|
||||
),
|
||||
bottomSheet = AddCustomTokenChooseTokenBottomSheet(categoriesBlocks = emptyList(), onTestTokenClick = {}),
|
||||
)
|
||||
}
|
||||
|
||||
fun createContent(): AddCustomTokenStateHolder.Content {
|
||||
return AddCustomTokenStateHolder.Content(
|
||||
onBackButtonClick = {},
|
||||
toolbar = AddCustomTokensToolbar(
|
||||
title = TextReference.Res(R.string.add_custom_token_title),
|
||||
onBackButtonClick = {},
|
||||
),
|
||||
form = createDefaultForm(),
|
||||
warnings = createWarnings(),
|
||||
floatingButton = AddCustomTokenFloatingButton(
|
||||
isEnabled = false,
|
||||
showProgress = false,
|
||||
onClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
||||
|
||||
/**
|
||||
* Add custom token screen
|
||||
*
|
||||
* @param stateHolder state holder
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder, modifier: Modifier = Modifier) {
|
||||
when (stateHolder) {
|
||||
is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(stateHolder, modifier)
|
||||
is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(stateHolder, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showSystemUi = true)
|
||||
@Preview(showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenScreen(
|
||||
@PreviewParameter(AddCustomTokenScreenProvider::class) stateHolder: AddCustomTokenStateHolder,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenScreen(stateHolder)
|
||||
}
|
||||
}
|
||||
|
||||
private class AddCustomTokenScreenProvider : CollectionPreviewParameterProvider<AddCustomTokenStateHolder>(
|
||||
collection = listOf(
|
||||
AddCustomTokenPreviewData.createContent(),
|
||||
AddCustomTokenPreviewData.createTestContent(),
|
||||
),
|
||||
)
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Add custom token content for testing
|
||||
*
|
||||
* @param state screen state
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestContent, modifier: Modifier = Modifier) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = BottomSheetState(initialValue = BottomSheetValue.Collapsed, LocalDensity.current),
|
||||
)
|
||||
|
||||
BackHandler(
|
||||
onBack = {
|
||||
onBackButtonClicked(
|
||||
coroutineScope = coroutineScope,
|
||||
bottomSheetScaffoldState = bottomSheetScaffoldState,
|
||||
defaultAction = state.onBackButtonClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
var floatingButtonHeight by remember { mutableStateOf(0.dp) }
|
||||
BottomSheetScaffold(
|
||||
modifier = modifier,
|
||||
sheetContent = {
|
||||
SheetContent(
|
||||
coroutineScope = coroutineScope,
|
||||
bottomSheetScaffoldState = bottomSheetScaffoldState,
|
||||
model = state.bottomSheet,
|
||||
)
|
||||
},
|
||||
scaffoldState = bottomSheetScaffoldState,
|
||||
topBar = {
|
||||
AddCustomTokenToolbar(
|
||||
title = state.toolbar.title,
|
||||
onBackButtonClick = {
|
||||
onBackButtonClicked(
|
||||
coroutineScope,
|
||||
bottomSheetScaffoldState,
|
||||
defaultAction = state.toolbar.onBackButtonClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
val density = LocalDensity.current
|
||||
val verticalPadding = TangemTheme.dimens.spacing32
|
||||
AddCustomTokenFloatingButton(
|
||||
model = state.floatingButton,
|
||||
modifier = Modifier.onSizeChanged {
|
||||
floatingButtonHeight = with(density) { it.height.toDp() + verticalPadding }
|
||||
},
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
sheetBackgroundColor = TangemTheme.colors.background.secondary,
|
||||
sheetPeekHeight = TangemTheme.dimens.size0,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(paddingValues = it)
|
||||
.padding(bottom = floatingButtonHeight)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
TestBlock(
|
||||
state.testBlock,
|
||||
coroutineScope,
|
||||
bottomSheetScaffoldState,
|
||||
)
|
||||
|
||||
AddCustomTokenForm(model = state.form)
|
||||
|
||||
AddCustomTokenWarnings(warnings = state.warnings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
private fun onBackButtonClicked(
|
||||
coroutineScope: CoroutineScope,
|
||||
bottomSheetScaffoldState: BottomSheetScaffoldState,
|
||||
defaultAction: () -> Unit,
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
if (bottomSheetScaffoldState.bottomSheetState.isExpanded) {
|
||||
bottomSheetScaffoldState.bottomSheetState.collapse()
|
||||
} else {
|
||||
defaultAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun SheetContent(
|
||||
model: AddCustomTokenChooseTokenBottomSheet,
|
||||
coroutineScope: CoroutineScope,
|
||||
bottomSheetScaffoldState: BottomSheetScaffoldState,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(LocalConfiguration.current.screenHeightDp.dp - TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
Hand()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
model.categoriesBlocks.forEachIndexed { index, categoryBlock ->
|
||||
key(categoryBlock) {
|
||||
Column {
|
||||
TokensList(
|
||||
title = categoryBlock.name,
|
||||
tokens = categoryBlock.items,
|
||||
onTestTokenClick = { address ->
|
||||
model.onTestTokenClick(address)
|
||||
coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.collapse() }
|
||||
},
|
||||
)
|
||||
|
||||
if (model.categoriesBlocks.lastIndex != index) {
|
||||
Divider()
|
||||
SpacerH8()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokensList(title: String, tokens: List<TestTokenItem>, onTestTokenClick: (String) -> Unit) {
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.padding(
|
||||
horizontal = TangemTheme.dimens.spacing24,
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.h3,
|
||||
)
|
||||
|
||||
tokens.forEach { token ->
|
||||
key(token) {
|
||||
PrimaryButton(
|
||||
text = token.name,
|
||||
onClick = { onTestTokenClick(token.address) },
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.padding(bottom = TangemTheme.dimens.spacing8)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun TestBlock(
|
||||
model: AddCustomTokenTestBlock,
|
||||
coroutineScope: CoroutineScope,
|
||||
bottomSheetScaffoldState: BottomSheetScaffoldState,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.padding(top = TangemTheme.dimens.spacing16),
|
||||
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
val softwareKeyboardController = LocalSoftwareKeyboardController.current
|
||||
PrimaryButton(
|
||||
text = model.chooseTokenButtonText,
|
||||
onClick = {
|
||||
softwareKeyboardController?.hide()
|
||||
coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.expand() }
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
PrimaryButton(
|
||||
text = model.clearButtonText,
|
||||
onClick = model.onClearAddressButtonClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
PrimaryButton(
|
||||
text = model.resetButtonText,
|
||||
onClick = model.onResetButtonClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenTestContent() {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenTestContent(state = AddCustomTokenPreviewData.createTestContent())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconStart
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
* Add custom token floating button. Attached above the keyboard.
|
||||
*
|
||||
* @param model button model
|
||||
* @param modifier modifier
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, modifier: Modifier = Modifier) {
|
||||
PrimaryButtonIconStart(
|
||||
modifier = modifier
|
||||
.imePadding()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.custom_token_add_token),
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
enabled = model.isEnabled,
|
||||
showProgress = model.showProgress,
|
||||
onClick = model.onClick,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenFloatingButton(
|
||||
@PreviewParameter(AddCustomTokenFloatingButtonProvider::class) model: AddCustomTokenFloatingButton,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenFloatingButton(model)
|
||||
}
|
||||
}
|
||||
|
||||
private class AddCustomTokenFloatingButtonProvider : CollectionPreviewParameterProvider<AddCustomTokenFloatingButton>(
|
||||
listOf(
|
||||
AddCustomTokenFloatingButton(isEnabled = true, showProgress = false, onClick = {}),
|
||||
AddCustomTokenFloatingButton(isEnabled = false, showProgress = false, onClick = {}),
|
||||
),
|
||||
)
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.common.compose.TangemTextFieldsDefault
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
|
||||
/**
|
||||
* Add custom token form
|
||||
*
|
||||
* @param model component model
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddCustomTokenForm(model: AddCustomTokenForm) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius8),
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
elevation = TangemTheme.dimens.elevation4,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(TangemTheme.dimens.spacing16),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
if (model.showTokenFields) InputField(model = model.contractAddressInputField)
|
||||
SelectorField(model = model.networkSelectorField)
|
||||
if (model.showTokenFields) InputField(model = model.tokenNameInputField)
|
||||
if (model.showTokenFields) InputField(model = model.tokenSymbolInputField)
|
||||
if (model.showTokenFields) InputField(model = model.decimalsInputField)
|
||||
model.derivationPathSelectorField?.let { SelectorField(model = it) }
|
||||
if (model.derivationPathInputField?.showField == true) InputField(model = model.derivationPathInputField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InputField(model: AddCustomTokenInputField) {
|
||||
Column {
|
||||
val isError = (model as? AddCustomTokenInputField.ContactAddress)?.isError ?: false
|
||||
|
||||
TextField(model, isError)
|
||||
|
||||
(model as? AddCustomTokenInputField.ContactAddress)?.error?.resolveReference()?.let {
|
||||
AnimatedVisibility(
|
||||
visible = isError,
|
||||
enter = fadeIn() + slideInVertically(),
|
||||
exit = slideOutVertically() + fadeOut(),
|
||||
) {
|
||||
Text(
|
||||
text = it,
|
||||
color = MaterialTheme.colors.error,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextField(model: AddCustomTokenInputField, isError: Boolean) {
|
||||
Box {
|
||||
val isEnabled = when (model) {
|
||||
is AddCustomTokenInputField.ContactAddress -> true
|
||||
is AddCustomTokenInputField.Decimals -> model.isEnabled
|
||||
is AddCustomTokenInputField.TokenName -> model.isEnabled
|
||||
is AddCustomTokenInputField.TokenSymbol -> model.isEnabled
|
||||
is AddCustomTokenInputField.DerivationPath -> true
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = model.value,
|
||||
onValueChange = model.onValueChange,
|
||||
keyboardOptions = model.keyboardOptions,
|
||||
label = {
|
||||
Text(
|
||||
text = model.label.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTextFieldsDefault.defaultTextFieldColors.labelColor(
|
||||
enabled = isEnabled,
|
||||
error = isError,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
).value,
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = model.placeholder.resolveReference(),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTextFieldsDefault.defaultTextFieldColors
|
||||
.placeholderColor(enabled = isEnabled)
|
||||
.value,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
enabled = isEnabled,
|
||||
isError = isError,
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = (model as? AddCustomTokenInputField.ContactAddress)?.isLoading ?: false,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing6)
|
||||
.padding(bottom = TangemTheme.dimens.spacing6),
|
||||
) {
|
||||
LinearProgressIndicator(color = TangemTheme.colors.icon.primary1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Be careful with this function – ExposedDropdownMenuBox can crash the app if it is open and user clicks system back
|
||||
* button. It was fixed in compose-material 1.6.4.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun SelectorField(model: AddCustomTokenSelectorField) {
|
||||
var isExpanded by remember { mutableStateOf(value = false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = isExpanded,
|
||||
onExpandedChange = { isExpanded = !isExpanded },
|
||||
) {
|
||||
val isEnabled = (model as? AddCustomTokenSelectorField.DerivationPath)?.isEnabled ?: true
|
||||
OutlinedTextField(
|
||||
value = when (val item = model.selectedItem) {
|
||||
is AddCustomTokenSelectorField.SelectorItem.Title -> item.title
|
||||
is AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle -> item.subtitle
|
||||
}.resolveReference(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
enabled = isEnabled,
|
||||
label = { Text(text = model.label.resolveReference()) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = isExpanded) },
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
expanded = isExpanded && isEnabled,
|
||||
onDismissRequest = { isExpanded = false },
|
||||
) {
|
||||
model.items.forEachIndexed { index, item ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
model.onMenuItemClick(index)
|
||||
isExpanded = false
|
||||
},
|
||||
) {
|
||||
Column {
|
||||
Text(text = item.title.resolveReference())
|
||||
|
||||
val subtitle = (item as? AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle)
|
||||
?.subtitle?.resolveReference()
|
||||
|
||||
if (!subtitle.isNullOrBlank()) {
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenForm(@PreviewParameter(AddCustomTokenFormProvider::class) model: AddCustomTokenForm) {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenForm(model)
|
||||
}
|
||||
}
|
||||
|
||||
private class AddCustomTokenFormProvider : CollectionPreviewParameterProvider<AddCustomTokenForm>(
|
||||
collection = listOf(
|
||||
AddCustomTokenPreviewData.createDefaultForm(),
|
||||
AddCustomTokenPreviewData.createDefaultForm().copy(derivationPathSelectorField = null),
|
||||
AddCustomTokenPreviewData.createDefaultForm().let { form ->
|
||||
form.copy(contractAddressInputField = form.contractAddressInputField.copy(isLoading = true))
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
|
||||
/**
|
||||
* Add custom token toolbar
|
||||
*
|
||||
* @param title title
|
||||
* @param onBackButtonClick lambda be invoked when BackButton is been pressed
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddCustomTokenToolbar(title: TextReference, onBackButtonClick: () -> Unit) {
|
||||
val toolbarElevation = if (isSystemInDarkTheme()) {
|
||||
TangemTheme.dimens.elevation0
|
||||
} else {
|
||||
AppBarDefaults.TopAppBarElevation
|
||||
}
|
||||
TopAppBar(
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
elevation = toolbarElevation,
|
||||
) {
|
||||
IconButton(onClick = onBackButtonClick) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_back_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing26))
|
||||
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.h3,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun Preview_AddCustomTokenToolbar() {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenToolbar(title = TextReference.Res(R.string.add_custom_token_title), onBackButtonClick = {})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
* Add custom token warnings
|
||||
*
|
||||
* @param warnings warnings descriptions set
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddCustomTokenWarnings(warnings: Set<AddCustomTokenWarning>) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
warnings.forEach { warning ->
|
||||
key(warning) { AddCustomTokenWarning(warning) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddCustomTokenWarning(warning: AddCustomTokenWarning) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
shape = TangemTheme.shapes.roundedCornersSmall2,
|
||||
backgroundColor = TangemColorPalette.Tangerine,
|
||||
contentColor = TangemColorPalette.White,
|
||||
elevation = TangemTheme.dimens.elevation4,
|
||||
) {
|
||||
// FIXME("Incorrect typography. Replace with typography from design system")
|
||||
Column(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_warning),
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.body2.copy(fontWeight = FontWeight.Bold),
|
||||
)
|
||||
Text(
|
||||
text = warning.description.resolveReference(),
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 18.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenWarnings() {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenWarnings(warnings = AddCustomTokenPreviewData.createWarnings())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.validators
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.AddressService
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.domain.tokens.error.AddCustomTokenError
|
||||
|
||||
/**
|
||||
* Validator of contract address
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object ContractAddressValidator {
|
||||
|
||||
/** Validate a [address] using [blockchain] */
|
||||
fun validate(address: String, blockchain: Blockchain): ContractAddressValidatorResult {
|
||||
return when {
|
||||
address.isEmpty() -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.FIELD_IS_EMPTY)
|
||||
validateAddress(blockchain, address) -> ContractAddressValidatorResult.Success
|
||||
else -> ContractAddressValidatorResult.Error(type = AddCustomTokenError.INVALID_CONTRACT_ADDRESS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateAddress(blockchain: Blockchain, address: String): Boolean {
|
||||
return when (blockchain) {
|
||||
Blockchain.Unknown,
|
||||
Blockchain.Binance,
|
||||
Blockchain.BinanceTestnet,
|
||||
-> SuccessAddressValidator.validate(address)
|
||||
Blockchain.Cardano -> blockchain.validateContractAddress(address)
|
||||
else -> blockchain.validateAddress(address)
|
||||
}
|
||||
}
|
||||
|
||||
private object SuccessAddressValidator : AddressService() {
|
||||
override fun makeAddress(walletPublicKey: ByteArray, curve: EllipticCurve?): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun validate(address: String): Boolean = true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.validators
|
||||
|
||||
import com.tangem.domain.tokens.error.AddCustomTokenError
|
||||
|
||||
/**
|
||||
* Result of validation contract address
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface ContractAddressValidatorResult {
|
||||
|
||||
/** Success */
|
||||
object Success : ContractAddressValidatorResult
|
||||
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* @property type type of error
|
||||
*/
|
||||
data class Error(val type: AddCustomTokenError) : ContractAddressValidatorResult
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.viewmodels
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.tap.common.analytics.events.ManageTokens
|
||||
|
||||
/** Analytics sender for tokens list screen */
|
||||
class AddCustomTokenAnalyticsSender(private val analyticsEventHandler: AnalyticsEventHandler) {
|
||||
|
||||
fun sendWhenScreenOpened() {
|
||||
analyticsEventHandler.send(ManageTokens.CustomToken.ScreenOpened)
|
||||
}
|
||||
|
||||
fun sendWhenAddTokenButtonClicked(customCurrency: CustomCurrency) {
|
||||
analyticsEventHandler.send(ManageTokens.CustomToken.TokenWasAdded(customCurrency))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -14,15 +13,6 @@ sealed class DetailsAction : Action {
|
|||
val shouldSaveUserWallets: Boolean,
|
||||
) : DetailsAction()
|
||||
|
||||
data object ScanAndSaveUserWallet : DetailsAction() {
|
||||
|
||||
data object Success : DetailsAction()
|
||||
|
||||
data class Error(val error: TextReference?) : DetailsAction()
|
||||
}
|
||||
|
||||
data object DismissError : DetailsAction()
|
||||
|
||||
sealed class AppSettings : DetailsAction() {
|
||||
data class SwitchPrivacySetting(
|
||||
val enable: Boolean,
|
||||
|
|
|
|||
|
|
@ -2,36 +2,24 @@ package com.tangem.tap.features.details.redux
|
|||
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
|
@ -41,7 +29,6 @@ import kotlinx.coroutines.launch
|
|||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
|
||||
|
||||
class DetailsMiddleware {
|
||||
private val appSettingsMiddleware = AppSettingsMiddleware()
|
||||
|
|
@ -62,7 +49,6 @@ class DetailsMiddleware {
|
|||
private fun handleAction(state: DetailsState, action: Action) {
|
||||
when (action) {
|
||||
is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, action)
|
||||
is DetailsAction.ScanAndSaveUserWallet -> scanAndSaveUserWallet()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,95 +264,4 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanAndSaveUserWallet() = scope.launch(Dispatchers.IO) {
|
||||
val cardSdkConfigRepository = store.inject(DaggerGraphState::cardSdkConfigRepository)
|
||||
|
||||
val prevUseBiometricsForAccessCode = cardSdkConfigRepository.isBiometricsRequestPolicy()
|
||||
|
||||
// Update access code policy for access code saving when a card was scanned
|
||||
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = shouldSaveAccessCodes)
|
||||
|
||||
store.inject(DaggerGraphState::scanCardProcessor).scan(
|
||||
analyticsSource = CoreAnalyticsParam.ScreensSources.Settings,
|
||||
onWalletNotCreated = {
|
||||
// No need to rollback policy, continue with the policy set before the card scan
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
|
||||
},
|
||||
disclaimerWillShow = {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
createUserWallet(scanResponse)
|
||||
.doOnSuccess {
|
||||
saveUserWalletAndPopBackToWalletScreen(
|
||||
userWallet = it,
|
||||
prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode,
|
||||
)
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to create user wallet")
|
||||
handleError(error = error, prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode)
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
Timber.e(error, "Unable to scan card")
|
||||
handleError(error = error, prevUseBiometricsForAccessCode = prevUseBiometricsForAccessCode)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse): CompletionResult<UserWallet> {
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build()
|
||||
|
||||
return if (userWallet != null) {
|
||||
CompletionResult.Success(userWallet)
|
||||
} else {
|
||||
CompletionResult.Failure(TangemSdkError.WalletIsNotCreated())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveUserWalletAndPopBackToWalletScreen(
|
||||
userWallet: UserWallet,
|
||||
prevUseBiometricsForAccessCode: Boolean,
|
||||
) {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
userWalletsListManager.save(userWallet)
|
||||
.doOnSuccess {
|
||||
store.onUserWalletSelected(userWallet)
|
||||
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
if (error is UserWalletsListError.WalletAlreadySaved) {
|
||||
userWalletsListManager.select(userWallet.walletId)
|
||||
store.onUserWalletSelected(userWallet)
|
||||
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
} else {
|
||||
Timber.e(error, "Unable to create user wallet")
|
||||
handleError(error, prevUseBiometricsForAccessCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleError(error: TangemError, prevUseBiometricsForAccessCode: Boolean) {
|
||||
val cardSdkConfigRepository = store.inject(DaggerGraphState::cardSdkConfigRepository)
|
||||
|
||||
// Rollback policy if card saving was failed
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode)
|
||||
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Error(error.toTextReference()))
|
||||
}
|
||||
|
||||
private fun TangemError.toTextReference(): TextReference? {
|
||||
if (silent) return null
|
||||
|
||||
return messageResId?.let(::resourceReference) ?: stringReference(customMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
@ -21,7 +20,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
val detailsState = state.detailsState
|
||||
return when (action) {
|
||||
is DetailsAction.PrepareScreen -> {
|
||||
handlePrepareScreen(action, state)
|
||||
handlePrepareScreen(action)
|
||||
}
|
||||
is DetailsAction.AppSettings -> {
|
||||
handlePrivacyAction(action, detailsState)
|
||||
|
|
@ -31,26 +30,12 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
selectedAppCurrency = action.currency,
|
||||
),
|
||||
)
|
||||
is DetailsAction.ScanAndSaveUserWallet -> detailsState.copy(
|
||||
isScanningInProgress = true,
|
||||
)
|
||||
is DetailsAction.ScanAndSaveUserWallet.Error -> detailsState.copy(
|
||||
isScanningInProgress = false,
|
||||
error = action.error,
|
||||
)
|
||||
is DetailsAction.ScanAndSaveUserWallet.Success -> detailsState.copy(
|
||||
isScanningInProgress = false,
|
||||
)
|
||||
is DetailsAction.DismissError -> detailsState.copy(
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppState): DetailsState {
|
||||
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsState {
|
||||
return DetailsState(
|
||||
scanResponse = action.scanResponse,
|
||||
createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
appSettingsState = AppSettingsState(
|
||||
isBiometricsAvailable = runBlocking {
|
||||
tangemSdkManager.checkCanUseBiometry()
|
||||
|
|
|
|||
|
|
@ -1,27 +1,16 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DetailsState(
|
||||
@Deprecated("Delete after onboarding refactoring")
|
||||
val scanResponse: ScanResponse? = null,
|
||||
val privacyPolicyUrl: String? = null,
|
||||
val createBackupAllowed: Boolean = false,
|
||||
val isScanningInProgress: Boolean = false,
|
||||
val error: TextReference? = null,
|
||||
val appSettingsState: AppSettingsState = AppSettingsState(),
|
||||
) : StateType
|
||||
|
||||
sealed class ResetCardDialog {
|
||||
data object StartResetDialog : ResetCardDialog()
|
||||
data object ContinueResetDialog : ResetCardDialog()
|
||||
data object InterruptedResetDialog : ResetCardDialog()
|
||||
data object CompletedResetDialog : ResetCardDialog()
|
||||
}
|
||||
|
||||
data class AppSettingsState(
|
||||
val saveWallets: Boolean = false,
|
||||
val saveAccessCodes: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
|||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -45,7 +44,6 @@ internal class AppSettingsViewModel @Inject constructor(
|
|||
private val appThemeModeRepository: AppThemeModeRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
|
||||
private val detailsFeatureToggles: DetailsFeatureToggles,
|
||||
) : ViewModel(),
|
||||
StoreSubscriber<DetailsState>,
|
||||
DefaultLifecycleObserver {
|
||||
|
|
@ -62,9 +60,7 @@ internal class AppSettingsViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
bootstrapAppCurrencyUpdates()
|
||||
if (detailsFeatureToggles.isRedesignEnabled) {
|
||||
bootstrapBiometricsUpdates()
|
||||
}
|
||||
|
||||
subscribeToStoreChanges()
|
||||
sendItemsAnalytics()
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState> {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var walletsRepository: WalletsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var userWalletsListManager: UserWalletsListManager
|
||||
|
||||
private lateinit var detailsViewModel: DetailsViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
detailsViewModel = DetailsViewModel(
|
||||
store = store,
|
||||
walletsRepository = walletsRepository,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
)
|
||||
Analytics.send(Settings.ScreenOpened())
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
DetailsScreen(
|
||||
modifier = modifier,
|
||||
state = detailsViewModel.detailsScreenState.value,
|
||||
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: DetailsState) {
|
||||
if (activity == null || view == null) return
|
||||
detailsViewModel.detailsScreenState.value = detailsViewModel.updateState(state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,288 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.details
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.ui.common.ScreenTitle
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun DetailsScreen(state: DetailsScreenState, onBackClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
SettingsScreensScaffold(
|
||||
modifier = modifier,
|
||||
snackbarHostState = snackbarHostState,
|
||||
content = { Content(state = state) },
|
||||
onBackClick = onBackClick,
|
||||
)
|
||||
|
||||
ShowSnackbarIfNeeded(
|
||||
snackbarHostState = snackbarHostState,
|
||||
messageEvent = state.showSnackbar,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: DetailsScreenState, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
ScreenTitle(titleRes = R.string.details_title)
|
||||
SpacerH(height = TangemTheme.dimens.spacing36)
|
||||
SettingsItems(
|
||||
items = state.elements,
|
||||
)
|
||||
SpacerHMax()
|
||||
TangemSocialAccounts(
|
||||
links = state.tangemLinks,
|
||||
onSocialNetworkClick = state.onSocialNetworkClick,
|
||||
)
|
||||
SpacerH(height = TangemTheme.dimens.spacing12)
|
||||
TangemAppVersion(
|
||||
appNameRes = state.appNameRes,
|
||||
version = state.tangemVersion,
|
||||
)
|
||||
SpacerH(height = TangemTheme.dimens.spacing16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsItems(items: List<SettingsItem>) {
|
||||
items.forEach { item ->
|
||||
if (item.isLarge) {
|
||||
LargeDetailsItem(item)
|
||||
} else {
|
||||
DetailsItem(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LargeDetailsItem(item: SettingsItem) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = item.onClick)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing20)
|
||||
.heightIn(min = TangemTheme.dimens.size84)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing20),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (item.showProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
color = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = item.iconResId),
|
||||
contentDescription = item.title.resolveReference(),
|
||||
tint = TangemColorPalette.Azure,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.heightIn(min = TangemTheme.dimens.size56),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
space = TangemTheme.dimens.spacing4,
|
||||
alignment = Alignment.CenterVertically,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = item.title.resolveReference(),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
if (item.subtitle != null) {
|
||||
Text(
|
||||
text = item.subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DetailsItem(item: SettingsItem) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(enabled = !item.showProgress, onClick = item.onClick)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing20)
|
||||
.heightIn(min = TangemTheme.dimens.size56)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing20),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (item.showProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
color = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = item.iconResId),
|
||||
contentDescription = item.title.resolveReference(),
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.SpaceAround,
|
||||
) {
|
||||
Text(
|
||||
text = item.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
if (item.subtitle != null) {
|
||||
Text(
|
||||
text = item.subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemSocialAccounts(links: List<SocialNetworkLink>, onSocialNetworkClick: (SocialNetworkLink) -> Unit) {
|
||||
LazyRow(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
items(links) {
|
||||
val onClick = remember(it) {
|
||||
{ onSocialNetworkClick(it) }
|
||||
}
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing4)
|
||||
.size(TangemTheme.dimens.size32),
|
||||
onClick = onClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = it.network.iconRes),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShowSnackbarIfNeeded(snackbarHostState: SnackbarHostState, messageEvent: StateEvent<TextReference>) {
|
||||
var message: TextReference? by remember { mutableStateOf(value = null) }
|
||||
val resolvedMessage by rememberUpdatedState(newValue = message?.resolveReference())
|
||||
|
||||
LaunchedEffect(resolvedMessage) {
|
||||
resolvedMessage?.let {
|
||||
snackbarHostState.showSnackbar(it)
|
||||
}
|
||||
}
|
||||
|
||||
EventEffect(messageEvent) {
|
||||
message = it
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemAppVersion(appNameRes: Int, version: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
text = "${stringResource(id = appNameRes)} $version",
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 900)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 900, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun DetailsScreenPreview(@PreviewParameter(DetailsScreenStateProvider::class) param: DetailsScreenState) {
|
||||
TangemThemePreview {
|
||||
DetailsScreen(param, onBackClick = {})
|
||||
}
|
||||
}
|
||||
|
||||
private class DetailsScreenStateProvider : CollectionPreviewParameterProvider<DetailsScreenState>(
|
||||
collection = buildList {
|
||||
DetailsScreenState(
|
||||
elements = buildList {
|
||||
SettingsItem.WalletConnect({}).let(::add)
|
||||
SettingsItem.AddWallet(showProgress = false, {}).let(::add)
|
||||
SettingsItem.LinkMoreCards({}).let(::add)
|
||||
SettingsItem.CardSettings({}).let(::add)
|
||||
SettingsItem.AppSettings({}).let(::add)
|
||||
// removed chat in task [REDACTED_TASK_KEY]
|
||||
// SettingsItem.Chat({}).let(::add)
|
||||
SettingsItem.SendFeedback({}).let(::add)
|
||||
SettingsItem.ReferralProgram({}).let(::add)
|
||||
SettingsItem.TermsOfService({}).let(::add)
|
||||
}.toImmutableList(),
|
||||
tangemLinks = TangemSocialAccounts.accountsEn,
|
||||
tangemVersion = "Tangem 2.14.12 (343)",
|
||||
showSnackbar = consumedEvent(),
|
||||
onSocialNetworkClick = {},
|
||||
).let(::add)
|
||||
|
||||
DetailsScreenState(
|
||||
elements = buildList {
|
||||
SettingsItem.WalletConnect({}).let(::add)
|
||||
SettingsItem.AddWallet(showProgress = true, {}).let(::add)
|
||||
SettingsItem.CardSettings({}).let(::add)
|
||||
SettingsItem.AppSettings({}).let(::add)
|
||||
// removed chat in task [REDACTED_TASK_KEY]
|
||||
// SettingsItem.Chat({}).let(::add)
|
||||
SettingsItem.SendFeedback({}).let(::add)
|
||||
SettingsItem.TermsOfService({}).let(::add)
|
||||
}.toImmutableList(),
|
||||
tangemLinks = TangemSocialAccounts.accountsRu,
|
||||
tangemVersion = "Tangem 2.14.12 (343)",
|
||||
showSnackbar = consumedEvent(),
|
||||
onSocialNetworkClick = {},
|
||||
).let(::add)
|
||||
},
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.details
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Immutable
|
||||
internal data class DetailsScreenState(
|
||||
val elements: ImmutableList<SettingsItem>,
|
||||
val tangemLinks: ImmutableList<SocialNetworkLink>,
|
||||
val tangemVersion: String,
|
||||
val showSnackbar: StateEvent<TextReference>,
|
||||
val onSocialNetworkClick: (SocialNetworkLink) -> Unit,
|
||||
) {
|
||||
val appNameRes: Int = R.string.tangem_app_name
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal sealed class SettingsItem(
|
||||
@DrawableRes val iconResId: Int,
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference? = null,
|
||||
val isLarge: Boolean = false,
|
||||
) {
|
||||
|
||||
abstract val onClick: () -> Unit
|
||||
|
||||
open val showProgress: Boolean = false
|
||||
|
||||
data class WalletConnect(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_wallet_connect_24,
|
||||
title = resourceReference(R.string.wallet_connect_title),
|
||||
subtitle = resourceReference(R.string.wallet_connect_subtitle),
|
||||
isLarge = true,
|
||||
)
|
||||
|
||||
data class AddWallet(
|
||||
override val showProgress: Boolean,
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
title = resourceReference(R.string.user_wallet_list_add_button),
|
||||
)
|
||||
|
||||
data class ScanWallet(
|
||||
override val showProgress: Boolean,
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
title = resourceReference(R.string.scan_card_settings_button),
|
||||
)
|
||||
|
||||
data class LinkMoreCards(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_more_cards_24,
|
||||
title = resourceReference(R.string.details_row_title_create_backup),
|
||||
)
|
||||
|
||||
data class CardSettings(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_card_settings_24,
|
||||
title = resourceReference(R.string.card_settings_title),
|
||||
)
|
||||
|
||||
data class AppSettings(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_settings,
|
||||
title = resourceReference(R.string.app_settings_title),
|
||||
)
|
||||
|
||||
data class Chat(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_chat_24,
|
||||
title = resourceReference(R.string.details_chat),
|
||||
)
|
||||
|
||||
data class SendFeedback(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_comment_24,
|
||||
title = resourceReference(R.string.details_row_title_contact_to_support),
|
||||
)
|
||||
|
||||
data class ReferralProgram(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_add_friends_24,
|
||||
title = resourceReference(R.string.details_referral_title),
|
||||
)
|
||||
|
||||
data class TermsOfService(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_text_24,
|
||||
title = resourceReference(R.string.disclaimer_title),
|
||||
)
|
||||
|
||||
data class TesterMenu(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_alert_24,
|
||||
title = resourceReference(R.string.tester_menu),
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal data class SocialNetworkLink(
|
||||
val network: SocialNetwork,
|
||||
val url: String,
|
||||
)
|
||||
|
||||
internal sealed class EventError {
|
||||
object Empty : EventError()
|
||||
}
|
||||
|
||||
sealed class SocialNetwork(val id: String, val iconRes: Int) {
|
||||
object Twitter : SocialNetwork("Twitter", R.drawable.ic_twitter_24)
|
||||
object Telegram : SocialNetwork("Telegram", R.drawable.ic_telegram_24)
|
||||
object Discord : SocialNetwork("Discord", R.drawable.ic_discord_24)
|
||||
object Reddit : SocialNetwork("Reddit", R.drawable.ic_reddit_24)
|
||||
object Instagram : SocialNetwork("Instagram", R.drawable.ic_instagram_24)
|
||||
object GitHub : SocialNetwork("GitHub", R.drawable.ic_github_24)
|
||||
object Facebook : SocialNetwork("Facebook", R.drawable.ic_facebook_24)
|
||||
object LinkedIn : SocialNetwork("LinkedIn", R.drawable.ic_linkedin_24)
|
||||
object YouTube : SocialNetwork("YouTube", R.drawable.ic_youtube_24)
|
||||
}
|
||||
|
||||
internal object TangemSocialAccounts {
|
||||
val accountsEn: ImmutableList<SocialNetworkLink> = persistentListOf(
|
||||
SocialNetworkLink(SocialNetwork.Twitter, "https://x.com/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/tangem_chat"),
|
||||
SocialNetworkLink(SocialNetwork.Discord, "https://discord.gg/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Reddit, "https://www.reddit.com/r/Tangem/"),
|
||||
SocialNetworkLink(SocialNetwork.Instagram, "https://www.instagram.com/tangemwallet"),
|
||||
SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Facebook, "https://www.facebook.com/tangemwallet"),
|
||||
SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.YouTube, "https://youtube.com/@tangem_official"),
|
||||
)
|
||||
val accountsRu: ImmutableList<SocialNetworkLink> = persistentListOf(
|
||||
SocialNetworkLink(SocialNetwork.Twitter, "https://x.com/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/tangem_chat_ru"),
|
||||
SocialNetworkLink(SocialNetwork.Discord, "https://discord.gg/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Reddit, "https://www.reddit.com/r/Tangem/"),
|
||||
SocialNetworkLink(SocialNetwork.Instagram, "https://www.instagram.com/tangemwallet"),
|
||||
SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Facebook, "https://www.facebook.com/tangemwallet"),
|
||||
SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.YouTube, "https://youtube.com/@tangem_official"),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.details
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DetailsViewModel(
|
||||
private val store: Store<AppState>,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
) {
|
||||
|
||||
var detailsScreenState: MutableState<DetailsScreenState> = mutableStateOf(updateState(store.state.detailsState))
|
||||
private set
|
||||
|
||||
init {
|
||||
bootstrapScreenState()
|
||||
}
|
||||
|
||||
fun updateState(state: DetailsState): DetailsScreenState {
|
||||
return DetailsScreenState(
|
||||
elements = createSettingsItems(state),
|
||||
tangemLinks = getSocialLinks(),
|
||||
tangemVersion = getTangemAppVersion(),
|
||||
showSnackbar = triggerErrorSnackbarIfNeeded(state.error),
|
||||
onSocialNetworkClick = ::handleSocialNetworkClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSettingsItems(state: DetailsState): ImmutableList<SettingsItem> {
|
||||
val scanResponse = state.scanResponse ?: return persistentListOf()
|
||||
val cardTypesResolver = scanResponse.cardTypesResolver
|
||||
|
||||
return buildList {
|
||||
SettingsItem.WalletConnect(::navigateToWalletConnect)
|
||||
.takeIf { cardTypesResolver.isMultiwalletAllowed() }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.AddWallet(showProgress = state.isScanningInProgress, ::scanAndSaveUserWallet)
|
||||
.takeIf { state.appSettingsState.saveWallets }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.ScanWallet(showProgress = state.isScanningInProgress, ::scanAndSaveUserWallet)
|
||||
.takeUnless { state.appSettingsState.saveWallets }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.LinkMoreCards(::linkMoreCards)
|
||||
.takeIf { state.createBackupAllowed }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.CardSettings(::navigateToCardSettings)
|
||||
.let(::add)
|
||||
|
||||
SettingsItem.AppSettings(::navigateToAppSettings)
|
||||
.let(::add)
|
||||
|
||||
SettingsItem.SendFeedback(::sendFeedback)
|
||||
.let(::add)
|
||||
|
||||
SettingsItem.ReferralProgram(::navigateToReferralProgram)
|
||||
.takeIf { cardTypesResolver.isTangemWallet() }
|
||||
?.let(::add)
|
||||
|
||||
SettingsItem.TermsOfService(::navigateToToS)
|
||||
.let(::add)
|
||||
|
||||
SettingsItem.TesterMenu(::navigateToTesterMenu)
|
||||
.takeIf { BuildConfig.TESTER_MENU_ENABLED }
|
||||
?.let(::add)
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
private fun triggerErrorSnackbarIfNeeded(text: TextReference?): StateEvent<TextReference> {
|
||||
return if (text == null) {
|
||||
consumedEvent()
|
||||
} else {
|
||||
triggeredEvent(text) {
|
||||
store.dispatch(DetailsAction.DismissError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTangemAppVersion(): String {
|
||||
val versionCode: Int = BuildConfig.VERSION_CODE
|
||||
val versionName: String = BuildConfig.VERSION_NAME
|
||||
return "$versionName ($versionCode)"
|
||||
}
|
||||
|
||||
private fun navigateToTesterMenu() {
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.TesterMenu)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToToS() {
|
||||
store.dispatchOnMain(DisclaimerAction.Show(DisclaimerSource.Details))
|
||||
}
|
||||
|
||||
private fun navigateToReferralProgram() {
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync
|
||||
?: error("Selected wallet must be not null")
|
||||
|
||||
store.dispatchNavigationAction { push(AppRoute.ReferralProgram(userWallet.walletId)) }
|
||||
}
|
||||
|
||||
private fun sendFeedback() {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Settings))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = FeedbackEmail(),
|
||||
scanResponse = userWalletsListManager.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateToAppSettings() {
|
||||
Analytics.send(Settings.ButtonAppSettings())
|
||||
store.dispatchNavigationAction { push(AppRoute.AppSettings) }
|
||||
}
|
||||
|
||||
private fun navigateToCardSettings() {
|
||||
val userWalletId = userWalletsListManager.selectedUserWalletSync?.walletId
|
||||
?: error("UserWalletId must be not null")
|
||||
Analytics.send(Settings.ButtonCardSettings())
|
||||
store.dispatchNavigationAction { push(AppRoute.CardSettings(userWalletId)) }
|
||||
}
|
||||
|
||||
private fun linkMoreCards() {
|
||||
Analytics.send(Settings.ButtonCreateBackup())
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to backup wallet, no user wallet selected")
|
||||
return
|
||||
}
|
||||
val scanResponse = selectedUserWallet.scanResponse
|
||||
Analytics.addContext(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
|
||||
}
|
||||
|
||||
private fun scanAndSaveUserWallet() {
|
||||
Analytics.send(Settings.ScanNewCard)
|
||||
store.dispatchOnMain(DetailsAction.ScanAndSaveUserWallet)
|
||||
}
|
||||
|
||||
private fun navigateToWalletConnect() {
|
||||
Analytics.send(Settings.ButtonWalletConnect())
|
||||
store.dispatchNavigationAction { push(AppRoute.WalletConnectSessions) }
|
||||
}
|
||||
|
||||
private fun handleSocialNetworkClick(link: SocialNetworkLink) {
|
||||
Analytics.send(Settings.ButtonSocialNetwork(link.network))
|
||||
store.dispatchOpenUrl(link.url)
|
||||
}
|
||||
|
||||
private fun getSocialLinks(): ImmutableList<SocialNetworkLink> {
|
||||
val locale = LocaleRegionProvider().getRegion()
|
||||
return if (locale.lowercase() == RUSSIA_COUNTRY_CODE) {
|
||||
TangemSocialAccounts.accountsRu
|
||||
} else {
|
||||
TangemSocialAccounts.accountsEn
|
||||
}
|
||||
}
|
||||
|
||||
private fun bootstrapScreenState() {
|
||||
userWalletsListManager.selectedUserWallet
|
||||
.distinctUntilChanged()
|
||||
.onEach { selectedUserWallet ->
|
||||
store.dispatchWithMain(
|
||||
DetailsAction.PrepareScreen(
|
||||
scanResponse = selectedUserWallet.scanResponse,
|
||||
shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||
),
|
||||
)
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard
|
||||
|
||||
internal sealed class ResetCardDialog {
|
||||
data object StartResetDialog : ResetCardDialog()
|
||||
data object ContinueResetDialog : ResetCardDialog()
|
||||
data object InterruptedResetDialog : ResetCardDialog()
|
||||
data object CompletedResetDialog : ResetCardDialog()
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
|||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.features.details.redux.ResetCardDialog
|
||||
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
|
||||
import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription
|
||||
import com.tangem.tap.store
|
||||
|
|
|
|||
|
|
@ -24,12 +24,6 @@ abstract class BaseDisclaimer(
|
|||
override suspend fun isAccepted(): Boolean = dataProvider.isAccepted()
|
||||
}
|
||||
|
||||
class DummyDisclaimer : Disclaimer {
|
||||
override fun getUri(): Uri = Uri.parse("https://tangem.com/tangem_tos.html")
|
||||
override suspend fun accept() {}
|
||||
override suspend fun isAccepted(): Boolean = false
|
||||
}
|
||||
|
||||
class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) {
|
||||
override fun getUri(): Uri = Uri.parse("$baseUrl/tangem_tos.html")
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class DisclaimerAction : Action {
|
||||
|
||||
data class SetDisclaimer(val disclaimer: Disclaimer) : DisclaimerAction()
|
||||
|
||||
data class Show(
|
||||
val from: DisclaimerSource,
|
||||
val callback: DisclaimerCallback? = null,
|
||||
) : DisclaimerAction()
|
||||
|
||||
object AcceptDisclaimer : DisclaimerAction()
|
||||
object OnBackPressed : DisclaimerAction()
|
||||
|
||||
data class OnProgressStateChanged(val state: ProgressState?) : DisclaimerAction()
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class DisclaimerMiddleware {
|
||||
val disclaimerMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
state()?.let { handleDisclaimerMiddleware(action, it) }
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDisclaimerMiddleware(action: Action, appState: AppState) {
|
||||
val state = appState.disclaimerState
|
||||
|
||||
when (action) {
|
||||
is DisclaimerAction.Show -> {
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.Disclaimer(isTosAccepted = action.from == DisclaimerSource.Details))
|
||||
}
|
||||
}
|
||||
is DisclaimerAction.AcceptDisclaimer -> {
|
||||
mainScope.launch {
|
||||
state.disclaimer.accept()
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
state.callback?.onAccept?.invoke()
|
||||
}
|
||||
}
|
||||
is DisclaimerAction.OnBackPressed -> {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
state.callback?.onDismiss?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
object DisclaimerReducer {
|
||||
fun reduce(action: Action, state: AppState): DisclaimerState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, state: AppState): DisclaimerState {
|
||||
if (action !is DisclaimerAction) return state.disclaimerState
|
||||
|
||||
val disclaimerState = state.disclaimerState
|
||||
|
||||
return when (action) {
|
||||
is DisclaimerAction.SetDisclaimer -> disclaimerState.copy(
|
||||
disclaimer = action.disclaimer,
|
||||
)
|
||||
is DisclaimerAction.Show -> disclaimerState.copy(
|
||||
showedFrom = action.from,
|
||||
callback = action.callback,
|
||||
)
|
||||
is DisclaimerAction.AcceptDisclaimer, is DisclaimerAction.OnBackPressed -> disclaimerState.copy(
|
||||
callback = null,
|
||||
progressState = null,
|
||||
)
|
||||
is DisclaimerAction.OnProgressStateChanged -> disclaimerState.copy(
|
||||
progressState = action.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
enum class DisclaimerSource {
|
||||
Home, Details
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue