Updated on 2026-08-14
This commit is contained in:
commit
6c6112283c
433 changed files with 6045 additions and 6454 deletions
|
|
@ -1,13 +0,0 @@
|
|||
[*.{kt,kts}]
|
||||
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
|
||||
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
|
||||
ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**
|
||||
|
||||
max_line_length = 120
|
||||
13
.idea/codeStyles/Project.xml
generated
13
.idea/codeStyles/Project.xml
generated
|
|
@ -2,6 +2,10 @@
|
|||
<code_scheme name="Project" version="173">
|
||||
<option name="RIGHT_MARGIN" value="120" />
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="5" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="3" />
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
|
|
@ -9,6 +13,9 @@
|
|||
<package name="io.ktor" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="LINE_BREAK_AFTER_MULTILINE_WHEN_ENTRY" value="false" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="5" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="3" />
|
||||
<option name="ALLOW_TRAILING_COMMA" value="true" />
|
||||
<option name="BLANK_LINES_BEFORE_DECLARATION_WITH_COMMENT_OR_ANNOTATION_ON_SEPARATE_LINE" value="0" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
|
|
@ -17,6 +24,9 @@
|
|||
<option name="SPACES_AROUND_KEY_VALUE_DELIMITER" value="true" />
|
||||
<option name="KEEP_BLANK_LINES" value="true" />
|
||||
</Properties>
|
||||
<editorconfig>
|
||||
<option name="ENABLED" value="false" />
|
||||
</editorconfig>
|
||||
<codeStyleSettings language="CMake">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
|
|
@ -176,9 +186,6 @@
|
|||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="5" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="5" />
|
||||
<option name="ASSIGNMENT_WRAP" value="5" />
|
||||
<option name="PARAMETER_ANNOTATION_WRAP" value="5" />
|
||||
<option name="VARIABLE_ANNOTATION_WRAP" value="5" />
|
||||
<option name="ENUM_CONSTANTS_WRAP" value="5" />
|
||||
|
|
|
|||
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
|
|
@ -17,6 +17,7 @@
|
|||
</inspection_tool>
|
||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ dependencies {
|
|||
implementation(files("libs/walletconnect-1.5.6.aar"))
|
||||
implementation(project(":domain:legacy"))
|
||||
implementation(project(":domain:models"))
|
||||
implementation(project(":domain:core"))
|
||||
implementation(project(":domain:card"))
|
||||
implementation(project(":common"))
|
||||
implementation(project(":core:analytics"))
|
||||
implementation(project(":core:featuretoggles"))
|
||||
|
|
@ -22,6 +24,7 @@ dependencies {
|
|||
implementation(project(":core:utils"))
|
||||
implementation(project(":libs:crypto"))
|
||||
implementation(project(":libs:auth"))
|
||||
implementation(project(":data:source:preferences"))
|
||||
|
||||
/** Features */
|
||||
implementation(project(":features:onboarding"))
|
||||
|
|
|
|||
|
|
@ -484,6 +484,17 @@
|
|||
"networkId": "ravencoin/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cosmos",
|
||||
"symbol": "ATOM",
|
||||
"name": "Cosmos Hub",
|
||||
"networks":
|
||||
[
|
||||
{
|
||||
"networkId": "cosmos/test"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.blockchain.common.WalletManagerFactory
|
|||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
|
|
@ -50,10 +51,8 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
|||
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -66,7 +65,7 @@ lateinit var store: Store<AppState>
|
|||
|
||||
lateinit var foregroundActivityObserver: ForegroundActivityObserver
|
||||
lateinit var activityResultCaller: ActivityResultCaller
|
||||
lateinit var preferencesStorage: PreferencesStorage
|
||||
lateinit var preferencesStorage: PreferencesDataSource
|
||||
lateinit var walletConnectRepository: WalletConnectRepository
|
||||
lateinit var shopService: TangemShopService
|
||||
lateinit var userTokensRepository: UserTokensRepository
|
||||
|
|
@ -129,10 +128,10 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
lateinit var networkConnectionManager: NetworkConnectionManager
|
||||
|
||||
@Inject
|
||||
lateinit var tokensListFeatureToggles: TokensListFeatureToggles
|
||||
lateinit var customTokenFeatureToggles: CustomTokenFeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var customTokenFeatureToggles: CustomTokenFeatureToggles
|
||||
lateinit var preferencesDataSource: PreferencesDataSource
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
|
@ -142,7 +141,13 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
appReducer(action, state, appStateHolder)
|
||||
},
|
||||
middleware = AppState.getMiddleware(),
|
||||
state = AppState(),
|
||||
state = AppState(
|
||||
daggerGraphState = DaggerGraphState(
|
||||
assetReader = assetReader,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
customTokenFeatureToggles = customTokenFeatureToggles,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
|
@ -154,7 +159,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
|
||||
|
||||
DomainLayer.init()
|
||||
preferencesStorage = PreferencesStorage(this)
|
||||
preferencesStorage = preferencesDataSource
|
||||
walletConnectRepository = WalletConnectRepository(this)
|
||||
|
||||
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
|
||||
|
|
@ -178,15 +183,6 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
appStateHolder.userTokensRepository = userTokensRepository
|
||||
appStateHolder.walletStoresManager = walletStoresManager
|
||||
|
||||
store.dispatch(
|
||||
action = DaggerGraphAction.SetApplicationDependencies(
|
||||
assetReader = assetReader,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
tokensListFeatureToggles = tokensListFeatureToggles,
|
||||
customTokenFeatureToggles = customTokenFeatureToggles,
|
||||
),
|
||||
)
|
||||
|
||||
scope.launch {
|
||||
featureTogglesManager.init()
|
||||
}
|
||||
|
|
@ -248,7 +244,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
private fun initFeedbackManager(
|
||||
context: Context,
|
||||
preferencesStorage: PreferencesStorage,
|
||||
preferencesStorage: PreferencesDataSource,
|
||||
foregroundActivityObserver: ForegroundActivityObserver,
|
||||
store: Store<AppState>,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -7,32 +7,15 @@ import com.tangem.tap.common.redux.global.GlobalState
|
|||
import com.tangem.tap.common.ui.SimpleAlertDialog
|
||||
import com.tangem.tap.common.ui.SimpleCancelableAlertDialog
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.ApproveWcSessionDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.BnbTransactionDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.ChooseNetworkDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.ClipboardOrScanQrDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
|
||||
import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.dialog.TwinningProcessNotCompletedDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.InterruptOnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.NoFundsForActivationDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.PutVisaCardDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.RegistrationErrorDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBackupCardsDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.*
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendBottomSheetDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.ChooseTradeActionBottomSheetDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.RussianCardholdersWarningBottomSheetDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.SimpleOkDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.*
|
||||
import com.tangem.tap.features.wallet.ui.wallet.CurrencySelectionDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -131,6 +114,10 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context)
|
||||
is BackupDialog.ResetBackupCard -> ResetBackupCardDialog.create(
|
||||
context = context,
|
||||
cardId = state.dialog.cardId,
|
||||
)
|
||||
is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context)
|
||||
is SaltPayDialog.Activation.PutVisaCard -> PutVisaCardDialog.create(context)
|
||||
is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog)
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ private class ThrowableErrorConverter : Converter<Throwable, Map<String, String>
|
|||
override fun convert(value: Throwable): Map<String, String> {
|
||||
val unknown = "unknown"
|
||||
return mapOf(
|
||||
AnalyticsParam.ErrorKey to AnalyticsParam.Error.App.value,
|
||||
AnalyticsParam.ErrorDescription to (value.message ?: value.cause?.message ?: unknown),
|
||||
AnalyticsParam.ERROR_KEY to AnalyticsParam.Error.App.value,
|
||||
AnalyticsParam.ERROR_DESCRIPTION to (value.message ?: value.cause?.message ?: unknown),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -43,9 +43,9 @@ class CardSdkErrorConverter : Converter<TangemSdkError, Map<String, String>> {
|
|||
if (value is TangemSdkError.UserCancelled) return emptyMap()
|
||||
|
||||
return mapOf(
|
||||
AnalyticsParam.ErrorKey to AnalyticsParam.Error.CardSdk.value,
|
||||
AnalyticsParam.ErrorCode to value.code.toString(),
|
||||
AnalyticsParam.ErrorDescription to value.toString(),
|
||||
AnalyticsParam.ERROR_KEY to AnalyticsParam.Error.CardSdk.value,
|
||||
AnalyticsParam.ERROR_CODE to value.code.toString(),
|
||||
AnalyticsParam.ERROR_DESCRIPTION to value.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -62,9 +62,9 @@ private class BlockchainSdkErrorConverter(
|
|||
}
|
||||
|
||||
return mapOf(
|
||||
AnalyticsParam.ErrorKey to AnalyticsParam.Error.BlockchainSdk.value,
|
||||
AnalyticsParam.ErrorCode to value.code.toString(),
|
||||
AnalyticsParam.ErrorDescription to "${value.javaClass.simpleName}: ${value.customMessage}",
|
||||
AnalyticsParam.ERROR_KEY to AnalyticsParam.Error.BlockchainSdk.value,
|
||||
AnalyticsParam.ERROR_CODE to value.code.toString(),
|
||||
AnalyticsParam.ERROR_DESCRIPTION to "${value.javaClass.simpleName}: ${value.customMessage}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -93,15 +93,22 @@ sealed class AnalyticsParam {
|
|||
object Sell : TxSentFrom("Sell")
|
||||
}
|
||||
|
||||
sealed class WalletCreationType(val value: String) {
|
||||
object PrivateKey : WalletCreationType("Private key")
|
||||
object NewSeed : WalletCreationType("New seed")
|
||||
object SeedImport : WalletCreationType("Seed import")
|
||||
}
|
||||
|
||||
companion object Key {
|
||||
const val Source = "Source"
|
||||
const val Balance = "Balance"
|
||||
const val Batch = "Batch"
|
||||
const val ProductType = "Product Type"
|
||||
const val Firmware = "Firmware"
|
||||
const val Currency = "Currency"
|
||||
const val ErrorDescription = "Error Description"
|
||||
const val ErrorCode = "Error Code"
|
||||
const val ErrorKey = "Error Key"
|
||||
const val SOURCE = "Source"
|
||||
const val BALANCE = "Balance"
|
||||
const val BATCH = "Batch"
|
||||
const val PRODUCT_TYPE = "Product Type"
|
||||
const val FIRMWARE = "Firmware"
|
||||
const val CURRENCY = "Currency"
|
||||
const val ERROR_DESCRIPTION = "Error Description"
|
||||
const val ERROR_CODE = "Error Code"
|
||||
const val ERROR_KEY = "Error Key"
|
||||
const val CREATION_TYPE = "Creation type"
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ sealed class Basic(
|
|||
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState) : Basic(
|
||||
event = "Balance Loaded",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Balance to balance.value,
|
||||
AnalyticsParam.BALANCE to balance.value,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ sealed class Basic(
|
|||
) : Basic(
|
||||
event = "Card Was Scanned",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Source to source.value,
|
||||
AnalyticsParam.SOURCE to source.value,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -34,8 +34,8 @@ sealed class Basic(
|
|||
) : Basic(
|
||||
event = "Signed in",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Currency to currency.value,
|
||||
AnalyticsParam.Batch to batch,
|
||||
AnalyticsParam.CURRENCY to currency.value,
|
||||
AnalyticsParam.BATCH to batch,
|
||||
"Sign in type" to signInType.name,
|
||||
),
|
||||
) {
|
||||
|
|
@ -46,12 +46,12 @@ sealed class Basic(
|
|||
|
||||
class ToppedUp(currency: AnalyticsParam.CardCurrency) : Basic(
|
||||
event = "Topped up",
|
||||
params = mapOf(AnalyticsParam.Currency to currency.value),
|
||||
params = mapOf(AnalyticsParam.CURRENCY to currency.value),
|
||||
)
|
||||
|
||||
class TransactionSent(sentFrom: AnalyticsParam.TxSentFrom) : Basic(
|
||||
event = "Transaction sent",
|
||||
params = mapOf(AnalyticsParam.Source to sentFrom.value),
|
||||
params = mapOf(AnalyticsParam.SOURCE to sentFrom.value),
|
||||
)
|
||||
|
||||
class ScanError(error: Throwable) : Basic(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,12 @@ sealed class Onboarding(
|
|||
|
||||
class ScreenOpened : CreateWallet("Create Wallet Screen Opened")
|
||||
class ButtonCreateWallet : CreateWallet("Button - Create Wallet")
|
||||
class WalletCreatedSuccessfully : CreateWallet("Wallet Created Successfully")
|
||||
class WalletCreatedSuccessfully(
|
||||
creationType: AnalyticsParam.WalletCreationType = AnalyticsParam.WalletCreationType.PrivateKey,
|
||||
) : CreateWallet(
|
||||
event = "Wallet Created Successfully",
|
||||
params = mapOf(AnalyticsParam.CREATION_TYPE to creationType.value),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Backup(
|
||||
|
|
@ -51,7 +56,7 @@ sealed class Onboarding(
|
|||
|
||||
class ButtonBuyCrypto(currency: AnalyticsParam.CurrencyType) : Topup(
|
||||
event = "Button - Buy Crypto",
|
||||
params = mapOf(AnalyticsParam.Currency to currency.value),
|
||||
params = mapOf(AnalyticsParam.CURRENCY to currency.value),
|
||||
)
|
||||
|
||||
class ButtonShowWalletAddress : Topup("Button - Show the Wallet Address")
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.tangem.tap.common.analytics.paramsInterceptor
|
|||
|
||||
import com.tangem.core.analytics.AnalyticsEvent
|
||||
import com.tangem.core.analytics.api.ParamsInterceptor
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
|
|
@ -31,12 +31,12 @@ class CardContextInterceptor(
|
|||
scanResponse ?: return
|
||||
|
||||
val card = scanResponse.card
|
||||
params[AnalyticsParam.Batch] = card.batchId
|
||||
params[AnalyticsParam.ProductType] = getProductType(scanResponse)
|
||||
params[AnalyticsParam.Firmware] = card.firmwareVersion.stringValue
|
||||
params[AnalyticsParam.BATCH] = card.batchId
|
||||
params[AnalyticsParam.PRODUCT_TYPE] = getProductType(scanResponse)
|
||||
params[AnalyticsParam.FIRMWARE] = card.firmwareVersion.stringValue
|
||||
|
||||
ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)?.let {
|
||||
params[AnalyticsParam.Currency] = it.value
|
||||
params[AnalyticsParam.CURRENCY] = it.value
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ package com.tangem.tap.common.analytics.topup
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.data.source.preferences.model.DataSourceTopupInfo
|
||||
import com.tangem.data.source.preferences.storage.ToppedUpWalletStorage
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.converters.TopUpEventConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
|
|
@ -19,7 +21,6 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
|||
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.persistence.ToppedUpWalletStorage
|
||||
import com.tangem.tap.scope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -90,9 +91,9 @@ class TopUpController(
|
|||
|
||||
val isToppedUpInPast = findToppedUpCurrenciesInPast(walletDataModels).isNotEmpty()
|
||||
if (isToppedUpInPast) {
|
||||
val newWalletInfo = ToppedUpWalletStorage.TopupInfo(
|
||||
val newWalletInfo = DataSourceTopupInfo(
|
||||
walletId = userWalletId.stringValue,
|
||||
cardBalanceState = AnalyticsParam.CardBalanceState.Full,
|
||||
cardBalanceState = DataSourceTopupInfo.CardBalanceState.Full,
|
||||
)
|
||||
topupWalletStorage.save(newWalletInfo)
|
||||
return@launch
|
||||
|
|
@ -109,9 +110,9 @@ class TopUpController(
|
|||
fun registerEmptyWallet(scanResponse: ScanResponse) {
|
||||
UserWalletIdBuilder.scanResponse(scanResponse).build()?.let {
|
||||
topupWalletStorage.save(
|
||||
ToppedUpWalletStorage.TopupInfo(
|
||||
DataSourceTopupInfo(
|
||||
walletId = it.stringValue,
|
||||
cardBalanceState = AnalyticsParam.CardBalanceState.Empty,
|
||||
cardBalanceState = DataSourceTopupInfo.CardBalanceState.Empty,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -129,17 +130,28 @@ class TopUpController(
|
|||
cardTypesResolver: CardTypesResolver,
|
||||
) {
|
||||
val topupInfo = topupWalletStorage.restore(userWalletId.stringValue).guard {
|
||||
val topupInfo = ToppedUpWalletStorage.TopupInfo(
|
||||
val topupInfo = DataSourceTopupInfo(
|
||||
walletId = userWalletId.stringValue,
|
||||
cardBalanceState = cardBalanceState,
|
||||
cardBalanceState = when (cardBalanceState) {
|
||||
AnalyticsParam.CardBalanceState.BlockchainError ->
|
||||
DataSourceTopupInfo.CardBalanceState.BlockchainError
|
||||
AnalyticsParam.CardBalanceState.CustomToken ->
|
||||
DataSourceTopupInfo.CardBalanceState.CustomToken
|
||||
AnalyticsParam.CardBalanceState.Empty ->
|
||||
DataSourceTopupInfo.CardBalanceState.Empty
|
||||
AnalyticsParam.CardBalanceState.Full ->
|
||||
DataSourceTopupInfo.CardBalanceState.Full
|
||||
},
|
||||
)
|
||||
topupWalletStorage.save(topupInfo)
|
||||
return
|
||||
}
|
||||
if (topupInfo.isToppedUp) return
|
||||
|
||||
if (!topupInfo.isToppedUp && cardBalanceState.isToppedUp()) {
|
||||
topupWalletStorage.save(topupInfo.copy(cardBalanceState = AnalyticsParam.CardBalanceState.Full))
|
||||
val isToppedUp = topupInfo.cardBalanceState == DataSourceTopupInfo.CardBalanceState.Full
|
||||
if (isToppedUp) return
|
||||
|
||||
if (cardBalanceState.isToppedUp()) {
|
||||
topupWalletStorage.save(topupInfo.copy(cardBalanceState = DataSourceTopupInfo.CardBalanceState.Full))
|
||||
TopUpEventConverter().convert(cardTypesResolver)?.let {
|
||||
Analytics.send(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@ package com.tangem.tap.common.chat
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.tangem.tap.ForegroundActivityObserver
|
||||
import com.tangem.datasource.config.models.ChatConfig
|
||||
import com.tangem.tap.common.chat.opener.ChatOpener
|
||||
import com.tangem.datasource.config.models.SprinklrConfig
|
||||
import com.tangem.datasource.config.models.ZendeskConfig
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.tap.ForegroundActivityObserver
|
||||
import com.tangem.tap.common.chat.opener.ChatOpener
|
||||
import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener
|
||||
import com.tangem.tap.common.chat.opener.implementation.ZendeskChatOpener
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import org.rekotlin.Store
|
||||
|
||||
class ChatManager(
|
||||
private val preferencesStorage: PreferencesStorage,
|
||||
private val preferencesStorage: PreferencesDataSource,
|
||||
private val foregroundActivityObserver: ForegroundActivityObserver,
|
||||
private val store: Store<AppState>,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object TangemTypography {
|
||||
|
||||
val body1 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_regular)),
|
||||
fontSize = 16.0.sp,
|
||||
letterSpacing = 0.5.sp,
|
||||
lineHeight = 24.0.sp,
|
||||
)
|
||||
val body2 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_regular)),
|
||||
fontSize = 14.0.sp,
|
||||
letterSpacing = 0.25.sp,
|
||||
lineHeight = 20.0.sp,
|
||||
)
|
||||
val button = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_medium)),
|
||||
fontSize = 14.0.sp,
|
||||
letterSpacing = 0.10000000149011612.sp,
|
||||
lineHeight = 20.0.sp,
|
||||
)
|
||||
val caption = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_regular)),
|
||||
fontSize = 12.0.sp,
|
||||
letterSpacing = 0.4000000059604645.sp,
|
||||
lineHeight = 16.0.sp,
|
||||
)
|
||||
val headline1 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_regular)),
|
||||
fontSize = 34.0.sp,
|
||||
letterSpacing = 0.0.sp,
|
||||
lineHeight = 44.0.sp,
|
||||
)
|
||||
val headline2 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_medium)),
|
||||
fontSize = 24.0.sp,
|
||||
letterSpacing = 0.18000000715255737.sp,
|
||||
lineHeight = 32.0.sp,
|
||||
)
|
||||
val headline3 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_medium)),
|
||||
fontSize = 20.0.sp,
|
||||
letterSpacing = 0.15000000596046448.sp,
|
||||
lineHeight = 24.0.sp,
|
||||
)
|
||||
val overline = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_medium)),
|
||||
fontSize = 10.0.sp,
|
||||
letterSpacing = 1.5.sp,
|
||||
lineHeight = 16.0.sp,
|
||||
)
|
||||
val subtitle1 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_medium)),
|
||||
fontSize = 16.0.sp,
|
||||
letterSpacing = 0.15000000596046448.sp,
|
||||
lineHeight = 24.0.sp,
|
||||
)
|
||||
val subtitle2 = TextStyle(
|
||||
fontFamily = FontFamily(Font(R.font.roboto_medium)),
|
||||
fontSize = 14.0.sp,
|
||||
letterSpacing = 0.10000000149011612.sp,
|
||||
lineHeight = 24.0.sp,
|
||||
)
|
||||
}
|
||||
|
|
@ -40,6 +40,10 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
|||
Blockchain.TON, Blockchain.TONTestnet -> R.drawable.ic_ton_no_color
|
||||
Blockchain.Kava, Blockchain.KavaTestnet -> R.drawable.ic_kava_no_color
|
||||
Blockchain.Ravencoin, Blockchain.RavencoinTestnet -> R.drawable.ic_ravencoin_no_color
|
||||
Blockchain.Cosmos, Blockchain.CosmosTestnet -> R.drawable.ic_cosmos_no_color
|
||||
Blockchain.TerraV1 -> R.drawable.ic_terra_no_color
|
||||
Blockchain.TerraV2 -> R.drawable.ic_terra2_no_color
|
||||
Blockchain.Cronos -> R.drawable.ic_cronos_no_color
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,5 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun <T> List<T>.containsAny(list: List<T>): Boolean {
|
||||
this.forEach { mainItem ->
|
||||
list.forEach { if (it == mainItem) return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.removeBy(predicate: (T) -> Boolean): Boolean {
|
||||
val toRemove = this.filter(predicate)
|
||||
this.removeAll(toRemove)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.fragment.app.DialogFragment
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import com.tangem.feature.referral.ReferralFragment
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -28,7 +29,6 @@ import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
|
|||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.shop.ui.ShopFragment
|
||||
import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment
|
||||
import com.tangem.tap.features.tokens.legacy.AddTokensFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment
|
||||
|
|
@ -45,24 +45,35 @@ fun FragmentActivity.openFragment(
|
|||
bundle: Bundle? = null,
|
||||
fgShareTransition: FragmentShareTransition? = null,
|
||||
) {
|
||||
val transaction = this.supportFragmentManager.beginTransaction()
|
||||
val fragment = fragmentFactory(screen)
|
||||
fragment.arguments = bundle
|
||||
fgShareTransition?.apply {
|
||||
fragment.sharedElementEnterTransition = enterTransitionSet
|
||||
fragment.sharedElementReturnTransition = exitTransitionSet
|
||||
transaction.setReorderingAllowed(true)
|
||||
shareElements.forEach { shareElement ->
|
||||
shareElement.wView.get()?.let { view ->
|
||||
transaction.addSharedElement(view, shareElement.elementName)
|
||||
val transaction = supportFragmentManager.beginTransaction().apply {
|
||||
setReorderingAllowed(true)
|
||||
}
|
||||
|
||||
val fragment = fragmentFactory(screen).apply {
|
||||
arguments = bundle
|
||||
|
||||
if (fgShareTransition != null) {
|
||||
sharedElementEnterTransition = fgShareTransition.enterTransitionSet
|
||||
sharedElementReturnTransition = fgShareTransition.exitTransitionSet
|
||||
fgShareTransition.shareElements.forEach { shareElement ->
|
||||
shareElement.wView.get()?.let { view ->
|
||||
transaction.addSharedElement(view, shareElement.elementName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (screen.isDialogFragment) {
|
||||
(fragment as DialogFragment).show(transaction, screen.name)
|
||||
if (addToBackstack) {
|
||||
transaction.addToBackStack(screen.name)
|
||||
val dialogFragment = requireNotNull(fragment as? DialogFragment) {
|
||||
"If screen.isDialogFragment == true then fragment must be a DialogFragment"
|
||||
}
|
||||
|
||||
dialogFragment.showAllowingStateLoss(
|
||||
fragmentManager = supportFragmentManager,
|
||||
baseTransaction = transaction,
|
||||
tag = screen.name,
|
||||
addToBackstack = addToBackstack,
|
||||
)
|
||||
} else {
|
||||
transaction.replace(R.id.fragment_container, fragment, screen.name)
|
||||
if (addToBackstack) {
|
||||
|
|
@ -72,6 +83,29 @@ fun FragmentActivity.openFragment(
|
|||
}
|
||||
}
|
||||
|
||||
private fun DialogFragment.showAllowingStateLoss(
|
||||
fragmentManager: FragmentManager,
|
||||
baseTransaction: FragmentTransaction,
|
||||
tag: String,
|
||||
addToBackstack: Boolean,
|
||||
) {
|
||||
runCatching {
|
||||
if (addToBackstack) baseTransaction.addToBackStack(tag)
|
||||
show(baseTransaction, tag)
|
||||
}
|
||||
.onFailure { throwable ->
|
||||
if (throwable is IllegalStateException) {
|
||||
val transaction = fragmentManager.beginTransaction()
|
||||
transaction.add(this, tag)
|
||||
if (addToBackstack) transaction.addToBackStack(tag)
|
||||
|
||||
transaction.commitAllowingStateLoss()
|
||||
} else {
|
||||
Timber.e(throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {
|
||||
val inclusiveFlag = if (inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0
|
||||
try {
|
||||
|
|
@ -113,12 +147,8 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.ResetToFactory -> ResetCardFragment()
|
||||
AppScreen.AccessCodeRecovery -> AccessCodeRecoveryFragment()
|
||||
AppScreen.Disclaimer -> DisclaimerFragment()
|
||||
AppScreen.AddTokens -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::tokensListFeatureToggles,
|
||||
)
|
||||
if (featureToggles.isRedesignedScreenEnabled) TokensListFragment() else AddTokensFragment()
|
||||
}
|
||||
AppScreen.AddTokens -> TokensListFragment()
|
||||
|
||||
AppScreen.AddCustomToken -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::customTokenFeatureToggles,
|
||||
|
|
@ -129,6 +159,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AddCustomTokenFragment()
|
||||
}
|
||||
}
|
||||
|
||||
AppScreen.WalletDetails -> WalletDetailsFragment()
|
||||
AppScreen.WalletConnectSessions -> WalletConnectFragment()
|
||||
AppScreen.QrScan -> QrScanFragment()
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ data class AppState(
|
|||
SendMiddleware().sendMiddleware,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
DisclaimerMiddleware().disclaimerMiddleware,
|
||||
TokensMiddleware().tokensMiddleware,
|
||||
TokensMiddleware.tokensMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
ShopMiddleware().shopMiddleware,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -75,7 +76,9 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
is GlobalAction.RestoreAppCurrency -> {
|
||||
store.dispatch(
|
||||
GlobalAction.RestoreAppCurrency.Success(
|
||||
preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency(),
|
||||
preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
|
||||
?.run { FiatCurrency(code, name, symbol) }
|
||||
?: FiatCurrency.Default,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,14 @@ import androidx.annotation.StringRes
|
|||
import com.tangem.Message
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CardFilter
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.UserCode
|
||||
import com.tangem.common.UserCodeType
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.biometric.BiometricManager
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.Config
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.UserCodeRequestPolicy
|
||||
import com.tangem.common.core.*
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.usersCode.UserCodeRepository
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
@ -45,6 +37,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
|||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
|
||||
|
||||
private val userCodeRepository by lazy {
|
||||
|
|
@ -91,6 +84,21 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun importWallet(
|
||||
scanResponse: ScanResponse,
|
||||
mnemonic: String,
|
||||
): CompletionResult<CreateProductWalletTaskResponse> {
|
||||
return when (val seedResult = DefaultMnemonic(mnemonic, tangemSdk.wordlist).generateSeed()) {
|
||||
is CompletionResult.Success -> runTaskAsync(
|
||||
CreateProductWalletTask(scanResponse.cardTypesResolver, seedResult.data),
|
||||
scanResponse.card.cardId,
|
||||
Message(context.getString(R.string.initial_message_create_wallet_body)),
|
||||
)
|
||||
|
||||
is CompletionResult.Failure -> CompletionResult.Failure(seedResult.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendScanResultsToAnalytics(result: CompletionResult<ScanResponse>) {
|
||||
if (result is CompletionResult.Failure) {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.core.analytics.Analytics
|
||||
|
|
@ -11,6 +15,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
|
|
@ -24,17 +29,39 @@ import com.tangem.tap.preferencesStorage
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class TapWalletManager {
|
||||
val walletManagerFactory: WalletManagerFactory
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
class TapWalletManager(
|
||||
private val dispatchers: CoroutineDispatcherProvider = AppCoroutineDispatcherProvider(),
|
||||
) {
|
||||
|
||||
private val blockchainSdkConfig by lazy {
|
||||
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
||||
}
|
||||
|
||||
private var loadUserWalletDataJob: Job? = null
|
||||
set(value) {
|
||||
field?.cancel()
|
||||
field = value
|
||||
}
|
||||
|
||||
val walletManagerFactory: WalletManagerFactory
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
|
||||
suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean, sendAnalyticsEvent: Boolean) {
|
||||
// If a previous job was running, it gets cancelled before the new one starts,
|
||||
// ensuring that only one job is active at any given time.
|
||||
loadUserWalletDataJob = CoroutineScope(dispatchers.io)
|
||||
.launch { loadUserWalletData(userWallet, refresh, sendAnalyticsEvent) }
|
||||
.also { it.join() }
|
||||
}
|
||||
|
||||
private suspend fun loadUserWalletData(userWallet: UserWallet, refresh: Boolean, sendAnalyticsEvent: Boolean) {
|
||||
Analytics.setContext(userWallet.scanResponse)
|
||||
if (sendAnalyticsEvent) {
|
||||
Analytics.send(Basic.WalletOpened())
|
||||
|
|
@ -68,6 +95,7 @@ class TapWalletManager {
|
|||
Timber.d("Wallet stores fetched for ${userWallet.walletId}")
|
||||
store.dispatchOnMain(WalletAction.LoadData.Success)
|
||||
store.state.globalState.topUpController?.loadDataSuccess()
|
||||
store.dispatchWithMain(WalletAction.Warnings.CheckHashesCount.VerifyOnlineIfNeeded)
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
val errorAction = when (error) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
package com.tangem.tap.domain.configurable.warningMessage
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.extensions.containsAny
|
||||
import com.tangem.tap.common.extensions.removeBy
|
||||
import com.tangem.wallet.R
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WarningMessagesManager {
|
||||
|
||||
private val warningsList: MutableList<WarningMessage> = mutableListOf()
|
||||
private val warningsList = CopyOnWriteArrayList<WarningMessage>()
|
||||
|
||||
fun addWarning(warning: WarningMessage) {
|
||||
if (findWarning(warning) == null) {
|
||||
|
|
@ -19,36 +19,27 @@ class WarningMessagesManager {
|
|||
}
|
||||
}
|
||||
|
||||
fun getWarnings(
|
||||
location: WarningMessage.Location,
|
||||
forBlockchains: List<Blockchain> = emptyList(),
|
||||
): List<WarningMessage> {
|
||||
return warningsList
|
||||
.filter { !it.isHidden && it.location.contains(location) }
|
||||
.filter {
|
||||
val list = it.blockchainList
|
||||
when {
|
||||
list == null -> true
|
||||
list.containsAny(forBlockchains) -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
fun getWarnings(location: WarningMessage.Location, blockchains: List<Blockchain>): List<WarningMessage> {
|
||||
return warningsList.filter { message ->
|
||||
val messageBlockchains = message.blockchainList
|
||||
val isCorrespondingMessageBlockchains = messageBlockchains == null ||
|
||||
messageBlockchains.any(blockchains::contains)
|
||||
val isCorrespondingMessageLocation = message.location.contains(location)
|
||||
|
||||
!message.isHidden && isCorrespondingMessageLocation && isCorrespondingMessageBlockchains
|
||||
}
|
||||
}
|
||||
|
||||
fun hideWarning(warning: WarningMessage): Boolean {
|
||||
val foundWarning = findWarning(warning)
|
||||
return when {
|
||||
foundWarning == null -> false
|
||||
foundWarning.type == WarningMessage.Type.Temporary ||
|
||||
foundWarning.type == WarningMessage.Type.AppRating -> {
|
||||
if (foundWarning.isHidden) {
|
||||
false
|
||||
} else {
|
||||
foundWarning.isHidden = true
|
||||
true
|
||||
}
|
||||
}
|
||||
else -> false
|
||||
val foundWarning = findWarning(warning) ?: return false
|
||||
val isCorrectType = foundWarning.type == WarningMessage.Type.Temporary ||
|
||||
foundWarning.type == WarningMessage.Type.AppRating
|
||||
|
||||
return if (!foundWarning.isHidden && isCorrectType) {
|
||||
foundWarning.isHidden = true
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,6 +52,8 @@ class WarningMessagesManager {
|
|||
warningsList.removeBy { it.messageResId == messageRes }
|
||||
}
|
||||
|
||||
fun containsWarning(warning: WarningMessage) = warning in warningsList
|
||||
|
||||
private fun sortByPriority() {
|
||||
warningsList.sortBy { it.priority.ordinal }
|
||||
}
|
||||
|
|
@ -72,31 +65,31 @@ class WarningMessagesManager {
|
|||
companion object {
|
||||
const val REMAINING_SIGNATURES_WARNING = 10
|
||||
|
||||
fun devCardWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
val devCardWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.common_warning,
|
||||
R.string.alert_developer_card,
|
||||
WarningMessage.Origin.Local,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
messageResId = R.string.alert_developer_card,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
fun alreadySignedHashesWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
val alreadySignedHashesWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Temporary,
|
||||
priority = WarningMessage.Priority.Info,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.common_warning,
|
||||
R.string.alert_card_signed_transactions,
|
||||
WarningMessage.Origin.Local,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
messageResId = R.string.alert_card_signed_transactions,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
fun signedHashesMultiWalletWarning(): WarningMessage = WarningMessage(
|
||||
val signedHashesMultiWalletWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Temporary,
|
||||
|
|
@ -110,69 +103,71 @@ class WarningMessagesManager {
|
|||
titleFormatArg = "\u26A0",
|
||||
)
|
||||
|
||||
fun appRatingWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
WarningMessage.Type.AppRating,
|
||||
WarningMessage.Priority.Info,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.warning_rate_app_title,
|
||||
R.string.warning_rate_app_message,
|
||||
WarningMessage.Origin.Local,
|
||||
val appRatingWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.AppRating,
|
||||
priority = WarningMessage.Priority.Info,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.warning_rate_app_title,
|
||||
messageResId = R.string.warning_rate_app_message,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
|
||||
return warning.messageResId == R.string.alert_card_signed_transactions
|
||||
}
|
||||
|
||||
fun onlineVerificationFailed(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.warning_failed_to_verify_card_title,
|
||||
R.string.warning_failed_to_verify_card_message,
|
||||
WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage = WarningMessage(
|
||||
val onlineVerificationFailed = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
messageResId = R.string.warning_low_signatures_format,
|
||||
titleResId = R.string.warning_failed_to_verify_card_title,
|
||||
messageResId = R.string.warning_failed_to_verify_card_message,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
messageFormatArg = remainingSignatures.toString(),
|
||||
)
|
||||
|
||||
fun testCardWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
val testCardWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.TestCard,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen),
|
||||
null,
|
||||
R.string.common_warning,
|
||||
R.string.warning_testnet_card_message,
|
||||
WarningMessage.Origin.Local,
|
||||
location = listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
messageResId = R.string.warning_testnet_card_message,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
fun demoCardWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
val demoCardWarning = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.common_warning,
|
||||
R.string.alert_demo_message,
|
||||
WarningMessage.Origin.Local,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
messageResId = R.string.alert_demo_message,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage {
|
||||
return WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.common_warning,
|
||||
messageResId = R.string.warning_low_signatures_format,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
messageFormatArg = remainingSignatures.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
|
||||
return warning.messageResId == R.string.alert_card_signed_transactions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.right
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.AnalyticsEvent
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
|
||||
|
||||
/**
|
||||
* Handles the analytics event interception and sending event after the scan card operation. Always returns result of
|
||||
* previous chain.
|
||||
*
|
||||
* @param event the analytics event to be handled by this chain.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
class AnalyticsChain(
|
||||
private val event: AnalyticsEvent,
|
||||
) : Chain<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(
|
||||
previousChainResult: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
val interceptor = CardContextInterceptor(previousChainResult)
|
||||
val params = event.params.toMutableMap()
|
||||
interceptor.intercept(params)
|
||||
event.params = params.toMap()
|
||||
|
||||
Analytics.send(event)
|
||||
|
||||
return previousChainResult.right()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
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.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper
|
||||
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.saltPay.SaltPayActivationManagerFactory
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import kotlinx.coroutines.delay
|
||||
import org.rekotlin.Store
|
||||
|
||||
/**
|
||||
* Handles the verification process to determine if the scanned card requires onboarding.
|
||||
*
|
||||
* Returns:
|
||||
* - [ScanChainException.OnboardingNeeded] if onboarding required for none SaltPay cards.
|
||||
* - [ScanChainException.CheckForSaltPayOnboardingCaseException] if unable to check for SaltPay onboarding case.
|
||||
* - [ScanChainException.PutSaltPayVisaCard] if scanned SaltPay card is not activated and onboarding required.
|
||||
*
|
||||
* @param store the [Store] that holds the state of the app.
|
||||
* @param tapWalletManager manager responsible for handling operations related to the Wallet.
|
||||
* @param preferencesStore data source for user preferences.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
class CheckForOnboardingChain(
|
||||
private val store: Store<AppState>,
|
||||
private val tapWalletManager: TapWalletManager,
|
||||
private val preferencesStore: PreferencesDataSource,
|
||||
) : Chain<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(
|
||||
previousChainResult: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
val cardTypesResolver = previousChainResult.cardTypesResolver
|
||||
|
||||
tapWalletManager.updateConfigManager(previousChainResult)
|
||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult))
|
||||
|
||||
return when {
|
||||
cardTypesResolver.isSaltPayVisa() -> {
|
||||
val manager = SaltPayActivationManagerFactory(
|
||||
blockchain = previousChainResult.cardTypesResolver.getBlockchain(),
|
||||
card = previousChainResult.card,
|
||||
).create()
|
||||
val result = OnboardingSaltPayHelper.isOnboardingCase(previousChainResult, manager)
|
||||
delay(timeMillis = 500)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
Analytics.addContext(previousChainResult)
|
||||
val isOnboardingCase = result.data
|
||||
if (isOnboardingCase) {
|
||||
store.dispatch(
|
||||
GlobalAction.Onboarding.Start(previousChainResult, canSkipBackup = false),
|
||||
)
|
||||
store.dispatch(OnboardingSaltPayAction.SetDependencies(manager))
|
||||
store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false))
|
||||
ScanChainException.OnboardingNeeded(AppScreen.OnboardingWallet).left()
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
SaltPayExceptionHandler.handle(result.error)
|
||||
ScanChainException.CheckForSaltPayOnboardingCaseException(result.error).left()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cardTypesResolver.isSaltPayWallet() -> {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
if (previousChainResult.card.backupStatus?.isActive == false) {
|
||||
SaltPayExceptionHandler.handle(SaltPayActivationError.PutVisaCard)
|
||||
ScanChainException.PutSaltPayVisaCard.left()
|
||||
} else {
|
||||
Analytics.setContext(previousChainResult)
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
OnboardingHelper.isOnboardingCase(previousChainResult) -> {
|
||||
Analytics.addContext(previousChainResult)
|
||||
store.dispatchOnMain(GlobalAction.Onboarding.Start(previousChainResult, canSkipBackup = true))
|
||||
val appScreen = OnboardingHelper.whereToNavigate(previousChainResult)
|
||||
ScanChainException.OnboardingNeeded(appScreen).left()
|
||||
}
|
||||
else -> {
|
||||
Analytics.setContext(previousChainResult)
|
||||
// If twins was twinned previously but twins welcome not shown
|
||||
if (previousChainResult.twinsIsTwinned() && !preferencesStore.wasTwinsOnboardingShown()) {
|
||||
store.dispatchOnMain(
|
||||
TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(previousChainResult)),
|
||||
)
|
||||
ScanChainException.OnboardingNeeded(AppScreen.OnboardingTwins).left()
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa
|
||||
|
||||
/**
|
||||
* Handles the verification for unfinished SaltPay backup processes after the card scanning operation.
|
||||
*
|
||||
* Returns [ScanChainException.PutSaltPayVisaCard] if backup is not finished.
|
||||
*
|
||||
* @param backupService a service responsible for managing backup operations, used here to check for unfinished backups
|
||||
* and verify the primary card type.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
internal class CheckForUnfinishedSaltPayBackupChain(
|
||||
private val backupService: BackupService,
|
||||
) : Chain<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(
|
||||
previousChainResult: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) {
|
||||
return previousChainResult.right()
|
||||
}
|
||||
|
||||
val isTheSamePrimaryCard = backupService.primaryCardId
|
||||
?.let { it == previousChainResult.card.cardId }
|
||||
?: false
|
||||
|
||||
return if (previousChainResult.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) {
|
||||
ScanChainException.PutSaltPayVisaCard.left()
|
||||
} else {
|
||||
previousChainResult.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
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 kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.rekotlin.Store
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
/**
|
||||
* Handles disclaimer display after the card scanning operation.
|
||||
*
|
||||
* Returns [ScanChainException.DisclaimerWasCanceled] if the disclaimer is dismissed by the user.
|
||||
*
|
||||
* @param store the [Store] that holds the state of the app, used here to dispatch actions related to disclaimers.
|
||||
* @param disclaimerWillShow an optional function to be invoked when a disclaimer is about to be shown. Default is an
|
||||
* empty function.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
internal class DisclaimerChain(
|
||||
private val store: Store<AppState>,
|
||||
private val disclaimerWillShow: () -> Unit = {},
|
||||
) : Chain<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(
|
||||
previousChainResult: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
val disclaimer = previousChainResult.card.createDisclaimer()
|
||||
|
||||
return if (disclaimer.isAccepted()) {
|
||||
previousChainResult.right()
|
||||
} else {
|
||||
disclaimerWillShow()
|
||||
showDisclaimer(disclaimer, previousChainResult)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun showDisclaimer(
|
||||
disclaimer: Disclaimer,
|
||||
response: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
|
||||
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
store.dispatchOnMain(
|
||||
DisclaimerAction.Show(
|
||||
fromScreen = AppScreen.Home,
|
||||
callback = DisclaimerCallback(
|
||||
onAccept = {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(response.right())
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(ScanChainException.DisclaimerWasCanceled.left())
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
||||
sealed class ScanChainException : ScanCardException.ChainException() {
|
||||
|
||||
/**
|
||||
* May be returned from [CheckForOnboardingChain] and [CheckForUnfinishedSaltPayBackupChain]
|
||||
* */
|
||||
object PutSaltPayVisaCard : ScanChainException()
|
||||
|
||||
/**
|
||||
* May be returned from [DisclaimerChain]
|
||||
* */
|
||||
object DisclaimerWasCanceled : ScanChainException()
|
||||
|
||||
/**
|
||||
* May be returned from [CheckForOnboardingChain]
|
||||
*
|
||||
* @param onboardingRoute route where to navigate
|
||||
* */
|
||||
data class OnboardingNeeded(val onboardingRoute: AppScreen) : ScanChainException()
|
||||
|
||||
/**
|
||||
* May be returned from [CheckForOnboardingChain]
|
||||
*
|
||||
* @param cause cause of exception
|
||||
* */
|
||||
data class CheckForSaltPayOnboardingCaseException(override val cause: Throwable?) : ScanChainException()
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.tap.domain.scanCard.chains
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
||||
/**
|
||||
* Responsible for invoking the callback at the end of the card scanning operation. Should be passed as last chain in
|
||||
* after card scanning chains. Always returns result of previous chain.
|
||||
*
|
||||
* @param onScanningFinished a suspending function to be called when the scanning process has finished.
|
||||
*
|
||||
* @see Chain for more information about the Chain interface.
|
||||
*/
|
||||
internal class ScanningFinishedChain(
|
||||
private val onScanningFinished: suspend () -> Unit,
|
||||
) : Chain<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(previousChainResult: ScanResponse): Either<ScanChainException, ScanResponse> {
|
||||
onScanningFinished()
|
||||
return previousChainResult.right()
|
||||
}
|
||||
}
|
||||
|
|
@ -10,12 +10,12 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.common.map
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
|
|
@ -56,6 +56,7 @@ private data class CreateWalletResponse(
|
|||
|
||||
class CreateProductWalletTask(
|
||||
private val cardTypesResolver: CardTypesResolver,
|
||||
private val seed: ByteArray? = null,
|
||||
) : CardSessionRunnable<CreateProductWalletTaskResponse> {
|
||||
|
||||
override val allowsRequestAccessCodeFromRepository: Boolean = false
|
||||
|
|
@ -74,7 +75,8 @@ class CreateProductWalletTask(
|
|||
cardTypesResolver.isTangemNote() -> CreateWalletTangemNote(cardTypesResolver)
|
||||
cardTypesResolver.isTangemTwins() ->
|
||||
throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
|
||||
else -> CreateWalletTangemWallet()
|
||||
|
||||
else -> CreateWalletTangemWallet(seed)
|
||||
}
|
||||
commandProcessor.proceed(cardDto, session) {
|
||||
when (it) {
|
||||
|
|
@ -129,7 +131,9 @@ private class CreateWalletTangemNote(private val cardTypesResolver: CardTypesRes
|
|||
}
|
||||
}
|
||||
|
||||
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWalletTaskResponse> {
|
||||
private class CreateWalletTangemWallet(
|
||||
private val seed: ByteArray?,
|
||||
) : ProductCommandProcessor<CreateProductWalletTaskResponse> {
|
||||
|
||||
private var primaryCard: PrimaryCard? = null
|
||||
|
||||
|
|
@ -138,7 +142,8 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
val curves = card.getCurvesForNonCreatedWallets()
|
||||
val walletsOnCard = card.wallets.map { it.curve }.toSet()
|
||||
val curves = card.supportedCurves.intersect(CURVES_FOR_WALLETS).subtract(walletsOnCard).toList()
|
||||
|
||||
if (curves.isEmpty()) {
|
||||
val createWalletResponses = card.wallets.map { wallet ->
|
||||
|
|
@ -148,7 +153,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
return
|
||||
}
|
||||
|
||||
CreateWalletsTask(curves).run(session) { result ->
|
||||
CreateWalletsTask(curves, seed).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
proceedWithCreatedWallets(
|
||||
|
|
@ -158,7 +163,6 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
callback = callback,
|
||||
)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
|
|
@ -233,17 +237,38 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
val map = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
|
||||
var isBlockchainsForCurvesExist = false
|
||||
createWalletResponse.forEach { response ->
|
||||
val blockchainsForCurve = getBlockchains(response.cardId, card).filter {
|
||||
it.getSupportedCurves().contains(response.wallet.curve)
|
||||
}
|
||||
val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath(card.derivationStyle) }
|
||||
val derivationPaths = blockchainsForCurve.mapNotNull {
|
||||
isBlockchainsForCurvesExist = true
|
||||
it.derivationPath(card.derivationStyle)
|
||||
}
|
||||
if (derivationPaths.isNotEmpty()) {
|
||||
map[response.wallet.publicKey.toMapKey()] = derivationPaths
|
||||
}
|
||||
}
|
||||
val cardEnv = session.environment.card
|
||||
if (cardEnv == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
return
|
||||
}
|
||||
if (map.isEmpty()) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
|
||||
if (isBlockchainsForCurvesExist) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
|
||||
} else {
|
||||
// if there is no blockchains to derive, just return success response with empty derivedKeys
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
CreateProductWalletTaskResponse(
|
||||
card = cardEnv,
|
||||
primaryCard = primaryCard,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +279,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
callback(
|
||||
CompletionResult.Success(
|
||||
CreateProductWalletTaskResponse(
|
||||
card = session.environment.card!!,
|
||||
card = cardEnv,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard,
|
||||
),
|
||||
|
|
@ -274,4 +299,8 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
else -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val CURVES_FOR_WALLETS = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ class CreateWalletsResponse(
|
|||
|
||||
class CreateWalletsTask(
|
||||
private val curves: List<EllipticCurve>,
|
||||
private val seed: ByteArray? = null,
|
||||
) : CardSessionRunnable<CreateWalletsResponse> {
|
||||
|
||||
private val createdWalletsResponses = mutableListOf<CreateWalletResponse>()
|
||||
|
|
@ -37,7 +38,7 @@ class CreateWalletsTask(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit,
|
||||
) {
|
||||
CreateWalletTask(curve).run(session) { result ->
|
||||
CreateWalletTask(curve, seed).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
createdWalletsResponses.add(result.data)
|
||||
|
|
@ -47,6 +48,7 @@ class CreateWalletsTask(
|
|||
}
|
||||
createWallet(curves[createdWalletsResponses.size], session, callback)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.domain.tasks.product
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemError
|
||||
|
|
@ -19,9 +18,6 @@ import com.tangem.common.tlv.Tlv
|
|||
import com.tangem.common.tlv.TlvDecoder
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isExcluded
|
||||
import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
|
|
@ -29,8 +25,9 @@ import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
|||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.common.TwinsHelper
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
||||
|
|
@ -39,7 +36,6 @@ import com.tangem.operations.files.ReadFilesTask
|
|||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.extensions.getPrimaryCurve
|
||||
import com.tangem.tap.domain.extensions.isFirmwareMultiwalletAllowed
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
|
@ -114,7 +110,7 @@ private class ScanWalletProcessor(
|
|||
return
|
||||
}
|
||||
|
||||
createMissingWalletsIfNeeded(card, session, callback)
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
}
|
||||
|
||||
private fun readFile(
|
||||
|
|
@ -122,7 +118,7 @@ private class ScanWalletProcessor(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||
) {
|
||||
val funToContinue = { createMissingWalletsIfNeeded(card, session, callback) }
|
||||
val funToContinue = { startLinkingForBackupIfNeeded(card, session, callback) }
|
||||
|
||||
ReadFilesTask(fileName = "blockchainInfo", walletPublicKey = null).run(session) { result ->
|
||||
|
||||
|
|
@ -185,45 +181,6 @@ private class ScanWalletProcessor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createMissingWalletsIfNeeded(
|
||||
card: CardDTO,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||
) {
|
||||
if (card.wallets.isNotEmpty() && card.backupStatus?.isActive == true) {
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
return
|
||||
}
|
||||
|
||||
if (card.wallets.isEmpty() || !card.isFirmwareMultiwalletAllowed) {
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
return
|
||||
}
|
||||
|
||||
val curvesToCreate = card.getCurvesForNonCreatedWallets()
|
||||
if (curvesToCreate.isEmpty()) {
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
return
|
||||
}
|
||||
|
||||
CreateWalletsTask(curvesToCreate).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
PreflightReadTask(
|
||||
readMode = PreflightReadMode.FullCardRead,
|
||||
cardId = card.cardId,
|
||||
).run(session) { readResult ->
|
||||
when (readResult) {
|
||||
is CompletionResult.Success -> startLinkingForBackupIfNeeded(card, session, callback)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startLinkingForBackupIfNeeded(
|
||||
card: CardDTO,
|
||||
session: CardSession,
|
||||
|
|
@ -424,10 +381,4 @@ private class ScanTwinProcessor : ProductCommandProcessor<ScanResponse> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun CardDTO.getCurvesForNonCreatedWallets(): List<EllipticCurve> {
|
||||
val curvesPresent = wallets.map { it.curve }.toSet()
|
||||
val curvesForNonCreatedWallets = supportedCurves.subtract(curvesPresent + EllipticCurve.Secp256r1)
|
||||
return curvesForNonCreatedWallets.toList()
|
||||
}
|
||||
|
|
@ -52,42 +52,36 @@ internal fun List<WalletDataModel>.updateWithAmounts(wallet: Wallet): List<Walle
|
|||
internal fun WalletDataModel.updateWithAmount(wallet: Wallet): WalletDataModel {
|
||||
val pendingTransactions = wallet.getPendingTransactions()
|
||||
return this.copy(
|
||||
status = when (val currency = this.currency) {
|
||||
status = when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val amount = wallet.fundsAvailable(AmountType.Coin)
|
||||
if (pendingTransactions.isEmpty()) {
|
||||
WalletDataModel.VerifiedOnline(
|
||||
amount = amount,
|
||||
)
|
||||
WalletDataModel.VerifiedOnline(amount)
|
||||
} else {
|
||||
WalletDataModel.TransactionInProgress(
|
||||
amount = amount,
|
||||
pendingTransactions = pendingTransactions,
|
||||
)
|
||||
WalletDataModel.TransactionInProgress(amount, pendingTransactions)
|
||||
}
|
||||
}
|
||||
|
||||
is Currency.Token -> {
|
||||
val token = currency.token
|
||||
val amount = wallet.fundsAvailable(AmountType.Token(token))
|
||||
val hasTokenPendingTransactions = pendingTransactions
|
||||
.any { it.transactionData.amount.currencySymbol == token.symbol }
|
||||
val hasTokenPendingTransactions = pendingTransactions.any {
|
||||
it.transactionData.amount.currencySymbol == token.symbol
|
||||
}
|
||||
|
||||
when {
|
||||
hasTokenPendingTransactions -> {
|
||||
WalletDataModel.TransactionInProgress(
|
||||
amount = amount,
|
||||
pendingTransactions = pendingTransactions,
|
||||
)
|
||||
WalletDataModel.TransactionInProgress(amount, pendingTransactions)
|
||||
}
|
||||
|
||||
pendingTransactions.isNotEmpty() -> {
|
||||
WalletDataModel.SameCurrencyTransactionInProgress(
|
||||
amount = amount,
|
||||
pendingTransactions = pendingTransactions,
|
||||
)
|
||||
// FIXME: Necessary to avoid passing pending transactions
|
||||
// because SameCurrencyTransactionInProgress didn't use it. It used only to define main button
|
||||
// availability. UI layer turned off pending transaction visibility for this state.
|
||||
WalletDataModel.SameCurrencyTransactionInProgress(amount, pendingTransactions)
|
||||
}
|
||||
else -> {
|
||||
WalletDataModel.VerifiedOnline(
|
||||
amount = amount,
|
||||
)
|
||||
WalletDataModel.VerifiedOnline(amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ object FoundTokenConverter : Converter<CoinsResponse.Coin, FoundToken> {
|
|||
id = value.id,
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
isActive = value.active,
|
||||
network = value.networks.firstOrNull()?.let { network ->
|
||||
FoundToken.Network(
|
||||
id = network.networkId,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.customtoken.impl.di
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
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
|
||||
|
|
@ -26,7 +25,6 @@ internal object CustomTokenInteractorModule {
|
|||
tangemTechApi: TangemTechApi,
|
||||
appCoroutineDispatcherProvider: AppCoroutineDispatcherProvider,
|
||||
reduxStateHolder: AppStateHolder,
|
||||
derivationManager: DerivationManager,
|
||||
): CustomTokenInteractor {
|
||||
return DefaultCustomTokenInteractor(
|
||||
featureRepository = DefaultCustomTokenRepository(
|
||||
|
|
@ -34,7 +32,6 @@ internal object CustomTokenInteractorModule {
|
|||
dispatchers = appCoroutineDispatcherProvider,
|
||||
reduxStateHolder = reduxStateHolder,
|
||||
),
|
||||
derivationManager = derivationManager,
|
||||
reduxStateHolder = reduxStateHolder,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.features.customtoken.impl.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
|
||||
/**
|
||||
* Custom token interactor
|
||||
|
|
@ -14,6 +14,6 @@ interface CustomTokenInteractor {
|
|||
/** Find token by [address] and [blockchain] */
|
||||
suspend fun findToken(address: String, blockchain: Blockchain): FoundToken
|
||||
|
||||
/** Save token [currency] with contact address [address] */
|
||||
suspend fun saveToken(currency: Currency, address: String)
|
||||
/** Save token [customCurrency] */
|
||||
suspend fun saveToken(customCurrency: CustomCurrency)
|
||||
}
|
||||
|
|
@ -1,34 +1,40 @@
|
|||
package com.tangem.tap.features.customtoken.impl.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.util.hasDerivation
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.models.Currency.NativeToken
|
||||
import com.tangem.lib.crypto.models.Currency.NonNativeToken
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.delay
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Default implementation of custom token interactor
|
||||
*
|
||||
* @property featureRepository feature repository
|
||||
* @property derivationManager derivation manager
|
||||
* @property reduxStateHolder redux state holder
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DefaultCustomTokenInteractor(
|
||||
private val featureRepository: CustomTokenRepository,
|
||||
private val derivationManager: DerivationManager,
|
||||
private val reduxStateHolder: AppStateHolder,
|
||||
) : CustomTokenInteractor {
|
||||
|
||||
|
|
@ -39,58 +45,111 @@ class DefaultCustomTokenInteractor(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun saveToken(currency: Currency, address: String) {
|
||||
val hasDerivation = derivationManager.hasDerivation(
|
||||
networkId = currency.blockchain.toNetworkId(),
|
||||
derivationPath = requireNotNull(currency.derivationPath),
|
||||
)
|
||||
override suspend fun saveToken(customCurrency: CustomCurrency) {
|
||||
val scanResponse = reduxStateHolder.scanResponse ?: return
|
||||
|
||||
if (!hasDerivation) {
|
||||
derivationManager.deriveMissingBlockchains(
|
||||
when (currency) {
|
||||
is Currency.Blockchain -> NativeToken(
|
||||
id = requireNotNull(currency.coinId),
|
||||
name = currency.currencyName,
|
||||
symbol = currency.currencySymbol,
|
||||
networkId = currency.blockchain.toNetworkId(),
|
||||
)
|
||||
|
||||
is Currency.Token -> NonNativeToken(
|
||||
id = requireNotNull(currency.coinId),
|
||||
name = currency.currencyName,
|
||||
symbol = currency.currencySymbol,
|
||||
networkId = currency.blockchain.toNetworkId(),
|
||||
contractAddress = address,
|
||||
decimalCount = currency.decimals,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
submitAdd(
|
||||
scanResponse = requireNotNull(reduxStateHolder.scanResponse),
|
||||
currency = currency,
|
||||
)
|
||||
val currency = Currency.fromCustomCurrency(customCurrency)
|
||||
val isNeedToDerive = isNeedToDerive(scanResponse, currency)
|
||||
if (isNeedToDerive) {
|
||||
deriveMissingBlockchains(scanResponse = scanResponse, currencyList = listOf(currency)) {
|
||||
submitAdd(scanResponse = it, currency = currency)
|
||||
}
|
||||
} else {
|
||||
submitAdd(scanResponse, currency)
|
||||
}
|
||||
}
|
||||
|
||||
private fun submitAdd(scanResponse: ScanResponse, currency: Currency) {
|
||||
private fun isNeedToDerive(scanResponse: ScanResponse, currency: Currency): Boolean {
|
||||
return currency.derivationPath?.let { !scanResponse.hasDerivation(currency.blockchain, it) } ?: false
|
||||
}
|
||||
|
||||
private suspend fun deriveMissingBlockchains(
|
||||
scanResponse: ScanResponse,
|
||||
currencyList: List<Currency>,
|
||||
onSuccess: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
val derivationDataList = listOfNotNull(
|
||||
getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList),
|
||||
getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList),
|
||||
)
|
||||
|
||||
val derivations = derivationDataList.associate(TokensMiddleware.DerivationData::derivations)
|
||||
if (derivations.isEmpty()) {
|
||||
onSuccess(scanResponse)
|
||||
return
|
||||
}
|
||||
|
||||
when (val result = tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)) {
|
||||
is CompletionResult.Success -> {
|
||||
val newDerivedKeys = result.data.entries
|
||||
val oldDerivedKeys = scanResponse.derivedKeys
|
||||
|
||||
val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet()
|
||||
|
||||
val updatedDerivedKeys = walletKeys.associateWith { walletKey ->
|
||||
val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap())
|
||||
val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
ExtendedPublicKeysMap(oldDerivations + newDerivations)
|
||||
}
|
||||
|
||||
val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys)
|
||||
store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
|
||||
onSuccess(updatedScanResponse)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
currencyList: List<Currency>,
|
||||
): TokensMiddleware.DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
|
||||
val manageTokensCandidates = currencyList.map { it.blockchain }.distinct().filter {
|
||||
it.getSupportedCurves().contains(curve)
|
||||
}.mapNotNull {
|
||||
it.derivationPath(scanResponse.card.derivationStyle)
|
||||
}
|
||||
|
||||
val customTokensCandidates = currencyList.filter {
|
||||
it.blockchain.getSupportedCurves().contains(curve)
|
||||
}.mapNotNull { it.derivationPath }.map { DerivationPath(it) }
|
||||
|
||||
val bothCandidates = (manageTokensCandidates + customTokensCandidates).distinct()
|
||||
if (bothCandidates.isEmpty()) return null
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys: ExtendedPublicKeysMap =
|
||||
scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
|
||||
|
||||
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
if (toDerive.isEmpty()) return null
|
||||
|
||||
return TokensMiddleware.DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
|
||||
}
|
||||
|
||||
private suspend fun submitAdd(scanResponse: ScanResponse, currency: Currency) {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to add currencies, no user wallet selected")
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
userWalletsListManager.update(
|
||||
userWalletId = selectedUserWallet.walletId,
|
||||
update = { userWallet ->
|
||||
userWallet.copy(scanResponse = scanResponse)
|
||||
},
|
||||
)
|
||||
.flatMap { updatedUserWallet ->
|
||||
walletCurrenciesManager.addCurrencies(
|
||||
userWallet = updatedUserWallet,
|
||||
currenciesToAdd = listOf(currency),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
userWalletsListManager.update(
|
||||
userWalletId = selectedUserWallet.walletId,
|
||||
update = { userWallet -> userWallet.copy(scanResponse = scanResponse) },
|
||||
)
|
||||
.flatMap { updatedUserWallet ->
|
||||
walletCurrenciesManager.addCurrencies(
|
||||
userWallet = updatedUserWallet,
|
||||
currenciesToAdd = listOf(currency),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,14 +3,21 @@ package com.tangem.tap.features.customtoken.impl.domain.models
|
|||
/**
|
||||
* Found token model
|
||||
*
|
||||
* @property id id
|
||||
* @property name name
|
||||
* @property symbol symbol
|
||||
* @property network network
|
||||
* @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 network: Network) {
|
||||
data class FoundToken(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val isActive: Boolean,
|
||||
val network: Network,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Found token network
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.transition.TransitionInflater
|
||||
|
|
@ -24,6 +25,8 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
internal class AddCustomTokenFragment : Fragment() {
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
|
||||
|
||||
with(TransitionInflater.from(requireContext())) {
|
||||
enterTransition = inflateTransition(R.transition.fade)
|
||||
exitTransition = inflateTransition(R.transition.fade)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.models
|
||||
|
||||
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.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -94,106 +92,94 @@ internal sealed interface AddCustomTokenInputField {
|
|||
/** Label */
|
||||
val label: TextReference
|
||||
|
||||
/** Input availability */
|
||||
val isEnabled: Boolean
|
||||
|
||||
/** Flag that determine if current value has error */
|
||||
val isError: Boolean
|
||||
|
||||
/** Placeholder (hint) */
|
||||
val placeholder: TextReference
|
||||
|
||||
/** Flag that determine the processing of current value */
|
||||
val isLoading: Boolean
|
||||
|
||||
/**
|
||||
* Input field model to enter the contract address
|
||||
*
|
||||
* @property value current value
|
||||
* @property onValueChange lambda be invoked when value is been changed
|
||||
* @property isError flag that determine if current value has error
|
||||
* @property isLoading flag that determine the processing of current value
|
||||
* @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 isError: Boolean,
|
||||
override val isLoading: Boolean,
|
||||
) : AddCustomTokenInputField {
|
||||
override val isEnabled = true
|
||||
override val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
|
||||
override val label = TextReference.Res(R.string.custom_token_contract_address_input_title)
|
||||
override val placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000")
|
||||
}
|
||||
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 isEnabled input availability
|
||||
* @property isError flag that determine if current value has error
|
||||
* @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 isEnabled: Boolean,
|
||||
override val isError: Boolean,
|
||||
) : AddCustomTokenInputField {
|
||||
override val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
|
||||
override val label = TextReference.Res(R.string.custom_token_name_input_title)
|
||||
override val placeholder = TextReference.Res(id = R.string.custom_token_name_input_placeholder)
|
||||
override val isLoading = false
|
||||
}
|
||||
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 isEnabled input availability
|
||||
* @property isError flag that determine if current value has error
|
||||
* @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 isEnabled: Boolean,
|
||||
override val isError: Boolean,
|
||||
) : AddCustomTokenInputField {
|
||||
override val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
|
||||
override val label = TextReference.Res(R.string.custom_token_token_symbol_input_title)
|
||||
override val placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder)
|
||||
override val isLoading = false
|
||||
}
|
||||
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 isEnabled input availability
|
||||
* @property isError flag that determine if current value has error
|
||||
* @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 isEnabled: Boolean,
|
||||
override val isError: Boolean,
|
||||
) : AddCustomTokenInputField {
|
||||
override val keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next)
|
||||
override val label = TextReference.Res(R.string.custom_token_decimals_input_title)
|
||||
override val placeholder = TextReference.Str(value = "8")
|
||||
override val isLoading = false
|
||||
}
|
||||
override val keyboardOptions: KeyboardOptions,
|
||||
override val label: TextReference,
|
||||
override val placeholder: TextReference,
|
||||
val isEnabled: Boolean,
|
||||
) : AddCustomTokenInputField
|
||||
}
|
||||
|
||||
/** Base selector field model of add custom token screen */
|
||||
internal sealed interface AddCustomTokenSelectorField {
|
||||
|
||||
/** Selection availability */
|
||||
val isEnabled: Boolean
|
||||
|
||||
/** Label string resource id */
|
||||
/** Label */
|
||||
val label: TextReference
|
||||
|
||||
/** Selected menu item */
|
||||
|
|
@ -208,35 +194,34 @@ internal sealed interface AddCustomTokenSelectorField {
|
|||
/**
|
||||
* 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 {
|
||||
override val isEnabled = true
|
||||
override val label = TextReference.Res(R.string.custom_token_network_input_title)
|
||||
}
|
||||
) : AddCustomTokenSelectorField
|
||||
|
||||
/**
|
||||
* Derivation path selector model
|
||||
*
|
||||
* @property isEnabled selection availability
|
||||
* @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 isEnabled: Boolean,
|
||||
override val label: TextReference,
|
||||
override val selectedItem: SelectorItem.TitleWithSubtitle,
|
||||
override val items: List<SelectorItem.TitleWithSubtitle>,
|
||||
override val onMenuItemClick: (Int) -> Unit,
|
||||
) : AddCustomTokenSelectorField {
|
||||
override val label = TextReference.Res(R.string.custom_token_derivation_path_input_title)
|
||||
}
|
||||
val isEnabled: Boolean,
|
||||
) : AddCustomTokenSelectorField
|
||||
|
||||
/** Base menu item model */
|
||||
sealed interface SelectorItem {
|
||||
|
|
@ -259,17 +244,40 @@ internal sealed interface AddCustomTokenSelectorField {
|
|||
* Menu item with title ans subtitle
|
||||
*
|
||||
* @property title title text
|
||||
* @property subtitle subtitle text
|
||||
* @property blockchain blockchain
|
||||
* @property subtitle subtitle text
|
||||
*/
|
||||
data class TitleWithSubtitle(
|
||||
override val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
override val blockchain: Blockchain,
|
||||
val subtitle: TextReference,
|
||||
) : SelectorItem
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning model of add custom token screen
|
||||
*
|
||||
* @property description warning description
|
||||
*/
|
||||
internal sealed class AddCustomTokenWarning(val description: TextReference) {
|
||||
|
||||
/** Potential scam warning */
|
||||
object PotentialScamToken : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.custom_token_validation_error_not_found),
|
||||
)
|
||||
|
||||
/** Token already added warning */
|
||||
object TokenAlreadyAdded : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.custom_token_validation_error_already_added),
|
||||
)
|
||||
|
||||
/** Unsupported Solana token warning */
|
||||
object UnsupportedSolanaToken : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.alert_manage_tokens_unsupported_message),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Floating button of add custom token screen
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.models
|
||||
|
||||
/** Custom token type */
|
||||
enum class CustomTokenType { TOKEN, BLOCKCHAIN }
|
||||
|
|
@ -9,4 +9,7 @@ internal interface CustomTokenRouter {
|
|||
|
||||
/** Return to last screen */
|
||||
fun popBackStack()
|
||||
|
||||
/** Open wallet (main) screen */
|
||||
fun openWalletScreen()
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.routers
|
||||
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.store
|
||||
|
||||
|
|
@ -9,4 +10,8 @@ internal class DefaultCustomTokenRouter : CustomTokenRouter {
|
|||
override fun popBackStack() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
|
||||
override fun openWalletScreen() {
|
||||
store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTok
|
|||
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
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
|
||||
/**
|
||||
* State holder of add custom token screen
|
||||
|
|
@ -24,7 +24,7 @@ internal sealed interface AddCustomTokenStateHolder {
|
|||
val form: AddCustomTokenForm
|
||||
|
||||
/** Warnings */
|
||||
val warnings: List<TextReference>
|
||||
val warnings: Set<AddCustomTokenWarning>
|
||||
|
||||
/** Floating button model */
|
||||
val floatingButton: AddCustomTokenFloatingButton
|
||||
|
|
@ -42,7 +42,7 @@ internal sealed interface AddCustomTokenStateHolder {
|
|||
onBackButtonClick: () -> Unit = this.onBackButtonClick,
|
||||
toolbar: AddCustomTokensToolbar = this.toolbar,
|
||||
form: AddCustomTokenForm = this.form,
|
||||
warnings: List<TextReference> = this.warnings,
|
||||
warnings: Set<AddCustomTokenWarning> = this.warnings,
|
||||
floatingButton: AddCustomTokenFloatingButton = this.floatingButton,
|
||||
): AddCustomTokenStateHolder {
|
||||
return when (this) {
|
||||
|
|
@ -64,7 +64,7 @@ internal sealed interface AddCustomTokenStateHolder {
|
|||
override val onBackButtonClick: () -> Unit,
|
||||
override val toolbar: AddCustomTokensToolbar,
|
||||
override val form: AddCustomTokenForm,
|
||||
override val warnings: List<TextReference>,
|
||||
override val warnings: Set<AddCustomTokenWarning>,
|
||||
override val floatingButton: AddCustomTokenFloatingButton,
|
||||
) : AddCustomTokenStateHolder
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ internal sealed interface AddCustomTokenStateHolder {
|
|||
override val onBackButtonClick: () -> Unit,
|
||||
override val toolbar: AddCustomTokensToolbar,
|
||||
override val form: AddCustomTokenForm,
|
||||
override val warnings: List<TextReference>,
|
||||
override val warnings: Set<AddCustomTokenWarning>,
|
||||
override val floatingButton: AddCustomTokenFloatingButton,
|
||||
val testBlock: AddCustomTokenTestBlock,
|
||||
val bottomSheet: AddCustomTokenChooseTokenBottomSheet,
|
||||
|
|
|
|||
|
|
@ -9,22 +9,21 @@ import androidx.compose.foundation.verticalScroll
|
|||
import androidx.compose.material.FabPosition
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
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 com.tangem.blockchain.common.Blockchain
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
|
||||
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.models.AddCustomTokensToolbar
|
||||
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.AddCustomTokenWarning
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
|
||||
|
||||
/**
|
||||
* Add custom token content
|
||||
|
|
@ -37,6 +36,7 @@ import com.tangem.wallet.R
|
|||
internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content) {
|
||||
BackHandler(onBack = state.onBackButtonClick)
|
||||
|
||||
var floatingButtonHeight by remember { mutableStateOf(0.dp) }
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AddCustomTokenToolbar(
|
||||
|
|
@ -44,78 +44,36 @@ internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content) {
|
|||
onBackButtonClick = state.toolbar.onBackButtonClick,
|
||||
)
|
||||
},
|
||||
floatingActionButton = { AddCustomTokenFloatingButton(model = state.floatingButton) },
|
||||
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)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
.padding(bottom = floatingButtonHeight)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
AddCustomTokenForm(model = state.form)
|
||||
|
||||
state.warnings.forEach { description ->
|
||||
key(description) {
|
||||
AddCustomTokenWarning(description)
|
||||
}
|
||||
}
|
||||
AddCustomTokenWarnings(warnings = state.warnings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showSystemUi = true)
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenContent() {
|
||||
TangemTheme {
|
||||
AddCustomTokenContent(
|
||||
state = AddCustomTokenStateHolder.Content(
|
||||
onBackButtonClick = {},
|
||||
toolbar = AddCustomTokensToolbar(
|
||||
title = TextReference.Res(R.string.add_custom_token_title),
|
||||
onBackButtonClick = {},
|
||||
),
|
||||
form = com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm(
|
||||
contractAddressInputField = AddCustomTokenInputField.ContactAddress(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isError = false,
|
||||
isLoading = false,
|
||||
),
|
||||
networkSelectorField = AddCustomTokenSelectorField.Network(
|
||||
selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
|
||||
title = TextReference.Str("Avalanche"),
|
||||
blockchain = Blockchain.Avalanche,
|
||||
),
|
||||
items = listOf(),
|
||||
onMenuItemClick = {},
|
||||
),
|
||||
tokenNameInputField = AddCustomTokenInputField.TokenName(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
isError = false,
|
||||
),
|
||||
tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
isError = false,
|
||||
),
|
||||
decimalsInputField = AddCustomTokenInputField.Decimals(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
isError = false,
|
||||
),
|
||||
derivationPathSelectorField = null,
|
||||
),
|
||||
warnings = listOf(),
|
||||
floatingButton = AddCustomTokenFloatingButton(
|
||||
isEnabled = false,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
AddCustomTokenContent(state = AddCustomTokenPreviewData.createContent())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
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.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.AddCustomTokenInputField
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
|
||||
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
|
||||
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.UnsupportedSolanaToken,
|
||||
)
|
||||
}
|
||||
|
||||
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),
|
||||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
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, 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, onClick = {}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
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.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
|
||||
|
||||
/**
|
||||
|
|
@ -10,11 +14,27 @@ import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTok
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@Composable
|
||||
internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder) {
|
||||
when (stateHolder) {
|
||||
is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(state = stateHolder)
|
||||
is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(state = stateHolder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showSystemUi = true)
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenScreen(
|
||||
@PreviewParameter(AddCustomTokenScreenProvider::class) stateHolder: AddCustomTokenStateHolder,
|
||||
) {
|
||||
TangemTheme {
|
||||
AddCustomTokenScreen(stateHolder)
|
||||
}
|
||||
}
|
||||
|
||||
private class AddCustomTokenScreenProvider : CollectionPreviewParameterProvider<AddCustomTokenStateHolder>(
|
||||
collection = listOf(
|
||||
AddCustomTokenPreviewData.createContent(),
|
||||
AddCustomTokenPreviewData.createTestContent(),
|
||||
),
|
||||
)
|
||||
|
|
@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -19,33 +20,33 @@ import androidx.compose.material.FabPosition
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
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.blockchain.common.Blockchain
|
||||
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.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.AddCustomTokenFloatingButton
|
||||
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.models.AddCustomTokenTestBlock
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
|
||||
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.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -74,6 +75,7 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
|
|||
},
|
||||
)
|
||||
|
||||
var floatingButtonHeight by remember { mutableStateOf(0.dp) }
|
||||
BottomSheetScaffold(
|
||||
sheetContent = {
|
||||
SheetContent(
|
||||
|
|
@ -95,7 +97,16 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
|
|||
},
|
||||
)
|
||||
},
|
||||
floatingActionButton = { AddCustomTokenFloatingButton(model = state.floatingButton) },
|
||||
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,
|
||||
|
|
@ -104,15 +115,19 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(it),
|
||||
.padding(paddingValues = it)
|
||||
.padding(bottom = floatingButtonHeight)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
TestBlock(
|
||||
model = state.testBlock,
|
||||
state.testBlock,
|
||||
coroutineScope,
|
||||
bottomSheetScaffoldState,
|
||||
)
|
||||
|
||||
AddCustomTokenForm(model = state.form)
|
||||
|
||||
AddCustomTokenWarnings(warnings = state.warnings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -243,69 +258,10 @@ private fun TestBlock(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview(showSystemUi = true)
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenTestContent() {
|
||||
TangemTheme {
|
||||
AddCustomTokenTestContent(
|
||||
state = AddCustomTokenStateHolder.TestContent(
|
||||
onBackButtonClick = {},
|
||||
toolbar = AddCustomTokensToolbar(
|
||||
title = TextReference.Res(R.string.add_custom_token_title),
|
||||
onBackButtonClick = {},
|
||||
),
|
||||
form = com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm(
|
||||
contractAddressInputField = AddCustomTokenInputField.ContactAddress(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isError = false,
|
||||
isLoading = false,
|
||||
),
|
||||
networkSelectorField = AddCustomTokenSelectorField.Network(
|
||||
selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
|
||||
title = TextReference.Str(value = "Avalanche"),
|
||||
blockchain = Blockchain.Avalanche,
|
||||
),
|
||||
items = listOf(),
|
||||
onMenuItemClick = {},
|
||||
),
|
||||
tokenNameInputField = AddCustomTokenInputField.TokenName(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
isError = false,
|
||||
),
|
||||
tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
isError = false,
|
||||
),
|
||||
decimalsInputField = AddCustomTokenInputField.Decimals(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
isError = false,
|
||||
),
|
||||
derivationPathSelectorField = null,
|
||||
),
|
||||
warnings = listOf(),
|
||||
floatingButton = AddCustomTokenFloatingButton(
|
||||
isEnabled = false,
|
||||
onClick = {},
|
||||
),
|
||||
testBlock = AddCustomTokenTestBlock(
|
||||
chooseTokenButtonText = "Choose token",
|
||||
clearButtonText = "Clear address",
|
||||
resetButtonText = "Reset",
|
||||
onClearAddressButtonClick = {},
|
||||
onResetButtonClick = {},
|
||||
),
|
||||
bottomSheet = AddCustomTokenChooseTokenBottomSheet(
|
||||
categoriesBlocks = listOf(),
|
||||
onTestTokenClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
AddCustomTokenTestContent(state = AddCustomTokenPreviewData.createTestContent())
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ 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.PrimaryButtonIconLeft
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
|
||||
|
|
@ -16,14 +18,15 @@ import com.tangem.wallet.R
|
|||
/**
|
||||
* Add custom token floating button. Attached above the keyboard.
|
||||
*
|
||||
* @param model button model
|
||||
* @param model button model
|
||||
* @param modifier modifier
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton) {
|
||||
internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, modifier: Modifier = Modifier) {
|
||||
PrimaryButtonIconLeft(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.imePadding()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
|
|
@ -36,16 +39,17 @@ internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton) {
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenFloatingButton_Enabled() {
|
||||
private fun Preview_AddCustomTokenFloatingButton(
|
||||
@PreviewParameter(AddCustomTokenFloatingButtonProvider::class) model: AddCustomTokenFloatingButton,
|
||||
) {
|
||||
TangemTheme {
|
||||
AddCustomTokenFloatingButton(model = AddCustomTokenFloatingButton(isEnabled = true, onClick = {}))
|
||||
AddCustomTokenFloatingButton(model)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenFloatingButton_Disabled() {
|
||||
TangemTheme {
|
||||
AddCustomTokenFloatingButton(model = AddCustomTokenFloatingButton(isEnabled = false, onClick = {}))
|
||||
}
|
||||
}
|
||||
private class AddCustomTokenFloatingButtonProvider : CollectionPreviewParameterProvider<AddCustomTokenFloatingButton>(
|
||||
listOf(
|
||||
AddCustomTokenFloatingButton(isEnabled = true, onClick = {}),
|
||||
AddCustomTokenFloatingButton(isEnabled = false, onClick = {}),
|
||||
),
|
||||
)
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -14,6 +18,7 @@ import androidx.compose.material.ExperimentalMaterialApi
|
|||
import androidx.compose.material.ExposedDropdownMenuBox
|
||||
import androidx.compose.material.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -26,13 +31,14 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
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.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.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
|
||||
/**
|
||||
|
|
@ -68,7 +74,37 @@ internal fun AddCustomTokenForm(model: AddCustomTokenForm) {
|
|||
|
||||
@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
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = model.value,
|
||||
|
|
@ -79,8 +115,8 @@ private fun InputField(model: AddCustomTokenInputField) {
|
|||
text = model.label.resolveReference(),
|
||||
style = TangemTheme.typography.caption,
|
||||
color = TangemTextFieldsDefault.defaultTextFieldColors.labelColor(
|
||||
enabled = model.isEnabled,
|
||||
error = model.isError,
|
||||
enabled = isEnabled,
|
||||
error = isError,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
).value,
|
||||
)
|
||||
|
|
@ -90,20 +126,20 @@ private fun InputField(model: AddCustomTokenInputField) {
|
|||
text = model.placeholder.resolveReference(),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTextFieldsDefault.defaultTextFieldColors
|
||||
.placeholderColor(enabled = model.isEnabled)
|
||||
.placeholderColor(enabled = isEnabled)
|
||||
.value,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
enabled = model.isEnabled,
|
||||
isError = model.isError,
|
||||
enabled = isEnabled,
|
||||
isError = isError,
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = model.isLoading,
|
||||
visible = (model as? AddCustomTokenInputField.ContactAddress)?.isLoading ?: false,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
|
|
@ -124,6 +160,7 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
|
|||
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
|
||||
|
|
@ -132,14 +169,14 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
enabled = model.isEnabled,
|
||||
enabled = isEnabled,
|
||||
label = { Text(text = model.label.resolveReference()) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = isExpanded) },
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
expanded = isExpanded && model.isEnabled,
|
||||
expanded = isExpanded && isEnabled,
|
||||
onDismissRequest = { isExpanded = false },
|
||||
) {
|
||||
FocusRequester
|
||||
|
|
@ -173,44 +210,18 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenForm() {
|
||||
private fun Preview_AddCustomTokenForm(@PreviewParameter(AddCustomTokenFormProvider::class) model: AddCustomTokenForm) {
|
||||
TangemTheme {
|
||||
AddCustomTokenForm(
|
||||
AddCustomTokenForm(
|
||||
contractAddressInputField = AddCustomTokenInputField.ContactAddress(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isError = false,
|
||||
isLoading = false,
|
||||
),
|
||||
networkSelectorField = AddCustomTokenSelectorField.Network(
|
||||
selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
|
||||
title = TextReference.Str(value = "Avalanche"),
|
||||
blockchain = Blockchain.Avalanche,
|
||||
),
|
||||
items = listOf(),
|
||||
onMenuItemClick = {},
|
||||
),
|
||||
tokenNameInputField = AddCustomTokenInputField.TokenName(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
isError = false,
|
||||
),
|
||||
tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
isError = false,
|
||||
),
|
||||
decimalsInputField = AddCustomTokenInputField.Decimals(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
isEnabled = false,
|
||||
isError = false,
|
||||
),
|
||||
derivationPathSelectorField = null,
|
||||
),
|
||||
)
|
||||
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))
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
@ -2,39 +2,56 @@ package com.tangem.tap.features.customtoken.impl.presentation.ui.components
|
|||
|
||||
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.foundation.shape.RoundedCornerShape
|
||||
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.TangemTheme
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
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 warning component
|
||||
* FIXME("Incorrect typography. Replace with typography from design system")
|
||||
* Add custom token warnings
|
||||
*
|
||||
* @param description warning description
|
||||
* @param modifier modifier
|
||||
* @param warnings warnings descriptions set
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun AddCustomTokenWarning(description: TextReference, modifier: Modifier = Modifier) {
|
||||
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,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius4),
|
||||
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),
|
||||
|
|
@ -45,10 +62,18 @@ internal fun AddCustomTokenWarning(description: TextReference, modifier: Modifie
|
|||
style = TangemTheme.typography.body2.copy(fontWeight = FontWeight.Bold),
|
||||
)
|
||||
Text(
|
||||
text = description.resolveReference(),
|
||||
text = warning.description.resolveReference(),
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 18.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AddCustomTokenWarnings() {
|
||||
TangemTheme {
|
||||
AddCustomTokenWarnings(warnings = AddCustomTokenPreviewData.createWarnings())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
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
|
|
@ -15,11 +15,11 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.analytics.events.ManageTokens
|
||||
import com.tangem.tap.common.compose.ClosePopupTrigger
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.customtoken.legacy.compose.AddCustomTokenScreen
|
||||
import com.tangem.tap.features.customtoken.legacy.compose.ClosePopupTrigger
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@ import com.tangem.domain.features.addCustomToken.redux.ScreenState
|
|||
import com.tangem.domain.features.addCustomToken.redux.ViewStates
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.AddCustomTokenWarning
|
||||
import com.tangem.tap.common.compose.ClosePopupTrigger
|
||||
import com.tangem.tap.common.compose.ComposeDialogManager
|
||||
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.tap.features.customtoken.legacy.compose.test.TestCase
|
||||
import com.tangem.tap.features.customtoken.legacy.compose.test.TestCasesList
|
||||
|
|
@ -174,12 +172,14 @@ fun Warnings(warnings: List<AddCustomTokenError.Warning>) {
|
|||
Column {
|
||||
warnings.forEachIndexed { index, item ->
|
||||
val modifier = when (index) {
|
||||
0 -> Modifier.padding(16.dp, 0.dp, 16.dp, 0.dp)
|
||||
warnings.lastIndex -> Modifier.padding(16.dp, 8.dp, 16.dp, 16.dp)
|
||||
else -> Modifier.padding(16.dp, 8.dp, 16.dp, 0.dp)
|
||||
0 -> Modifier.padding(vertical = 0.dp)
|
||||
warnings.lastIndex -> Modifier.padding(top = 8.dp, bottom = 16.dp)
|
||||
else -> Modifier.padding(top = 8.dp, bottom = 0.dp)
|
||||
}
|
||||
AddCustomTokenWarning(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
modifier = modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
warning = item,
|
||||
converter = warningConverter,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.common.compose
|
||||
package com.tangem.tap.features.customtoken.legacy.compose
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.common.compose
|
||||
package com.tangem.tap.features.customtoken.legacy.compose
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -31,17 +31,18 @@ import androidx.compose.ui.unit.sp
|
|||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.domain.DomainDialog
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.domain.redux.global.DomainGlobalAction
|
||||
import com.tangem.domain.redux.global.DomainGlobalState
|
||||
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.tap.features.customtoken.legacy.compose.SelectTokenNetworkDialog
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Composable
|
||||
fun ComposeDialogManager() {
|
||||
internal fun ComposeDialogManager() {
|
||||
val dialogSate = remember { mutableStateOf<DomainDialog?>(null) }
|
||||
val subscriber = remember {
|
||||
object : StoreSubscriber<DomainGlobalState> {
|
||||
|
|
@ -94,27 +95,22 @@ private fun ShowTheDialog(dialogState: MutableState<DomainDialog?>) {
|
|||
* Dialog with single item selection
|
||||
*/
|
||||
@Composable
|
||||
fun <T> SimpleDialog(
|
||||
fun SimpleDialog(
|
||||
title: String,
|
||||
items: List<T>,
|
||||
onSelect: (T) -> Unit,
|
||||
items: List<CoinsResponse.Coin.Network>,
|
||||
onSelect: (CoinsResponse.Coin.Network) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
itemContent: @Composable (T) -> Unit,
|
||||
itemContent: @Composable (CoinsResponse.Coin.Network) -> Unit,
|
||||
) {
|
||||
Dialog(
|
||||
properties = DialogProperties(false, false),
|
||||
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false),
|
||||
onDismissRequest = { },
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(22.dp),
|
||||
) {
|
||||
Surface(modifier = Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.medium) {
|
||||
Column(modifier = Modifier.padding(TangemTheme.dimens.spacing22)) {
|
||||
DialogTitle(title = title)
|
||||
LazyColumn {
|
||||
items(items) { item ->
|
||||
items(items = items, key = CoinsResponse.Coin.Network::networkId) { item ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -17,8 +17,6 @@ import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTo
|
|||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenSymbolChanged
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.BlockchainSpinner
|
||||
import com.tangem.tap.common.compose.ClosePopupTrigger
|
||||
import com.tangem.tap.common.compose.OutlinedTextFieldWidget
|
||||
import com.tangem.tap.common.compose.TitleSubtitle
|
||||
import com.tangem.wallet.R
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.tap.common.compose
|
||||
package com.tangem.tap.features.customtoken.legacy.compose
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -12,6 +13,7 @@ import androidx.core.os.postDelayed
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.tap.common.compose.TangemTextFieldsDefault
|
||||
import com.tangem.tap.common.extensions.ValueCallback
|
||||
|
||||
/**
|
||||
|
|
@ -20,7 +22,7 @@ import com.tangem.tap.common.extensions.ValueCallback
|
|||
@Suppress("MagicNumber")
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun <T> OutlinedSpinner(
|
||||
internal fun <T> OutlinedSpinner(
|
||||
label: String,
|
||||
itemList: List<T>,
|
||||
selectedItem: Field.Data<T>,
|
||||
|
|
@ -71,15 +73,11 @@ fun <T> OutlinedSpinner(
|
|||
)
|
||||
|
||||
if (isEnabled) {
|
||||
ExposedDropdownMenu(
|
||||
expanded = rIsExpanded.value,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
ExposedDropdownMenu(expanded = rIsExpanded.value, onDismissRequest = onDismissRequest) {
|
||||
itemList.forEach { item ->
|
||||
DropdownMenuItem(onClick = { onDropDownItemSelectedInternal(item) }) {
|
||||
when (dropdownItemView) {
|
||||
null -> Text(textFieldConverter(item))
|
||||
else -> dropdownItemView(item)
|
||||
key(item) {
|
||||
DropdownMenuItem(onClick = { onDropDownItemSelectedInternal(item) }) {
|
||||
if (dropdownItemView == null) Text(textFieldConverter(item)) else dropdownItemView(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +89,6 @@ fun <T> OutlinedSpinner(
|
|||
class ClosePopupTrigger {
|
||||
var close: () -> Unit = {}
|
||||
var onCloseComplete: () -> Unit = {}
|
||||
var isTriggered = false
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.customtoken.legacy.compose
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.domain.DomainDialog
|
||||
import com.tangem.tap.common.compose.SimpleDialog
|
||||
import com.tangem.tap.common.compose.TitleSubtitle
|
||||
import com.tangem.wallet.R
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.tap.features.details.ui.common.DetailsMainButton
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -86,13 +85,13 @@ fun CardSettingsReadCard(onScanCardClick: () -> Unit) {
|
|||
Text(
|
||||
text = stringResource(id = R.string.scan_card_settings_title),
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
style = TangemTypography.headline3,
|
||||
style = TangemTheme.typography.h3,
|
||||
)
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.scan_card_settings_message),
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
style = TangemTypography.body1,
|
||||
style = TangemTheme.typography.body1,
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.weight(weight = 1f, fill = false),
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ sealed class CardInfo(
|
|||
)
|
||||
}
|
||||
|
||||
// TODO("Remove and use the same from coreUI")
|
||||
sealed interface TextReference {
|
||||
class Res(@StringRes val id: Int, val formatArgs: List<Any> = emptyList()) : TextReference {
|
||||
constructor(@StringRes id: Int, vararg formatArgs: Any) : this(id, formatArgs.toList())
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconRight
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -145,13 +144,13 @@ fun DetailsRadioButtonElement(title: String, subtitle: String, selected: Boolean
|
|||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
style = TangemTypography.subtitle1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(4.dp))
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = TangemTypography.body2,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.tap.features.details.ui.common.ScreenTitle
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -78,7 +77,7 @@ fun Content(state: DetailsScreenState) {
|
|||
Spacer(modifier = Modifier.size(12.dp))
|
||||
Text(
|
||||
text = "${stringResource(id = state.appNameRes)} ${state.tangemVersion}",
|
||||
style = TangemTypography.caption,
|
||||
style = TangemTheme.typography.caption,
|
||||
color = colorResource(id = R.color.text_tertiary),
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 40.dp),
|
||||
)
|
||||
|
|
@ -111,13 +110,13 @@ fun WalletConnectDetailsItem(onItemsClick: (SettingsElement) -> Unit) {
|
|||
Text(
|
||||
text = stringResource(id = R.string.wallet_connect_title),
|
||||
modifier = Modifier.padding(end = 20.dp, bottom = 4.dp),
|
||||
style = TangemTypography.headline3,
|
||||
style = TangemTheme.typography.h3,
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.wallet_connect_subtitle),
|
||||
modifier = Modifier.padding(end = 20.dp, bottom = 4.dp),
|
||||
style = TangemTypography.body1,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
)
|
||||
}
|
||||
|
|
@ -144,13 +143,13 @@ fun DetailsItem(item: SettingsElement, appCurrency: String, onItemsClick: () ->
|
|||
Text(
|
||||
text = stringResource(id = item.titleRes),
|
||||
modifier = Modifier,
|
||||
style = TangemTypography.subtitle1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
)
|
||||
if (item == SettingsElement.AppCurrency) {
|
||||
Text(
|
||||
text = appCurrency,
|
||||
style = TangemTypography.body2,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -105,7 +105,7 @@ private fun EmptyScreen(state: WalletConnectScreenState) {
|
|||
Spacer(modifier = Modifier.size(24.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.wallet_connect_subtitle),
|
||||
style = TangemTypography.body2,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = colorResource(id = R.color.text_tertiary),
|
||||
)
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ private fun WalletConnectSessions(state: WalletConnectScreenState) {
|
|||
) {
|
||||
Text(
|
||||
text = session.description,
|
||||
style = TangemTypography.subtitle1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.features.disclaimer
|
||||
|
||||
import com.tangem.tap.persistence.DisclaimerPrefStorage
|
||||
import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.tap.features.disclaimer
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.tap.persistence.DisclaimerPrefStorage
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -91,8 +91,7 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
|
|||
onSearchTokensClick = {
|
||||
Analytics.send(IntroductionProcess.ButtonTokensList())
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
|
||||
store.dispatch(TokensAction.AllowToAddTokens(false))
|
||||
store.dispatch(TokensAction.LoadCurrencies())
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@ object OnboardingHelper {
|
|||
cardInfoStorage.isActivationInProgress(cardId)
|
||||
}
|
||||
}
|
||||
|
||||
response.cardTypesResolver.isWallet2() -> {
|
||||
val emptyWallets = response.card.wallets.isEmpty()
|
||||
val activationInProgress = cardInfoStorage.isActivationInProgress(cardId)
|
||||
val backupNotActive = response.card.backupStatus?.isActive != true
|
||||
emptyWallets || activationInProgress || backupNotActive
|
||||
}
|
||||
|
||||
response.card.wallets.isNotEmpty() -> cardInfoStorage.isActivationInProgress(cardId)
|
||||
else -> true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
|
|||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.hasPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.persistence.UsedCardsPrefStorage
|
||||
import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class OnboardingWalletAction : Action {
|
||||
object Init : OnboardingWalletAction()
|
||||
object GetToCreateWalletStep : OnboardingWalletAction()
|
||||
object GetToSaltPayStep : OnboardingWalletAction()
|
||||
|
||||
object CreateWallet : OnboardingWalletAction()
|
||||
data class WalletWasCreated(
|
||||
val result: CompletionResult<CreateProductWalletTaskResponse>,
|
||||
) : OnboardingWalletAction()
|
||||
|
||||
object Done : OnboardingWalletAction()
|
||||
object FinishOnboarding : OnboardingWalletAction()
|
||||
|
||||
|
|
@ -19,6 +28,25 @@ sealed class OnboardingWalletAction : Action {
|
|||
object OnBackPressed : OnboardingWalletAction()
|
||||
}
|
||||
|
||||
sealed class OnboardingWallet2Action : OnboardingWalletAction() {
|
||||
data class Init(val maxProgress: Int) : OnboardingWallet2Action()
|
||||
data class SetDependencies(val maxProgress: Int) : OnboardingWallet2Action()
|
||||
|
||||
data class CreateWallet(
|
||||
val callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) : OnboardingWallet2Action()
|
||||
|
||||
data class ImportWallet(
|
||||
val mnemonicComponents: List<String>,
|
||||
val seedPhraseSource: SeedPhraseSource,
|
||||
val callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) : OnboardingWallet2Action()
|
||||
|
||||
data class WalletWasCreated(
|
||||
val result: CompletionResult<CreateWalletResponse>,
|
||||
) : OnboardingWallet2Action()
|
||||
}
|
||||
|
||||
sealed class BackupAction : Action {
|
||||
|
||||
object IntroduceBackup : BackupAction()
|
||||
|
|
@ -62,4 +90,6 @@ sealed class BackupAction : Action {
|
|||
object DiscardBackup : BackupAction()
|
||||
object DiscardSavedBackup : BackupAction()
|
||||
object ResumeFoundUnfinishedBackup : BackupAction()
|
||||
|
||||
data class ResetBackupCard(val cardId: String) : BackupAction()
|
||||
}
|
||||
|
|
@ -3,15 +3,19 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
|
|||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -20,6 +24,7 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
|
|
@ -29,12 +34,6 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivat
|
|||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdk
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
|
|
@ -47,7 +46,10 @@ object OnboardingWalletMiddleware {
|
|||
private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleWalletAction(action, state, dispatch)
|
||||
when (action) {
|
||||
is OnboardingWallet2Action -> handleWallet2Action(action, state)
|
||||
else -> handleWalletAction(action, state, dispatch)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
|
|
@ -114,42 +116,44 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
}
|
||||
}
|
||||
is OnboardingWalletAction.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
scanResponse ?: return@launch
|
||||
val result = tangemSdkManager.createProductWallet(scanResponse)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
// here we must use updated scanResponse after createWallet & derivation
|
||||
val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
store.state.globalState.topUpController?.registerEmptyWallet(updatedResponse)
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(result))
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.WalletWasCreated -> {
|
||||
scanResponse ?: return
|
||||
when (val result = action.result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
// here we must use updated scanResponse after createWallet & derivation
|
||||
val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
store.state.globalState.topUpController?.registerEmptyWallet(updatedResponse)
|
||||
|
||||
val blockchainNetworks = if (DemoHelper.isDemoCardId(result.data.card.cardId)) {
|
||||
DemoHelper.config.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}.map { blockchain ->
|
||||
BlockchainNetwork(blockchain, result.data.card)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
userTokensRepository.saveUserTokens(
|
||||
card = result.data.card,
|
||||
tokens = blockchainNetworks.toCurrencies(),
|
||||
)
|
||||
}
|
||||
startCardActivation(updatedResponse)
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
val blockchainNetworks = if (DemoHelper.isDemoCardId(result.data.card.cardId)) {
|
||||
DemoHelper.config.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}.map { blockchain ->
|
||||
BlockchainNetwork(blockchain, result.data.card)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
userTokensRepository.saveUserTokens(
|
||||
card = result.data.card,
|
||||
tokens = blockchainNetworks.toCurrencies(),
|
||||
)
|
||||
}
|
||||
startCardActivation(updatedResponse)
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
}
|
||||
OnboardingWalletAction.FinishOnboarding -> {
|
||||
|
|
@ -194,6 +198,99 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> AppState?) {
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse
|
||||
|
||||
when (action) {
|
||||
is OnboardingWallet2Action.Init -> {
|
||||
if (scanResponse?.cardTypesResolver?.isWallet2() == true) {
|
||||
store.dispatch(OnboardingWallet2Action.SetDependencies(action.maxProgress))
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (val result = tangemSdkManager.createProductWallet(scanResponse)) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
withMainContext {
|
||||
action.callback(mediateResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.ImportWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (
|
||||
val result = tangemSdkManager.importWallet(
|
||||
scanResponse = scanResponse,
|
||||
mnemonic = action.mnemonicComponents.joinToString(" "),
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val creationType = when (action.seedPhraseSource) {
|
||||
SeedPhraseSource.IMPORTED -> AnalyticsParam.WalletCreationType.SeedImport
|
||||
SeedPhraseSource.GENERATED -> AnalyticsParam.WalletCreationType.NewSeed
|
||||
}
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully(creationType))
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
withMainContext {
|
||||
action.callback(mediateResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.WalletWasCreated -> {
|
||||
val result = when (val mediateResult = action.result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = CreateProductWalletTaskResponse(
|
||||
card = mediateResult.data.card,
|
||||
derivedKeys = mediateResult.data.derivedKeys,
|
||||
primaryCard = mediateResult.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(mediateResult.error)
|
||||
}
|
||||
}
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(result))
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateScanResponseAfterBackup(scanResponse: ScanResponse, backupState: BackupState): ScanResponse {
|
||||
val card = if (backupState.backupCardsNumber > 0) {
|
||||
val cardsCount = backupState.backupCardsNumber
|
||||
|
|
@ -263,7 +360,19 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.Success)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
val error = result.error
|
||||
if (error is TangemSdkError.BackupFailedNotEmptyWallets &&
|
||||
onboardingWalletState.wallet2State != null
|
||||
) {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
BackupDialog.ResetBackupCard(error.cardId),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -368,6 +477,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
Analytics.send(Onboarding.Finished())
|
||||
finishCardActivation(notActivatedCardIds)
|
||||
}
|
||||
|
||||
is BackupAction.ResetBackupCard -> {
|
||||
scope.launch { tangemSdkManager.resetToFactorySettings(action.cardId) }
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet
|
|||
is GlobalAction.Onboarding -> ReducerForGlobalAction.reduce(action, state)
|
||||
is OnboardingSaltPayAction -> OnboardingSaltPayReducer.reduce(action, state)
|
||||
is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState, state.isSaltPay))
|
||||
|
||||
is OnboardingWalletAction.GetToCreateWalletStep -> OnboardingWalletState(
|
||||
is OnboardingWallet2Action -> OnboardingWallet2Reducer.reduce(action, state)
|
||||
is OnboardingWalletAction.GetToCreateWalletStep -> state.copy(
|
||||
step = OnboardingWalletStep.CreateWallet,
|
||||
)
|
||||
is OnboardingWalletAction.GetToSaltPayStep -> state.copy(
|
||||
|
|
@ -65,6 +65,17 @@ private object ReducerForGlobalAction {
|
|||
private fun maxBackupCards(isSaltPay: Boolean): Int = if (isSaltPay) 1 else 2
|
||||
}
|
||||
|
||||
private object OnboardingWallet2Reducer {
|
||||
fun reduce(action: OnboardingWallet2Action, state: OnboardingWalletState): OnboardingWalletState = when (action) {
|
||||
is OnboardingWallet2Action.SetDependencies -> state.copy(
|
||||
wallet2State = OnboardingWallet2State(
|
||||
maxProgress = action.maxProgress,
|
||||
),
|
||||
)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private object BackupReducer {
|
||||
@Suppress("ComplexMethod")
|
||||
fun reduce(action: BackupAction, state: BackupState, isSaltPay: Boolean): BackupState {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import org.rekotlin.StateType
|
|||
*/
|
||||
data class OnboardingWalletState(
|
||||
val step: OnboardingWalletStep = OnboardingWalletStep.None,
|
||||
val wallet2State: OnboardingWallet2State? = null,
|
||||
val backupState: BackupState = BackupState(),
|
||||
val onboardingSaltPayState: OnboardingSaltPayState? = null,
|
||||
val isSaltPay: Boolean = false,
|
||||
|
|
@ -31,14 +32,14 @@ data class OnboardingWalletState(
|
|||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun getMaxProgress(): Int = when {
|
||||
isSaltPay -> 12
|
||||
else -> 6
|
||||
fun getMaxProgress(): Int {
|
||||
val baseProgress = if (isSaltPay) 12 else 6
|
||||
return getWallet2Progress() + baseProgress
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "MagicNumber")
|
||||
fun getProgressStep(): Int {
|
||||
return when {
|
||||
val progressByStep = when {
|
||||
step == OnboardingWalletStep.CreateWallet -> 1
|
||||
step == OnboardingWalletStep.Backup -> {
|
||||
when (backupState.backupStep) {
|
||||
|
|
@ -69,11 +70,19 @@ data class OnboardingWalletState(
|
|||
step == OnboardingWalletStep.Done -> getMaxProgress()
|
||||
else -> 1
|
||||
}
|
||||
|
||||
return getWallet2Progress() + progressByStep
|
||||
}
|
||||
|
||||
private fun getWallet2Progress(): Int = wallet2State?.maxProgress ?: 0
|
||||
}
|
||||
|
||||
data class OnboardingWallet2State(
|
||||
val maxProgress: Int,
|
||||
)
|
||||
|
||||
enum class OnboardingWalletStep {
|
||||
None, CreateWallet, Backup, SaltPay, Done
|
||||
None, CreateWallet, SaltPay, Backup, Done
|
||||
}
|
||||
|
||||
data class BackupState(
|
||||
|
|
@ -113,4 +122,5 @@ sealed class BackupDialog : StateDialog {
|
|||
object BackupInProgress : BackupDialog()
|
||||
object UnfinishedBackupFound : BackupDialog()
|
||||
object ConfirmDiscardingBackup : BackupDialog()
|
||||
data class ResetBackupCard(val cardId: String) : BackupDialog()
|
||||
}
|
||||
|
|
@ -18,15 +18,7 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.compose.PinCodeWidget
|
||||
import com.tangem.tap.common.extensions.configureSettings
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.setDrawable
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.extensions.stop
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -34,11 +26,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget
|
||||
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebChromeClient
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebViewClient
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
|
||||
|
|
@ -56,7 +44,7 @@ import com.tangem.wallet.databinding.LayoutOnboardingMainBinding
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class OnboardingSaltPayView(
|
||||
internal class OnboardingSaltPayStateHandler(
|
||||
private val walletFragment: OnboardingWalletFragment,
|
||||
) {
|
||||
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhrase
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseApi
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class OnboardingSeedPhraseStateHandler(
|
||||
private val onboardingSeedPhraseApi: OnboardingSeedPhraseApi = OnboardingSeedPhrase(),
|
||||
) {
|
||||
|
||||
fun newState(
|
||||
walletFragment: OnboardingWalletFragment,
|
||||
state: OnboardingWalletState,
|
||||
seedPhraseViewModel: SeedPhraseViewModel,
|
||||
) {
|
||||
if (state.step == OnboardingWalletStep.CreateWallet && !seedPhraseViewModel.isFinished) {
|
||||
switchToWallet2SeedPhraseOnboarding(walletFragment, seedPhraseViewModel, state.getMaxProgress())
|
||||
} else {
|
||||
switchToWalletOnboarding(walletFragment, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchToWalletOnboarding(walletFragment: OnboardingWalletFragment, state: OnboardingWalletState) {
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.hide()
|
||||
walletFragment.pbBinding.pbState.show()
|
||||
walletFragment.binding.onboardingWalletContainer.show()
|
||||
walletFragment.handleOnboardingStep(state)
|
||||
}
|
||||
|
||||
private fun switchToWallet2SeedPhraseOnboarding(
|
||||
walletFragment: OnboardingWalletFragment,
|
||||
viewModel: SeedPhraseViewModel,
|
||||
onboardingWalletMaxProgress: Int,
|
||||
) {
|
||||
walletFragment.pbBinding.pbState.hide()
|
||||
walletFragment.binding.onboardingWalletContainer.hide()
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.show()
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.setContent {
|
||||
onboardingSeedPhraseApi.ScreenContent(
|
||||
uiState = viewModel.uiState,
|
||||
subScreen = viewModel.currentScreen.collectAsState().value,
|
||||
progress = viewModel.progress.collectAsState(0).value.toFloat() / onboardingWalletMaxProgress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import android.widget.FrameLayout
|
|||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.transition.TransitionInflater
|
||||
import androidx.transition.TransitionManager
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
|
|
@ -17,46 +18,56 @@ import coil.load
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.tangem.common.CardIdFormatter
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseRouter
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.inflate
|
||||
import com.tangem.tap.common.extensions.resourceUri
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.BaseFragment
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayView
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayStateHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingSaltpayBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingSeedPhraseBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Suppress("LargeClass", "MagicNumber")
|
||||
@AndroidEntryPoint
|
||||
class OnboardingWalletFragment :
|
||||
BaseFragment(R.layout.fragment_onboarding_wallet),
|
||||
StoreSubscriber<OnboardingWalletState>,
|
||||
FragmentOnBackPressedHandler {
|
||||
|
||||
private val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
|
||||
internal val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind)
|
||||
internal val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
|
||||
|
||||
internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer }
|
||||
internal val bindingSaltPay: LayoutOnboardingSaltpayBinding by lazy { binding.onboardingSaltpayContainer }
|
||||
|
||||
private val onboardingSaltPayView: OnboardingSaltPayView = OnboardingSaltPayView(this)
|
||||
private val seedPhraseStateHandler: OnboardingSeedPhraseStateHandler = OnboardingSeedPhraseStateHandler()
|
||||
private val seedPhraseViewModel by viewModels<SeedPhraseViewModel>()
|
||||
|
||||
private val saltPayStateHandler: OnboardingSaltPayStateHandler = OnboardingSaltPayStateHandler(this)
|
||||
|
||||
private lateinit var cardsWidget: WalletCardsWidget
|
||||
private var accessCodeDialog: AccessCodeDialog? = null
|
||||
|
|
@ -69,6 +80,13 @@ class OnboardingWalletFragment :
|
|||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
seedPhraseViewModel.setRouter(makeSeedPhraseRouter())
|
||||
seedPhraseViewModel.setMediator(makeSeedPhraseMediator())
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
@ -88,6 +106,7 @@ class OnboardingWalletFragment :
|
|||
binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
addBackPressHandler(this)
|
||||
|
||||
store.dispatch(OnboardingWallet2Action.Init(seedPhraseViewModel.maxProgress))
|
||||
store.dispatch(OnboardingWalletAction.Init)
|
||||
store.dispatch(OnboardingSaltPayAction.Init)
|
||||
store.dispatch(
|
||||
|
|
@ -97,7 +116,7 @@ class OnboardingWalletFragment :
|
|||
)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider()
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider()
|
||||
|
||||
private fun reInitCardsWidgetIfNeeded(backupCardsCounts: Int) = with(binding) {
|
||||
val viewBackupCount = flCardsContainer.childCount - 1
|
||||
|
|
@ -159,13 +178,19 @@ class OnboardingWalletFragment :
|
|||
pbBinding.pbState.max = state.getMaxProgress()
|
||||
pbBinding.pbState.progress = state.getProgressStep()
|
||||
|
||||
if (state.isSaltPay) {
|
||||
onboardingSaltPayView.newState(state)
|
||||
} else {
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard)
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard)
|
||||
handleOnboardingStep(state)
|
||||
when {
|
||||
state.wallet2State != null -> {
|
||||
seedPhraseStateHandler.newState(this, state, seedPhraseViewModel)
|
||||
}
|
||||
state.isSaltPay -> {
|
||||
saltPayStateHandler.newState(state)
|
||||
}
|
||||
else -> {
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard)
|
||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard)
|
||||
handleOnboardingStep(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +205,12 @@ class OnboardingWalletFragment :
|
|||
internal fun handleOnboardingStep(state: OnboardingWalletState) {
|
||||
when (state.step) {
|
||||
OnboardingWalletStep.CreateWallet -> setupCreateWalletState()
|
||||
OnboardingWalletStep.Backup -> setBackupState(state.backupState, state.isSaltPay)
|
||||
OnboardingWalletStep.Backup -> setBackupState(
|
||||
state = state.backupState,
|
||||
isSaltPay = state.isSaltPay,
|
||||
isWallet2 = state.wallet2State != null,
|
||||
)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
|
@ -201,9 +231,9 @@ class OnboardingWalletFragment :
|
|||
animator.setupCreateWalletState()
|
||||
}
|
||||
|
||||
private fun setBackupState(state: BackupState, isSaltPay: Boolean) {
|
||||
private fun setBackupState(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) {
|
||||
when (state.backupStep) {
|
||||
BackupStep.InitBackup -> showBackupIntro(state, isSaltPay)
|
||||
BackupStep.InitBackup -> showBackupIntro(state, isSaltPay, isWallet2)
|
||||
BackupStep.ScanOriginCard -> showScanOriginCard()
|
||||
BackupStep.AddBackupCards -> showAddBackupCards(state, isSaltPay)
|
||||
BackupStep.SetAccessCode -> showSetAccessCode()
|
||||
|
|
@ -215,7 +245,7 @@ class OnboardingWalletFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun showBackupIntro(state: BackupState, isSaltPay: Boolean) = with(binding) {
|
||||
private fun showBackupIntro(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) = with(binding) {
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
|
||||
|
|
@ -230,7 +260,7 @@ class OnboardingWalletFragment :
|
|||
|
||||
btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
|
||||
btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.SkipBackup) }
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup && !isSaltPay)
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup && !isSaltPay && !isWallet2)
|
||||
}
|
||||
animator.showBackupIntro(state)
|
||||
}
|
||||
|
|
@ -271,7 +301,7 @@ class OnboardingWalletFragment :
|
|||
layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0
|
||||
|
||||
if (isSaltPay) {
|
||||
onboardingSaltPayView.showAddBackupCards(state)
|
||||
saltPayStateHandler.showAddBackupCards(state)
|
||||
} else {
|
||||
when (state.backupCardsNumber) {
|
||||
0 -> {
|
||||
|
|
@ -443,4 +473,47 @@ class OnboardingWalletFragment :
|
|||
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
|
||||
return typedValue.float
|
||||
}
|
||||
|
||||
private fun makeSeedPhraseRouter(): SeedPhraseRouter = SeedPhraseRouter(
|
||||
onBack = ::handleOnBackPressed,
|
||||
onOpenChat = {
|
||||
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
|
||||
},
|
||||
onOpenUriClick = { uri ->
|
||||
store.dispatchOpenUrl(uri.toString())
|
||||
},
|
||||
)
|
||||
|
||||
private fun makeSeedPhraseMediator(): SeedPhraseMediator {
|
||||
return object : SeedPhraseMediator {
|
||||
override fun createWallet(callback: (CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
store.dispatch(OnboardingWallet2Action.CreateWallet(callback))
|
||||
}
|
||||
|
||||
override fun onWalletCreated(result: CompletionResult<CreateWalletResponse>) {
|
||||
store.dispatch(OnboardingWallet2Action.WalletWasCreated(result))
|
||||
}
|
||||
|
||||
override fun importWallet(
|
||||
mnemonicComponents: List<String>,
|
||||
seedPhraseSource: SeedPhraseSource,
|
||||
callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) {
|
||||
store.dispatch(OnboardingWallet2Action.ImportWallet(mnemonicComponents, seedPhraseSource, callback))
|
||||
}
|
||||
|
||||
override fun allowScreenshots(allow: Boolean) {
|
||||
mainScope.launch {
|
||||
if (allow) {
|
||||
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
} else {
|
||||
requireActivity().window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object ResetBackupCardDialog {
|
||||
fun create(context: Context, cardId: String): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.common_warning)
|
||||
setMessage(R.string.onboarding_linking_error_card_with_wallets)
|
||||
setPositiveButton(R.string.common_continue) { _, _ ->
|
||||
store.dispatch(BackupAction.ResetBackupCard(cardId))
|
||||
}
|
||||
setNegativeButton(R.string.common_cancel) { _, _ ->
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -120,9 +120,16 @@ internal class SaveWalletMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
val savedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("User wallet is not saved")
|
||||
return@launch
|
||||
}
|
||||
store.dispatchWithMain(SaveWalletAction.Save.Success)
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true))
|
||||
store.dispatchWithMain(
|
||||
action = WalletAction.MultiWallet.CheckForBackupWarning(savedUserWallet.scanResponse.card),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ sealed class TransactionExtrasAction : SendScreenActionUi {
|
|||
sealed class TonMemo : TransactionExtrasAction() {
|
||||
data class HandleUserInput(val data: String) : TonMemo()
|
||||
}
|
||||
|
||||
sealed class CosmosMemo : TransactionExtrasAction() {
|
||||
data class HandleUserInput(val data: String) : CosmosMemo()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class AddressPayIdVerifyAction : SendScreenAction {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class AmountMiddleware {
|
|||
val sendState = appState?.sendState ?: return
|
||||
|
||||
dispatch(AmountAction.SetAmount(sendState.amountState.balanceCrypto, false))
|
||||
if (sendState.amountState.isCoinAmount()) {
|
||||
if (sendState.amountState.canIncludeFee()) {
|
||||
dispatch(FeeAction.ChangeLayoutVisibility(main = true, controls = true))
|
||||
dispatch(FeeActionUi.ChangeIncludeFee(true))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.send.redux.middlewares
|
|||
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ton.TonTransactionExtras
|
||||
|
|
@ -171,6 +172,7 @@ private fun sendTransaction(
|
|||
transactionExtras.xrpDestinationTag?.tag?.let {
|
||||
txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it))
|
||||
}
|
||||
transactionExtras.cosmosMemoState?.memo?.let { txData = txData.copy(extras = CosmosTransactionExtras(it)) }
|
||||
transactionExtras.tonMemoState?.memo?.let { txData = txData.copy(extras = TonTransactionExtras(it)) }
|
||||
|
||||
scope.launch {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,10 @@
|
|||
package com.tangem.tap.features.send.redux.reducers
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.CurrencyConverter
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
|
||||
import com.tangem.tap.features.send.redux.AmountAction
|
||||
import com.tangem.tap.features.send.redux.AmountActionUi
|
||||
import com.tangem.tap.features.send.redux.FeeAction
|
||||
import com.tangem.tap.features.send.redux.FeeActionUi
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.send.redux.ReceiptAction
|
||||
import com.tangem.tap.features.send.redux.ReleaseSendState
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
import com.tangem.tap.features.send.redux.states.ExternalTransactionData
|
||||
import com.tangem.tap.features.send.redux.states.IdStateHolder
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
|
|
@ -98,21 +88,28 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer {
|
|||
val walletManager = action.walletManager!!
|
||||
val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!!
|
||||
val decimals = amountToExtract.decimals
|
||||
val feePaidInNetworkCurrency = isFeePaidInNetworkCurrency(walletManager.wallet.blockchain)
|
||||
|
||||
return sendState.copy(
|
||||
walletManager = walletManager,
|
||||
coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) },
|
||||
tokenConverter = action.tokenRate?.let { CurrencyConverter(it, decimals) },
|
||||
amountState = sendState.amountState.copy(
|
||||
feePaidInCurrencyNetworkCurrency = feePaidInNetworkCurrency,
|
||||
amountToExtract = amountToExtract,
|
||||
typeOfAmount = amountToExtract.type,
|
||||
balanceCrypto = amountToExtract.value ?: BigDecimal.ZERO,
|
||||
),
|
||||
feeState = sendState.feeState.copy(
|
||||
includeFeeSwitcherIsEnabled = amountToExtract.type == AmountType.Coin,
|
||||
includeFeeSwitcherIsEnabled = feePaidInNetworkCurrency || isCoinAmount(amountToExtract.type),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun isFeePaidInNetworkCurrency(blockchain: Blockchain): Boolean =
|
||||
blockchain.tokenTransactionFeePaidInNetworkCurrency()
|
||||
|
||||
private fun isCoinAmount(typeOfAmount: AmountType): Boolean = typeOfAmount == AmountType.Coin
|
||||
}
|
||||
|
||||
internal fun updateLastState(sendState: SendState, lastChangedState: IdStateHolder): SendState {
|
||||
|
|
|
|||
|
|
@ -3,21 +3,8 @@ package com.tangem.tap.features.send.redux.reducers
|
|||
import com.tangem.blockchain.blockchains.stellar.StellarMemo
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction.BinanceMemo
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction.Prepare
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction.Release
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction.TonMemo
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction.XlmMemo
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction.XrpDestinationTag
|
||||
import com.tangem.tap.features.send.redux.states.BinanceMemoState
|
||||
import com.tangem.tap.features.send.redux.states.InputViewValue
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import com.tangem.tap.features.send.redux.states.TonMemoState
|
||||
import com.tangem.tap.features.send.redux.states.TransactionExtraError
|
||||
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
|
||||
import com.tangem.tap.features.send.redux.states.XlmMemoState
|
||||
import com.tangem.tap.features.send.redux.states.XlmMemoType
|
||||
import com.tangem.tap.features.send.redux.states.XrpDestinationTagState
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction.*
|
||||
import com.tangem.tap.features.send.redux.states.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -31,6 +18,7 @@ class TransactionExtrasReducer : SendInternalReducer {
|
|||
is BinanceMemo -> handleBinanceMemo(action, sendState, sendState.transactionExtrasState)
|
||||
is XrpDestinationTag -> handleXrpTag(action, sendState, sendState.transactionExtrasState)
|
||||
is TonMemo -> handleTonMemo(action, sendState, sendState.transactionExtrasState)
|
||||
is CosmosMemo -> handleCosmosMemo(action, sendState, sendState.transactionExtrasState)
|
||||
else -> sendState
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +48,10 @@ class TransactionExtrasReducer : SendInternalReducer {
|
|||
Blockchain.Stellar -> TransactionExtrasState(xlmMemo = XlmMemoState())
|
||||
Blockchain.Binance -> TransactionExtrasState(binanceMemo = BinanceMemoState())
|
||||
Blockchain.TON, Blockchain.TONTestnet -> TransactionExtrasState(tonMemoState = TonMemoState())
|
||||
Blockchain.Cosmos,
|
||||
Blockchain.TerraV1,
|
||||
Blockchain.TerraV2,
|
||||
-> TransactionExtrasState(cosmosMemoState = CosmosMemoState())
|
||||
else -> emptyResult
|
||||
}
|
||||
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||
|
|
@ -155,4 +147,19 @@ class TransactionExtrasReducer : SendInternalReducer {
|
|||
}
|
||||
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||
}
|
||||
|
||||
private fun handleCosmosMemo(
|
||||
action: CosmosMemo,
|
||||
sendState: SendState,
|
||||
infoState: TransactionExtrasState,
|
||||
): SendState {
|
||||
val result = when (action) {
|
||||
is CosmosMemo.HandleUserInput -> {
|
||||
val memo = action.data
|
||||
val input = InputViewValue(memo, true)
|
||||
infoState.copy(cosmosMemoState = CosmosMemoState(input, memo))
|
||||
}
|
||||
}
|
||||
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ data class TransactionExtrasState(
|
|||
val binanceMemo: BinanceMemoState? = null,
|
||||
val xrpDestinationTag: XrpDestinationTagState? = null,
|
||||
val tonMemoState: TonMemoState? = null,
|
||||
val cosmosMemoState: CosmosMemoState? = null,
|
||||
) : IdStateHolder {
|
||||
override val stateId: StateId = StateId.TRANSACTION_EXTRAS
|
||||
}
|
||||
|
|
@ -104,6 +105,11 @@ data class TonMemoState(
|
|||
val memo: String? = null,
|
||||
val error: TransactionExtraError? = null,
|
||||
)
|
||||
data class CosmosMemoState(
|
||||
val viewFieldValue: InputViewValue = InputViewValue(""),
|
||||
val memo: String? = null,
|
||||
val error: TransactionExtraError? = null,
|
||||
)
|
||||
|
||||
enum class TransactionExtraError {
|
||||
INVALID_DESTINATION_TAG,
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ data class SendState(
|
|||
fun getButtonState(): ButtonState = if (isReadyToSend()) ButtonState.ENABLED else ButtonState.DISABLED
|
||||
|
||||
fun getTotalAmountToSend(value: BigDecimal = amountState.amountToSendCrypto): BigDecimal {
|
||||
val needToExtractFee = amountState.isCoinAmount() && feeState.feeIsIncluded
|
||||
val needToExtractFee = amountState.canIncludeFee() && feeState.feeIsIncluded
|
||||
return if (needToExtractFee) value.minus(feeState.getCurrentFeeValue()) else value
|
||||
}
|
||||
|
||||
|
|
@ -135,13 +135,16 @@ data class AmountState(
|
|||
val decimalSeparator: String = ".",
|
||||
val error: TapError? = null,
|
||||
val inputIsEnabled: Boolean = true,
|
||||
private val feePaidInCurrencyNetworkCurrency: Boolean = false,
|
||||
) : SendScreenState {
|
||||
|
||||
override val stateId: StateId = StateId.AMOUNT
|
||||
|
||||
fun isReady(): Boolean = error == null && !amountToSendCrypto.isZero()
|
||||
|
||||
fun isCoinAmount(): Boolean = typeOfAmount == AmountType.Coin
|
||||
private fun isCoinAmount(): Boolean = typeOfAmount == AmountType.Coin
|
||||
|
||||
fun canIncludeFee(): Boolean = isCoinAmount() || feePaidInCurrencyNetworkCurrency
|
||||
|
||||
fun createMainCurrency(type: MainCurrencyType, canSwitched: Boolean): MainCurrency {
|
||||
return if (!canSwitched) {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ import com.tangem.tap.mainScope
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentSendBinding
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
|
|
@ -69,9 +70,12 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import java.text.DecimalFormatSymbols
|
||||
|
||||
private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(FlowPreview::class)
|
||||
class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
||||
|
||||
lateinit var sendBtn: ViewStateWidget
|
||||
|
|
@ -115,16 +119,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
sendBtn = IndeterminateProgressButtonWidget(btnSend, progress)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun setupAddressOrPayIdLayout() = with(binding.lSendAddressPayid) {
|
||||
store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") })
|
||||
store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString()))
|
||||
|
||||
etAddressOrPayId.setOnFocusChangeListener { v, hasFocus ->
|
||||
etAddressOrPayId.setOnFocusChangeListener { _, hasFocus ->
|
||||
store.dispatch(TruncateOrRestore(!hasFocus))
|
||||
}
|
||||
etAddressOrPayId.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
|
||||
.filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it }
|
||||
.onEach {
|
||||
store.dispatch(AddressPayIdActionUi.HandleUserInput(it))
|
||||
|
|
@ -146,8 +149,9 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
}
|
||||
|
||||
private fun setupTransactionExtrasLayout() = with(binding.lSendAddressPayid) {
|
||||
// TODO: [REDACTED_TASK_KEY]
|
||||
etXlmMemo.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
|
||||
.filter {
|
||||
val info = store.state.sendState.transactionExtrasState
|
||||
info.xlmMemo?.viewFieldValue?.value != it
|
||||
|
|
@ -156,7 +160,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
.launchIn(mainScope)
|
||||
|
||||
etDestinationTag.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
|
||||
.filter {
|
||||
val info = store.state.sendState.transactionExtrasState
|
||||
info.xrpDestinationTag?.viewFieldValue?.value != it
|
||||
|
|
@ -165,7 +169,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
.launchIn(mainScope)
|
||||
|
||||
etBinanceMemo.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
|
||||
.filter {
|
||||
val info = store.state.sendState.transactionExtrasState
|
||||
info.binanceMemo?.viewFieldValue?.value != it
|
||||
|
|
@ -174,13 +178,22 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
.launchIn(mainScope)
|
||||
|
||||
etTonMemo.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
|
||||
.filter {
|
||||
val info = store.state.sendState.transactionExtrasState
|
||||
info.tonMemoState?.viewFieldValue?.value != it
|
||||
}
|
||||
.onEach { store.dispatch(TransactionExtrasAction.TonMemo.HandleUserInput(it)) }
|
||||
.launchIn(mainScope)
|
||||
|
||||
etCosmosMemo.inputtedTextAsFlow()
|
||||
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
|
||||
.filter {
|
||||
val info = store.state.sendState.transactionExtrasState
|
||||
info.cosmosMemoState?.viewFieldValue?.value != it
|
||||
}
|
||||
.onEach { store.dispatch(TransactionExtrasAction.CosmosMemo.HandleUserInput(it)) }
|
||||
.launchIn(mainScope)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
|
|
@ -234,7 +247,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
}
|
||||
|
||||
etAmountToSend.keyListener = DigitsKeyListener.getInstance("0123456789,.")
|
||||
etAmountToSend.setOnFocusChangeListener { v, hasFocus ->
|
||||
etAmountToSend.setOnFocusChangeListener { _, hasFocus ->
|
||||
snackbarControlledByChangingFocus = true
|
||||
if (hasFocus) {
|
||||
etAmountToSend.postDelayed(200) {
|
||||
|
|
@ -257,7 +270,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
}
|
||||
|
||||
etAmountToSend.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
|
||||
.filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() }
|
||||
.onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) }
|
||||
.launchIn(mainScope)
|
||||
|
|
@ -273,13 +286,13 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
store.dispatch(ToggleControlsVisibility)
|
||||
}
|
||||
chipGroup.check(FeeUiHelper.toId(FeeType.NORMAL))
|
||||
chipGroup.setOnCheckedChangeListener { group, checkedId ->
|
||||
chipGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
|
||||
store.dispatch(ChangeSelectedFee(FeeUiHelper.toType(checkedId)))
|
||||
store.dispatch(CheckAmountToSend)
|
||||
}
|
||||
swIncludeFee.setOnCheckedChangeListener { btn, isChecked ->
|
||||
swIncludeFee.setOnCheckedChangeListener { _, isChecked ->
|
||||
store.dispatch(ChangeIncludeFee(isChecked))
|
||||
store.dispatch(CheckAmountToSend)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
showView(xrpDestinationTagContainer, infoState.xrpDestinationTag)
|
||||
showView(binanceMemoContainer, infoState.binanceMemo)
|
||||
showView(tonMemoContainer, infoState.tonMemoState)
|
||||
showView(cosmosMemoContainer, infoState.cosmosMemoState)
|
||||
|
||||
infoState.xlmMemo?.let {
|
||||
if (!it.viewFieldValue.isFromUserInput) etXlmMemo.setText(it.viewFieldValue.value)
|
||||
|
|
@ -126,6 +127,16 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
etTonMemo.setText(it.viewFieldValue.value)
|
||||
}
|
||||
}
|
||||
infoState.cosmosMemoState?.let {
|
||||
if (infoState.cosmosMemoState.error != null) {
|
||||
tilCosmosMemo.error = fg.getText(R.string.send_extras_error_invalid_memo)
|
||||
} else {
|
||||
tilBinanceMemo.error = null
|
||||
}
|
||||
if (!it.viewFieldValue.isFromUserInput) {
|
||||
etCosmosMemo.setText(it.viewFieldValue.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendScreen(fg: SendFragment, state: SendState) = with(fg.binding) {
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.api.featuretoggles
|
||||
|
||||
/**
|
||||
* TokensList feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface TokensListFeatureToggles {
|
||||
|
||||
/** Availability of redesigned screen (internal feature) */
|
||||
val isRedesignedScreenEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.impl.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
|
||||
import com.tangem.tap.features.tokens.impl.featuretoggles.DefaultTokensListFeatureToggles
|
||||
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 TokensListFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesTokensListFeatureToggles(featureTogglesManager: FeatureTogglesManager): TokensListFeatureToggles {
|
||||
return DefaultTokensListFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.tokens.impl.di
|
||||
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.tokens.impl.presentation.router.DefaultTokensListRouter
|
||||
import com.tangem.tap.features.tokens.impl.presentation.router.TokensListRouter
|
||||
import dagger.Module
|
||||
|
|
@ -17,5 +18,7 @@ internal object TokensListRouterModule {
|
|||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideTokensListRouter(): TokensListRouter = DefaultTokensListRouter()
|
||||
fun provideTokensListRouter(customTokenFeatureToggles: CustomTokenFeatureToggles): TokensListRouter {
|
||||
return DefaultTokensListRouter(customTokenFeatureToggles)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.impl.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
|
||||
|
||||
/**
|
||||
* Default implementation of TokensList feature toggles
|
||||
*
|
||||
* @property featureTogglesManager manager for getting information about the availability of feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultTokensListFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TokensListFeatureToggles {
|
||||
|
||||
override val isRedesignedScreenEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_TOKEN_LIST_SCREEN_ENABLED")
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
|
|
@ -35,7 +36,9 @@ internal class TokensListFragment : Fragment() {
|
|||
setContent {
|
||||
isTransitionGroup = true
|
||||
|
||||
val viewModel = hiltViewModel<TokensListViewModel>()
|
||||
val viewModel = hiltViewModel<TokensListViewModel>().apply {
|
||||
LocalLifecycleOwner.current.lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
TangemTheme {
|
||||
TokensListScreen(stateHolder = viewModel.uiState)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import com.tangem.tap.store
|
|||
*/
|
||||
class TokensListArgs {
|
||||
/** Tokens list screen mode */
|
||||
val isManageAccess: Boolean get() = store.state.tokensState.allowToAdd
|
||||
val isManageAccess: Boolean get() = store.state.tokensState.isManageAccess
|
||||
|
||||
/** Tokens list that accessible from the main screen */
|
||||
val mainScreenTokenList: List<TokenWithBlockchain> get() = store.state.tokensState.addedTokens
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.tangem.tap.features.tokens.impl.presentation.router
|
|||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -15,15 +17,20 @@ import com.tangem.wallet.R
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultTokensListRouter : TokensListRouter {
|
||||
internal class DefaultTokensListRouter(
|
||||
private val customTokenFeatureToggles: CustomTokenFeatureToggles,
|
||||
) : TokensListRouter {
|
||||
|
||||
override fun popBackStack() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(TokensAction.ResetState)
|
||||
}
|
||||
|
||||
override fun openAddCustomTokenScreen() {
|
||||
store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
|
||||
if (customTokenFeatureToggles.isRedesignedScreenEnabled) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
|
||||
} else {
|
||||
store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showAddressCopiedNotification() {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.compose.runtime.MutableState
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.features.tokens.legacy.redux.ContractAddress
|
||||
|
||||
/**
|
||||
* Network item state
|
||||
|
|
@ -62,7 +61,7 @@ sealed interface NetworkItemState {
|
|||
override val isMainNetwork: Boolean,
|
||||
val isAdded: MutableState<Boolean>,
|
||||
val id: String,
|
||||
val address: ContractAddress?,
|
||||
val address: String?,
|
||||
val decimalCount: Int?,
|
||||
val blockchain: Blockchain,
|
||||
val onToggleClick: (TokenItemState.ManageContent, ManageContent) -> Unit,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
*/
|
||||
sealed interface TokenItemState {
|
||||
|
||||
/** Token id */
|
||||
val id: String
|
||||
|
||||
/** Token full name (name with symbol) */
|
||||
val fullName: String
|
||||
|
||||
|
|
@ -21,11 +24,13 @@ sealed interface TokenItemState {
|
|||
/**
|
||||
* Token item state that is available for read
|
||||
*
|
||||
* @property fullName token name
|
||||
* @property id token id
|
||||
* @property fullName token name
|
||||
* @property iconUrl token icon url
|
||||
* @property networks list of networks that is available for read
|
||||
*/
|
||||
data class ReadContent(
|
||||
override val id: String,
|
||||
override val fullName: String,
|
||||
override val iconUrl: String,
|
||||
override val networks: ImmutableList<NetworkItemState.ReadContent>,
|
||||
|
|
@ -34,18 +39,18 @@ sealed interface TokenItemState {
|
|||
/**
|
||||
* Token item state that is available for read and manage
|
||||
*
|
||||
* @property id token id
|
||||
* @property fullName token name
|
||||
* @property iconUrl token icon url
|
||||
* @property networks list of networks is available for read and edit
|
||||
* @property id token id
|
||||
* @property name token name
|
||||
* @property symbol token brief name
|
||||
*/
|
||||
data class ManageContent(
|
||||
override val id: String,
|
||||
override val fullName: String,
|
||||
override val iconUrl: String,
|
||||
override val networks: ImmutableList<NetworkItemState.ManageContent>,
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
) : TokenItemState
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ internal fun BriefNetworksList(
|
|||
exit = fadeOut(),
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
|
||||
networks.forEach {
|
||||
key(NetworkItemState::name) {
|
||||
BriefNetworkItem(model = it)
|
||||
networks.forEach { network ->
|
||||
key(network.name + network.protocolName) {
|
||||
BriefNetworkItem(model = network)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,10 @@
|
|||
package com.tangem.tap.features.tokens.impl.presentation.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.SwitchDefaults
|
||||
import androidx.compose.material.Text
|
||||
|
|
@ -56,7 +45,7 @@ internal fun DetailedNetworksList(
|
|||
) {
|
||||
Column {
|
||||
networks.forEachIndexed { index, network ->
|
||||
key(network.name) {
|
||||
key(network.name + network.protocolName) {
|
||||
DetailedNetworkItem(token = token, network = network, isLastItem = networks.size - 1 == index)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,9 @@ import androidx.compose.animation.scaleIn
|
|||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.with
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
|
|
@ -24,18 +21,17 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.CurrencyPlaceholderIcon
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.extensions.toPx
|
||||
import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState
|
||||
|
|
@ -134,27 +130,11 @@ private fun Icon(name: String, iconUrl: String, modifier: Modifier = Modifier) {
|
|||
.crossfade(enable = true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
loading = { PlaceholderIcon(name = name, modifier = iconModifier) },
|
||||
error = { PlaceholderIcon(name = name, modifier = iconModifier) },
|
||||
loading = { CurrencyPlaceholderIcon(id = name, modifier = iconModifier) },
|
||||
error = { CurrencyPlaceholderIcon(id = name, modifier = iconModifier) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlaceholderIcon(name: String, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier.background(TangemTheme.colors.icon.primary1, shape = CircleShape),
|
||||
) {
|
||||
Text(
|
||||
text = name.firstOrNull()?.titlecase() ?: "",
|
||||
color = TangemTheme.colors.icon.primary2,
|
||||
maxLines = 1,
|
||||
// FIXME("Incorrect typography. Replace with typography from design system")
|
||||
style = TangemTheme.typography.h3.copy(fontWeight = FontWeight.Bold),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Title(title: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
|
|
|
|||
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