Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-11 11:39:36 +03:00
commit b93539504a
98 changed files with 626 additions and 312 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -187,13 +187,18 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
override fun onDestroy() {
store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this)))
intentProcessor.removeAll()
cardSdkLifecycleObserver.onDestroy()
cardSdkLifecycleObserver.onDestroy(this)
super.onDestroy()
}
private fun initIntentHandlers() {
val hasSavedWalletsProvider = { store.state.globalState.userWalletsListManager?.hasUserWallets == true }
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider))
intentProcessor.addHandler(
BackgroundScanIntentHandler(
hasSavedWalletsProvider,
lifecycleScope,
),
)
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
intentProcessor.addHandler(BuyCurrencyIntentHandler())
intentProcessor.addHandler(SellCurrencyIntentHandler())
@ -246,7 +251,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
*/
cardSdkLifecycleObserver.onCreate(context = this)
scope.launch {
lifecycleScope.launch {
intentProcessor.handleIntent(intent)
}
}
@ -318,8 +323,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
if (store.state.globalState.userWalletsListManager?.hasUserWallets == true) {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome))
store.dispatchOnMain(WelcomeAction.SetInitialIntent(intentWhichStartedActivity))
scope.launch {
val handler = BackgroundScanIntentHandler(hasSavedUserWalletsProvider = { true })
lifecycleScope.launch {
val handler = BackgroundScanIntentHandler(
hasSavedUserWalletsProvider = { true },
lifecycleCoroutineScope = lifecycleScope,
)
val isBackgroundScanHandled = handler.handleIntent(intentWhichStartedActivity)
val hasNotIncompletedBackup = !backupService.hasIncompletedBackup
if (!isBackgroundScanHandled && hasNotIncompletedBackup) {
@ -328,7 +336,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
} else {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home))
scope.launch {
lifecycleScope.launch {
intentProcessor.handleIntent(intentWhichStartedActivity)
}
}

View file

@ -29,7 +29,6 @@ import com.tangem.domain.common.LogConfig
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.common.analytics.AnalyticsFactory
@ -153,8 +152,8 @@ class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository
@Inject
lateinit var learn2earnInteractor: Learn2earnInteractor
// @Inject
// lateinit var learn2earnInteractor: Learn2earnInteractor
@Inject
lateinit var tokenDetailsFeatureToggles: TokenDetailsFeatureToggles
@ -247,7 +246,7 @@ class TapApplication : Application(), ImageLoaderFactory {
// [REDACTED_JIRA]
runBlocking {
featureTogglesManager.init()
learn2earnInteractor.init()
// learn2earnInteractor.init()
}
initTopUpController()

View file

@ -45,6 +45,16 @@ sealed class Onboarding(
event = "Backup Finished",
params = mapOf("Cards count" to "$cardsCount"),
)
object ResetCancelEvent : Backup(
event = "Reset Card Notification",
params = mapOf("Option" to "Cancel"),
)
object ResetPerformEvent : Backup(
event = "Reset Card Notification",
params = mapOf("Option" to "Reset"),
)
}
sealed class Topup(

View file

@ -2,10 +2,13 @@ package com.tangem.tap.common.redux.global
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.preferencesStorage
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.store
import com.tangem.utils.extensions.replaceBy
import org.rekotlin.Action
@ -82,9 +85,12 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
}
is GlobalAction.SetIfCardVerifiedOnline ->
globalState.copy(cardVerifiedOnline = action.verified)
is GlobalAction.FetchUserCountry.Success -> globalState.copy(
userCountryCode = action.countryCode,
)
is GlobalAction.FetchUserCountry.Success -> {
store.dispatchOnMain(HomeAction.UpdateCountryCode(action.countryCode))
globalState.copy(
userCountryCode = action.countryCode,
)
}
is GlobalAction.UpdateUserWalletsListManager -> {
appStateHolder.userWalletsListManager = action.manager
globalState.copy(

View file

@ -134,9 +134,12 @@ class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigReposit
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
}
suspend fun resetToFactorySettings(cardId: String): CompletionResult<CardDTO> {
suspend fun resetToFactorySettings(
cardId: String,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
return runTaskAsyncReturnOnMain(
runnable = ResetToFactorySettingsTask(),
runnable = ResetToFactorySettingsTask(allowsRequestAccessCodeFromRepository),
cardId = cardId,
initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)),
)

View file

@ -151,7 +151,9 @@ private class CreateWalletTangemWallet(
) {
val config = CardConfig.createConfig(card)
val walletsOnCard = card.wallets.map { it.curve }.toSet()
val curves = card.supportedCurves.intersect(config.mandatoryCurves.toSet()).subtract(walletsOnCard).toList()
val curves = config.mandatoryCurves.toSet()
.intersect(card.supportedCurves.toSet())
.subtract(walletsOnCard).toList()
if (curves.isEmpty()) {
val createWalletResponses = card.wallets.map { wallet ->

View file

@ -8,7 +8,9 @@ import com.tangem.common.extensions.guard
import com.tangem.operations.backup.ResetBackupCommand
import com.tangem.operations.wallet.PurgeWalletCommand
class ResetToFactorySettingsTask : CardSessionRunnable<Card> {
class ResetToFactorySettingsTask(
override val allowsRequestAccessCodeFromRepository: Boolean,
) : CardSessionRunnable<Card> {
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
deleteWallets(session, callback)

View file

@ -249,7 +249,10 @@ private class ScanWalletProcessor(
derivationStyleProvider: DerivationStyleProvider,
): List<BlockchainNetwork> {
val userTokensRepository = userTokensRepository ?: return emptyList()
val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(card)
val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(
card,
derivationStyleProvider.getDerivationStyle(),
)
.toMutableList()
.ifEmpty { getDefaultBlockchains(derivationStyleProvider) }

View file

@ -28,18 +28,23 @@ class UserTokensRepository(
private val networkConnectionManager: NetworkConnectionManager,
) {
suspend fun getUserTokens(card: CardDTO): List<Currency> = withContext(dispatchers.io) {
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
suspend fun getUserTokens(card: CardDTO, derivationStyle: DerivationStyle?): List<Currency> =
withContext(dispatchers.io) {
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
if (DemoHelper.isDemoCardId(card.cardId)) {
return@withContext loadTokensOffline(userWalletId = userWalletId).ifEmpty(::loadDemoCurrencies)
if (DemoHelper.isDemoCardId(card.cardId)) {
return@withContext loadTokensOffline(userWalletId = userWalletId).ifEmpty {
loadDemoCurrencies(
derivationStyle,
)
}
}
if (!networkConnectionManager.isOnline) return@withContext loadTokensOffline(userWalletId = userWalletId)
return@withContext remoteGetUserTokens(userWalletId = userWalletId)
}
if (!networkConnectionManager.isOnline) return@withContext loadTokensOffline(userWalletId = userWalletId)
return@withContext remoteGetUserTokens(userWalletId = userWalletId)
}
suspend fun saveUserTokens(card: CardDTO, tokens: List<Currency>) = withContext(dispatchers.io) {
val userWalletId = getUserWalletId(card) ?: return@withContext
val userTokens = tokens.toUserTokensResponse()
@ -48,28 +53,29 @@ class UserTokensRepository(
}
// FIXME: Move to data layer
suspend fun loadBlockchainsToDerive(card: CardDTO): List<BlockchainNetwork> = withContext(dispatchers.io) {
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
val blockchainNetworks = loadTokensOffline(userWalletId = userWalletId).toBlockchainNetworks()
suspend fun loadBlockchainsToDerive(card: CardDTO, derivationStyle: DerivationStyle?): List<BlockchainNetwork> =
withContext(dispatchers.io) {
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
val blockchainNetworks = loadTokensOffline(userWalletId = userWalletId).toBlockchainNetworks()
if (DemoHelper.isDemoCardId(card.cardId)) {
return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks)
if (DemoHelper.isDemoCardId(card.cardId)) {
return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies(derivationStyle)::toBlockchainNetworks)
}
return@withContext blockchainNetworks
}
return@withContext blockchainNetworks
}
private fun loadTokensOffline(userWalletId: String): List<Currency> {
return storageService.getUserTokens(userWalletId = userWalletId) ?: emptyList()
}
// FIXME: Move to user wallet config
private fun loadDemoCurrencies(): List<Currency> {
private fun loadDemoCurrencies(derivationStyle: DerivationStyle?): List<Currency> {
return DemoHelper.config.demoBlockchains
.map { blockchain ->
BlockchainNetwork(
blockchain = blockchain,
derivationPath = blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath,
derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath,
tokens = emptyList(),
)
}

View file

@ -3,6 +3,7 @@ package com.tangem.tap.domain.walletStores.implementation
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.*
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
@ -126,8 +127,9 @@ internal class DefaultWalletStoresManager(
private suspend fun fetchMultiWallets(userWallet: UserWallet): CompletionResult<Unit> {
val scanResponse = userWallet.scanResponse
val derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle()
val userTokens = withContext(Dispatchers.IO) {
userTokensRepository.getUserTokens(scanResponse.card)
userTokensRepository.getUserTokens(scanResponse.card, derivationStyle)
}
val userWalletId = userWallet.walletId

View file

@ -12,6 +12,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.HDWalletError
@ -212,13 +213,21 @@ internal class AddCustomTokenViewModel @Inject constructor(
return listOf(defaultNetwork) + Blockchain.values()
.filter { blockchain ->
scanResponse?.card?.supportedBlockchains(scanResponse.cardTypesResolver)
?.contains(blockchain) == true &&
blockchain.derivationPath(derivationStyle)?.rawPath?.isNotEmpty() == true
?.contains(blockchain) == true && isDerivationPathNotEmpty(derivationStyle, blockchain)
}
.sortedBy(Blockchain::fullName)
.map(::createNetworkSelectorItem)
}
private fun isDerivationPathNotEmpty(derivationStyle: DerivationStyle?, blockchain: Blockchain): Boolean {
// derivationStyle is null for cards without HD wallets, always return true
return if (derivationStyle != null) {
blockchain.derivationPath(derivationStyle)?.rawPath?.isNotEmpty() == true
} else {
true
}
}
private fun createTokenNameInputField(): AddCustomTokenInputField.TokenName {
return AddCustomTokenInputField.TokenName(
value = "",
@ -288,7 +297,12 @@ internal class AddCustomTokenViewModel @Inject constructor(
}
.sortedBy(Blockchain::fullName)
.mapNotNull {
val derivationPath = it.derivationPath(derivationStyle)?.rawPath
val derivationPath = if (derivationStyle != null) {
it.derivationPath(derivationStyle)?.rawPath
} else {
// derivationStyle is null for cards without HDWallet, use DerivationStyle.V1
it.derivationPath(DerivationStyle.V1)?.rawPath
}
if (derivationPath?.isNotEmpty() == true) {
createDerivationPathSelectorAdditionalItem(
blockchain = it,
@ -628,6 +642,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
if (blockchain == null) return null
val derivationStyle = reduxStateHolder.scanResponse?.derivationStyleProvider?.getDerivationStyle()
?: DerivationStyle.V1
val derivationNetwork = if (blockchain == Blockchain.Unknown) {
uiState.form.networkSelectorField.selectedItem.blockchain

View file

@ -124,7 +124,7 @@ class DetailsMiddleware {
scope.launch {
val userWalletId = UserWalletIdBuilder.card(card).build()
tangemSdkManager.resetToFactorySettings(card.cardId)
tangemSdkManager.resetToFactorySettings(card.cardId, true)
.flatMap { userWalletsListManager.delete(listOfNotNull(userWalletId)) }
.flatMap { tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) }
.doOnSuccess {

View file

@ -12,20 +12,19 @@ import androidx.compose.ui.platform.ComposeView
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.tokens.TokensAction
import com.tangem.feature.learn2earn.presentation.Learn2earnViewModel
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.home.redux.Stories
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.rekotlin.StoreSubscriber
@AndroidEntryPoint
@ -33,7 +32,7 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
private val learn2earnViewModel by activityViewModels<Learn2earnViewModel>()
// private val learn2earnViewModel by activityViewModels<Learn2earnViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -43,10 +42,10 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// sync adding story before screen creation
if (learn2earnViewModel.uiState.storyScreenState.isVisible) {
store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo))
homeState.value = store.state.homeState
}
// if (learn2earnViewModel.uiState.storyScreenState.isVisible) {
// store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo))
// homeState.value = store.state.homeState
// }
return ComposeView(inflater.context).apply {
setContent {
TangemTheme {
@ -91,10 +90,14 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
private fun ScreenContent() {
StoriesScreen(
homeState = homeState,
onLearn2earnClick = learn2earnViewModel.uiState.storyScreenState.onClick,
onLearn2earnClick = {}, // learn2earnViewModel.uiState.storyScreenState.onClick,
onScanButtonClick = {
Analytics.send(IntroductionProcess.ButtonScanCard())
store.dispatch(HomeAction.ReadCard())
lifecycleScope.launch {
store.dispatch(
HomeAction.ReadCard(lifecycleCoroutineScope = lifecycleScope),
)
}
},
onShopButtonClick = {
Analytics.send(IntroductionProcess.ButtonBuyCards())

View file

@ -23,6 +23,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.learn2earn.presentation.ui.Learn2earnStoriesScreen
import com.tangem.tap.features.home.compose.content.*
@ -169,15 +170,27 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M
.height(17.dp)
.alpha(if (hideContent) 0f else 1f)
.align(Alignment.Start),
colorFilter = if (config.currentStory.isDarkBackground) null else ColorFilter.tint(Color.Black),
colorFilter = if (config.currentStory.isDarkBackground) {
null
} else {
ColorFilter.tint(TangemColorPalette.Dark6)
},
)
when (config.currentStory) {
Stories.OneInchPromo -> Learn2earnStoriesScreen(config.onLearn2earnClick)
Stories.TangemIntro -> FirstStoriesContent(isPaused, currentStoryDuration) {
Stories.TangemIntro -> FirstStoriesContent(
isPaused = isPaused,
duration = currentStoryDuration,
isNewWalletAvailable = config.currentStory.isNewWalletAvailable,
) {
hideContent = it
}
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet(currentStoryDuration)
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(isPaused, currentStoryDuration)
is Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
isPaused = isPaused,
stepDuration = currentStoryDuration,
isNewWalletAvailable = config.currentStory.isNewWalletAvailable,
)
Stories.Currencies -> StoriesCurrencies(isPaused, currentStoryDuration)
Stories.Web3 -> StoriesWeb3(isPaused, currentStoryDuration)
Stories.WalletForEveryone -> StoriesWalletForEveryone(currentStoryDuration)

View file

@ -2,13 +2,12 @@ package com.tangem.tap.features.home.compose.content
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
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.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -56,7 +55,7 @@ fun StoriesRevolutionaryWallet(stepDuration: Int) {
}
@Composable
fun StoriesUltraSecureBackup(isPaused: Boolean, stepDuration: Int) {
fun StoriesUltraSecureBackup(isPaused: Boolean, stepDuration: Int, isNewWalletAvailable: MutableState<Boolean>) {
val subtitleText = buildAnnotatedString {
append(stringResource(id = R.string.story_backup_description_1))
append(" ")
@ -75,7 +74,11 @@ fun StoriesUltraSecureBackup(isPaused: Boolean, stepDuration: Int) {
)
},
bottomContent = {
FloatingCardsContent(isPaused, stepDuration)
FloatingCardsContent(
isPaused = isPaused,
stepDuration = stepDuration,
isNewWalletAvailable = isNewWalletAvailable,
)
},
)
}
@ -230,7 +233,13 @@ private fun RevolutionaryWalletPreview() {
@Preview
@Composable
private fun UltraSecureBackupPreview() {
StoriesUltraSecureBackup(false, 6000)
StoriesUltraSecureBackup(
false,
6000,
remember {
mutableStateOf(true)
},
)
}
@Preview

View file

@ -32,7 +32,12 @@ import com.tangem.wallet.R
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
@Composable
fun FirstStoriesContent(isPaused: Boolean, duration: Int, onHideContent: (Boolean) -> Unit) {
fun FirstStoriesContent(
isPaused: Boolean,
duration: Int,
isNewWalletAvailable: MutableState<Boolean>,
onHideContent: (Boolean) -> Unit,
) {
val bottomInsetsPx = WindowInsets.navigationBars.getBottom(LocalDensity.current)
val screenState = remember { mutableStateOf(StartingScreenState.INIT) }
@ -115,13 +120,18 @@ fun FirstStoriesContent(isPaused: Boolean, duration: Int, onHideContent: (Boolea
.weight(1.2f)
.wrapContentSize(),
) {
val painter = if (isNewWalletAvailable.value) {
painterResource(id = R.drawable.img_meet_tangem2)
} else {
painterResource(id = R.drawable.img_meet_tangem)
}
StoriesBottomImageAnimation(
totalDuration = duration,
firstStepDuration = 400,
) { modifier ->
Image(
modifier = modifier.fillMaxWidth(),
painter = painterResource(id = R.drawable.img_meet_tangem),
painter = painter,
contentDescription = "Tangem Wallet card",
)
}
@ -178,5 +188,11 @@ private fun MutableState<StartingScreenState>.isMeetTangemDisplaying(): Boolean
@Preview
@Composable
private fun FirstStoriesPreview() {
FirstStoriesContent(false, 8000) {}
FirstStoriesContent(
false,
8000,
remember {
mutableStateOf(false)
},
) {}
}

View file

@ -4,6 +4,7 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
@ -21,9 +22,13 @@ import com.tangem.wallet.R
[REDACTED_AUTHOR]
*/
@Composable
fun FloatingCardsContent(isPaused: Boolean, stepDuration: Int) {
fun FloatingCardsContent(isPaused: Boolean, stepDuration: Int, isNewWalletAvailable: MutableState<Boolean>) {
val bottomInsetsPx = WindowInsets.navigationBars.getBottom(LocalDensity.current)
val imageBitmap = asImageBitmap(R.drawable.img_card_placeholder_wallet_2)
val imageBitmap = if (isNewWalletAvailable.value) {
asImageBitmap(R.drawable.img_card_placeholder_wallet_2)
} else {
asImageBitmap(R.drawable.card_placeholder_wallet)
}
val cards = listOf(
FloatingCard.first(),
FloatingCard.second(),

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.home.redux
import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
@ -13,12 +14,22 @@ sealed class HomeAction : Action {
data class InsertStory(val position: Int, val story: Stories) : HomeAction()
/**
* Action for scanning card
*
* @property analyticsEvent analytics event
* @property lifecycleCoroutineScope lifecycle scope. It will be canceled when lifecycle-aware component is
* destroyed.
*/
data class ReadCard(
val analyticsEvent: AnalyticsEvent? = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Introduction),
val lifecycleCoroutineScope: LifecycleCoroutineScope,
) : HomeAction()
data class ScanInProgress(val scanInProgress: Boolean) : HomeAction()
data class GoToShop(val userCountryCode: String?) : HomeAction()
data class UpdateCountryCode(val userCountryCode: String) : HomeAction()
data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction()
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.home.redux
import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.common.doOnFailure
import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
@ -62,7 +63,7 @@ private fun handleHomeAction(action: Action) {
store.dispatch(GlobalAction.FetchUserCountry)
}
is HomeAction.ReadCard -> {
readCard(action.analyticsEvent)
readCard(action.analyticsEvent, action.lifecycleCoroutineScope)
}
is HomeAction.GoToShop -> {
Analytics.send(Shop.ScreenOpened())
@ -77,32 +78,34 @@ private fun handleHomeAction(action: Action) {
}
}
private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch {
delay(timeMillis = 200)
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes,
)
private fun readCard(analyticsEvent: AnalyticsEvent?, lifecycleCoroutineScope: LifecycleCoroutineScope) {
lifecycleCoroutineScope.launch {
delay(timeMillis = 200)
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes,
)
store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan(
analyticsEvent = analyticsEvent,
onProgressStateChange = { showProgress ->
if (showProgress) {
changeButtonState(ButtonState.PROGRESS)
} else {
store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan(
analyticsEvent = analyticsEvent,
onProgressStateChange = { showProgress ->
if (showProgress) {
changeButtonState(ButtonState.PROGRESS)
} else {
changeButtonState(ButtonState.ENABLED)
}
},
onScanStateChange = { scanInProgress ->
store.dispatch(HomeAction.ScanInProgress(scanInProgress))
},
onFailure = {
Timber.e(it, "Unable to scan card")
changeButtonState(ButtonState.ENABLED)
}
},
onScanStateChange = { scanInProgress ->
store.dispatch(HomeAction.ScanInProgress(scanInProgress))
},
onFailure = {
Timber.e(it, "Unable to scan card")
changeButtonState(ButtonState.ENABLED)
},
onSuccess = { scanResponse ->
proceedWithScanResponse(scanResponse)
},
)
},
onSuccess = { scanResponse ->
proceedWithScanResponse(scanResponse)
},
)
}
}
private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {

View file

@ -7,10 +7,10 @@ object HomeReducer {
fun reduce(action: Action, state: AppState): HomeState = internalReduce(action, state)
}
private fun internalReduce(action: Action, state: AppState): HomeState {
if (action !is HomeAction) return state.homeState
private fun internalReduce(action: Action, appState: AppState): HomeState {
if (action !is HomeAction) return appState.homeState
var state = state.homeState
var state = appState.homeState
when (action) {
is HomeAction.InsertStory -> {
state = state.copy(
@ -25,6 +25,9 @@ private fun internalReduce(action: Action, state: AppState): HomeState {
is HomeAction.ChangeScanCardButtonState -> {
state = state.copy(btnScanState = action.state)
}
is HomeAction.UpdateCountryCode -> {
state.onCountryCodeUpdate(state, action.userCountryCode)
}
else -> {}
}

View file

@ -1,9 +1,12 @@
package com.tangem.tap.features.home.redux
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.features.send.redux.states.ButtonState
import com.tangem.tap.features.wallet.redux.ProgressState
import org.rekotlin.StateType
import java.util.Locale
data class HomeState(
val scanInProgress: Boolean = false,
@ -19,7 +22,16 @@ data class HomeState(
fun stepOf(story: Stories): Int = stories.indexOf(story)
fun onCountryCodeUpdate(homeState: HomeState, countryCode: String) {
val isNewWalletAvailable = !(countryCode == RUSSIA_COUNTRY_CODE || countryCode == BELARUS_COUNTRY_CODE)
homeState.stories.forEach {
it.isNewWalletAvailable.value = isNewWalletAvailable
}
}
companion object {
private const val RUSSIA_COUNTRY_CODE = "ru"
private const val BELARUS_COUNTRY_CODE = "by"
fun initDefaultStories(): List<Stories> = listOf(
Stories.TangemIntro,
Stories.RevolutionaryWallet,
@ -28,12 +40,18 @@ data class HomeState(
Stories.Web3,
Stories.WalletForEveryone,
)
fun isNewWalletAvailableInit(): Boolean {
val locale = Locale.getDefault().language
return !(locale == RUSSIA_COUNTRY_CODE || locale == BELARUS_COUNTRY_CODE)
}
}
}
sealed class Stories(
val isDarkBackground: Boolean,
val duration: Int,
val isNewWalletAvailable: MutableState<Boolean> = mutableStateOf(HomeState.isNewWalletAvailableInit()),
) {
object OneInchPromo : Stories(true, duration = 8000)
object TangemIntro : Stories(true, duration = 8000)

View file

@ -4,11 +4,11 @@ import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Build
import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.intentHandler.IntentHandler
import com.tangem.tap.features.welcome.redux.WelcomeAction
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -18,6 +18,7 @@ import kotlinx.coroutines.launch
*/
class BackgroundScanIntentHandler(
private val hasSavedUserWalletsProvider: () -> Boolean,
private val lifecycleCoroutineScope: LifecycleCoroutineScope,
) : IntentHandler {
private val nfcActions = arrayOf(
@ -40,12 +41,12 @@ class BackgroundScanIntentHandler(
intent.action = null
if (hasSavedUserWalletsProvider.invoke()) {
// TODO: Remove delay after [REDACTED_JIRA]
scope.launch {
lifecycleCoroutineScope.launch {
delay(timeMillis = 200)
store.dispatchWithMain(WelcomeAction.ProceedWithCard)
store.dispatchWithMain(WelcomeAction.ProceedWithCard(lifecycleCoroutineScope))
}
} else {
store.dispatchWithMain(HomeAction.ReadCard())
store.dispatchWithMain(HomeAction.ReadCard(lifecycleCoroutineScope = lifecycleCoroutineScope))
}
return true

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import android.net.Uri
import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.common.CompletionResult
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
@ -12,11 +13,12 @@ sealed class OnboardingWalletAction : Action {
object GetToCreateWalletStep : OnboardingWalletAction()
object CreateWallet : OnboardingWalletAction()
data class WalletWasCreated(
val shouldSendAnalyticsEvent: Boolean,
val result: CompletionResult<CreateProductWalletTaskResponse>,
) : OnboardingWalletAction()
object Done : OnboardingWalletAction()
object FinishOnboarding : OnboardingWalletAction()
data class FinishOnboarding(val lifecycleCoroutineScope: LifecycleCoroutineScope) : OnboardingWalletAction()
object ResumeBackup : OnboardingWalletAction()

View file

@ -104,14 +104,16 @@ private fun handleWalletAction(action: Action) {
scanResponse ?: return
scope.launch {
val result = tangemSdkManager.createProductWallet(scanResponse)
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(result))
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(true, result))
}
}
is OnboardingWalletAction.WalletWasCreated -> {
scanResponse ?: return
when (val result = action.result) {
is CompletionResult.Success -> {
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
if (action.shouldSendAnalyticsEvent) {
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,
@ -144,12 +146,12 @@ private fun handleWalletAction(action: Action) {
is CompletionResult.Failure -> Unit
}
}
OnboardingWalletAction.FinishOnboarding -> {
is OnboardingWalletAction.FinishOnboarding -> {
store.dispatch(GlobalAction.Onboarding.Stop)
if (scanResponse == null) {
store.dispatch(NavigationAction.PopBackTo())
store.dispatch(HomeAction.ReadCard())
store.dispatch(HomeAction.ReadCard(lifecycleCoroutineScope = action.lifecycleCoroutineScope))
} else {
val backupState = store.state.onboardingWalletState.backupState
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
@ -268,7 +270,8 @@ private fun handleWallet2Action(action: OnboardingWallet2Action) {
CompletionResult.Failure(mediateResult.error)
}
}
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(result))
// do not send analytics event for wallet2 flow, cause its already sent
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(false, result))
}
else -> Unit
@ -457,7 +460,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
}
is BackupAction.ResetBackupCard -> {
scope.launch { tangemSdkManager.resetToFactorySettings(action.cardId) }
scope.launch { tangemSdkManager.resetToFactorySettings(action.cardId, false) }
}
else -> Unit

View file

@ -11,6 +11,7 @@ import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.MenuProvider
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.transition.TransitionInflater
import androidx.transition.TransitionManager
import by.kirich1409.viewbindingdelegate.viewBinding
@ -21,6 +22,7 @@ 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.domain.common.util.cardTypesResolver
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
@ -64,6 +66,7 @@ class OnboardingWalletFragment :
private val seedPhraseViewModel by viewModels<SeedPhraseViewModel>()
private lateinit var cardsWidget: WalletCardsWidget
private var seedPhraseRouter: SeedPhraseRouter? = null
private var accessCodeDialog: AccessCodeDialog? = null
private lateinit var animator: BackupAnimator
@ -77,7 +80,9 @@ class OnboardingWalletFragment :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
seedPhraseViewModel.setRouter(makeSeedPhraseRouter())
val newSeedPhraseRouter = makeSeedPhraseRouter()
seedPhraseRouter = newSeedPhraseRouter
seedPhraseViewModel.setRouter(newSeedPhraseRouter)
seedPhraseViewModel.setMediator(makeSeedPhraseMediator())
}
@ -172,6 +177,12 @@ class OnboardingWalletFragment :
when {
state.wallet2State != null -> {
seedPhraseStateHandler.newState(this, state, seedPhraseViewModel)
state.cardArtworkUri?.let {
seedPhraseViewModel.setCardArtworkUri(it.toString())
loadImageIntoImageView(it, binding.imvFrontCard)
loadImageIntoImageView(it, binding.imvFirstBackupCard)
loadImageIntoImageView(it, binding.imvSecondBackupCard)
}
}
else -> {
loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
@ -195,7 +206,6 @@ class OnboardingWalletFragment :
OnboardingWalletStep.CreateWallet -> setupCreateWalletState()
OnboardingWalletStep.Backup -> setBackupState(
state = state.backupState,
isWallet2 = state.wallet2State != null,
)
else -> {}
@ -220,9 +230,9 @@ class OnboardingWalletFragment :
animator.setupCreateWalletState()
}
private fun setBackupState(state: BackupState, isWallet2: Boolean) {
private fun setBackupState(state: BackupState) {
when (state.backupStep) {
BackupStep.InitBackup -> showBackupIntro(state, isWallet2)
BackupStep.InitBackup -> showBackupIntro(state)
BackupStep.ScanOriginCard -> showScanOriginCard()
BackupStep.AddBackupCards -> showAddBackupCards(state)
BackupStep.SetAccessCode -> showSetAccessCode()
@ -234,7 +244,7 @@ class OnboardingWalletFragment :
}
}
private fun showBackupIntro(state: BackupState, isWallet2: Boolean) = with(binding) {
private fun showBackupIntro(state: BackupState) = with(binding) {
imvFirstBackupCard.show()
imvSecondBackupCard.show()
@ -250,7 +260,7 @@ class OnboardingWalletFragment :
btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.SkipBackup) }
btnWalletAlternativeAction.show(state.canSkipBackup && !isWallet2)
btnWalletAlternativeAction.show(state.canSkipBackup)
}
animator.showBackupIntro(state)
}
@ -419,7 +429,9 @@ class OnboardingWalletFragment :
layoutButtonsCommon.btnWalletAlternativeAction.hide()
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
showConfetti(false)
store.dispatch(OnboardingWalletAction.FinishOnboarding)
lifecycleScope.launch {
store.dispatch(OnboardingWalletAction.FinishOnboarding(lifecycleCoroutineScope = lifecycleScope))
}
}
animator.showSuccess {
@ -443,6 +455,19 @@ class OnboardingWalletFragment :
}
override fun handleOnBackPressed() {
// workaround to use right navigation back for toolbar back btn on seed phrase flow
val isWallet2 =
store.state.globalState.onboardingState.onboardingManager?.scanResponse?.cardTypesResolver?.isWallet2()
?: false
val seedPhraseRouter = seedPhraseRouter
if (seedPhraseRouter != null && isWallet2) {
seedPhraseRouter.navigateBack()
} else {
legacyOnBackHandler()
}
}
private fun legacyOnBackHandler() {
store.dispatch(OnboardingWalletAction.OnBackPressed)
}
@ -453,7 +478,7 @@ class OnboardingWalletFragment :
}
private fun makeSeedPhraseRouter(): SeedPhraseRouter = SeedPhraseRouter(
onBack = ::handleOnBackPressed,
onBack = ::legacyOnBackHandler,
onOpenChat = {
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
},

View file

@ -2,6 +2,8 @@ package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.store
@ -13,9 +15,10 @@ object ResetBackupCardDialog {
setTitle(R.string.common_attention)
setMessage(R.string.onboarding_linking_error_card_with_wallets)
setPositiveButton(R.string.common_cancel) { _, _ ->
/* no-op */
Analytics.send(Onboarding.Backup.ResetCancelEvent)
}
setNegativeButton(R.string.card_settings_action_sheet_reset) { _, _ ->
Analytics.send(Onboarding.Backup.ResetPerformEvent)
store.dispatch(BackupAction.ResetBackupCard(cardId))
}
setOnDismissListener {

View file

@ -155,6 +155,13 @@ sealed class SendAction : SendScreenAction {
val onOk: () -> Unit,
) : Dialog()
data class ChiaWarningDialog(
val blockchainName: String,
val maxOutputs: Int,
val maxAmount: BigDecimal,
val onOk: () -> Unit,
) : Dialog()
sealed class SendTransactionFails : Dialog() {
data class CardSdkError(val error: TangemSdkError) : Dialog()
data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError) : Dialog()

View file

@ -307,6 +307,19 @@ private fun sendTransaction(
),
)
}
is BlockchainSdkError.Chia.UtxoAmountError -> {
dispatch(
SendAction.Dialog.ChiaWarningDialog(
blockchainName = Blockchain.Chia.fullName,
maxOutputs = error.maxOutputs,
maxAmount = error.maxAmount,
onOk = {
dispatch(AmountAction.SetAmount(error.maxAmount, isUserInput = false))
dispatch(AmountActionUi.CheckAmountToSend)
},
),
)
}
else -> {
when {
error.customMessage.contains(DemoTransactionSender.ID) -> {

View file

@ -46,6 +46,7 @@ private class SendReducer : SendInternalReducer {
}
is SendAction.Dialog.TezosWarningDialog -> sendState.copy(dialog = action)
is SendAction.Dialog.KaspaWarningDialog -> sendState.copy(dialog = action)
is SendAction.Dialog.ChiaWarningDialog -> sendState.copy(dialog = action)
is SendAction.Dialog.SendTransactionFails.CardSdkError -> sendState.copy(dialog = action)
is SendAction.Dialog.SendTransactionFails.BlockchainSdkError -> sendState.copy(dialog = action)
is SendAction.Dialog.RequestFeeError -> sendState.copy(dialog = action)

View file

@ -0,0 +1,32 @@
package com.tangem.tap.features.send.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.store
import com.tangem.wallet.R
// TODO: [REDACTED_JIRA]
object ChiaWarningDialog {
fun create(context: Context, dialog: SendAction.Dialog.ChiaWarningDialog): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(R.string.common_warning)
setMessage(
context.getString(
R.string.common_utxo_validate_withdrawal_message_warning,
dialog.blockchainName,
dialog.maxOutputs,
dialog.maxAmount.toPlainString(),
),
)
setPositiveButton(R.string.common_ok) { _, _ ->
dialog.onOk()
}
setOnDismissListener {
store.dispatch(SendAction.Dialog.Hide)
}
}
.create()
}
}

View file

@ -18,10 +18,7 @@ import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.send.ui.FeeUiHelper
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.send.ui.dialogs.KaspaWarningDialog
import com.tangem.tap.features.send.ui.dialogs.RequestFeeErrorDialog
import com.tangem.tap.features.send.ui.dialogs.SendTransactionFailsDialog
import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog
import com.tangem.tap.features.send.ui.dialogs.*
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.utils.ROUGH_SIGN
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
@ -124,6 +121,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
}
}
@Suppress("ComplexMethod")
private fun handleSendScreen(fg: SendFragment, state: SendState) = with(fg.binding) {
when (state.dialog) {
is SendAction.Dialog.TezosWarningDialog -> {
@ -138,6 +136,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
dialog?.show()
}
}
is SendAction.Dialog.ChiaWarningDialog -> {
if (dialog == null) {
dialog = ChiaWarningDialog.create(fg.requireContext(), state.dialog)
dialog?.show()
}
}
is SendAction.Dialog.SendTransactionFails.CardSdkError -> {
if (dialog == null) {
dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.wallet.redux
import android.content.Context
import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.address.AddressType
import com.tangem.core.analytics.models.AnalyticsEvent
@ -73,7 +74,10 @@ sealed class WalletAction : Action {
class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings()
}
data class Scan(val onScanSuccessEvent: AnalyticsEvent?) : WalletAction()
data class Scan(
val onScanSuccessEvent: AnalyticsEvent?,
val lifecycleScope: LifecycleCoroutineScope,
) : WalletAction()
data class Send(val amount: Amount? = null) : WalletAction()
@ -109,7 +113,7 @@ sealed class WalletAction : Action {
data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
object CreateWallet : WalletAction()
object ChangeWallet : WalletAction()
data class ChangeWallet(val lifecycleScope: LifecycleCoroutineScope) : WalletAction()
object ShowSaveWalletIfNeeded : WalletAction()
sealed class TradeCryptoAction : WalletAction() {

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.wallet.redux.middlewares
import androidx.lifecycle.LifecycleCoroutineScope
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
@ -115,9 +116,9 @@ class WalletMiddleware {
}
is WalletAction.Scan -> {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
scope.launch {
action.lifecycleScope.launch {
delay(timeMillis = 700)
store.dispatchOnMain(HomeAction.ReadCard(action.onScanSuccessEvent))
store.dispatchOnMain(HomeAction.ReadCard(action.onScanSuccessEvent, action.lifecycleScope))
}
}
is WalletAction.LoadData,
@ -226,7 +227,7 @@ class WalletMiddleware {
showSaveWalletIfNeeded()
}
is WalletAction.ChangeWallet -> {
changeWallet(walletState)
changeWallet(walletState, action.lifecycleScope)
}
is WalletAction.UserWalletChanged -> Unit
is WalletAction.WalletStoresChanged -> {
@ -379,7 +380,7 @@ class WalletMiddleware {
}
}
private fun changeWallet(state: WalletState) {
private fun changeWallet(state: WalletState, lifecycleScope: LifecycleCoroutineScope) {
when {
state.canSaveUserWallets -> {
Analytics.send(MainScreen.ButtonMyWallets())
@ -387,7 +388,12 @@ class WalletMiddleware {
}
else -> {
Analytics.send(MainScreen.ButtonScanCard())
store.dispatch(WalletAction.Scan(Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main)))
store.dispatch(
WalletAction.Scan(
onScanSuccessEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main),
lifecycleScope = lifecycleScope,
),
)
}
}
}

View file

@ -8,9 +8,7 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
@ -26,16 +24,12 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.extensions.setStatusBarColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.OneTouchClickListener
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.feature.learn2earn.presentation.Learn2earnViewModel
import com.tangem.feature.learn2earn.presentation.ui.Learn2earnMainPageScreen
import com.tangem.feature.swap.api.SwapFeatureToggleManager
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.tap.MainActivity
import com.tangem.tap.common.analytics.events.Portfolio
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
import com.tangem.tap.common.redux.global.GlobalAction
@ -78,7 +72,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<W
private var walletView: WalletView = MultiWalletView()
private val learn2earnViewModel by activityViewModels<Learn2earnViewModel>()
// private val learn2earnViewModel by activityViewModels<Learn2earnViewModel>()
private val viewModel by viewModels<WalletViewModel>()
private val totalBalanceWatcher = modelWatcher {
@ -108,7 +102,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<W
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(R.transition.slide_right)
exitTransition = inflater.inflateTransition(R.transition.fade)
learn2earnViewModel.onMainScreenCreated()
// learn2earnViewModel.onMainScreenCreated()
}
override fun onStart() {
@ -140,7 +134,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<W
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
binding.toolbar.setNavigationOnClickListener(
OneTouchClickListener { store.dispatch(WalletAction.ChangeWallet) },
OneTouchClickListener {
lifecycleScope.launch {
store.dispatch(WalletAction.ChangeWallet(lifecycleScope))
}
},
)
setupWarningsRecyclerView()
walletView.changeWalletView(this, binding)
@ -207,27 +205,27 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<W
if (state.canSaveUserWallets) R.drawable.ic_wallet_24 else R.drawable.ic_tap_card_24,
)
showLearn2earnView()
// showLearn2earnView()
}
private fun showLearn2earnView() {
val isShowing = learn2earnViewModel.uiState.mainScreenState.isVisible
if (!isShowing) return
binding.composeLearnToEarnContainer.show(true) { binding.llWarnings.beginDelayedTransition() }
binding.composeLearnToEarnContainer.apply {
setViewCompositionStrategy(
strategy = ViewCompositionStrategy.DisposeOnLifecycleDestroyed(
lifecycle = this@WalletFragment.lifecycle,
),
)
setContent {
TangemTheme {
Learn2earnMainPageScreen(learn2earnViewModel.uiState)
}
}
}
}
// private fun showLearn2earnView() {
// val isShowing = learn2earnViewModel.uiState.mainScreenState.isVisible
// if (!isShowing) return
//
// binding.composeLearnToEarnContainer.show(true) { binding.llWarnings.beginDelayedTransition() }
// binding.composeLearnToEarnContainer.apply {
// setViewCompositionStrategy(
// strategy = ViewCompositionStrategy.DisposeOnLifecycleDestroyed(
// lifecycle = [REDACTED_EMAIL],
// ),
// )
// setContent {
// TangemTheme {
// Learn2earnMainPageScreen(learn2earnViewModel.uiState)
// }
// }
// }
// }
private fun setupPullToRefreshLayout(state: WalletState) {
setupErrorPullToRefreshState(state)
@ -267,7 +265,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<W
private fun refreshWalletData() {
Analytics.send(Portfolio.Refreshed())
store.dispatch(WalletAction.LoadData.Refresh)
learn2earnViewModel.onMainScreenRefreshed()
// learn2earnViewModel.onMainScreenRefreshed()
}
private fun showWarningsIfPresent(warnings: List<WarningMessage>) {

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.welcome.redux
import android.content.Intent
import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.common.core.TangemError
import org.rekotlin.Action
@ -10,7 +11,7 @@ internal sealed interface WelcomeAction : Action {
data class Error(val error: TangemError) : WelcomeAction
}
object ProceedWithCard : WelcomeAction {
data class ProceedWithCard(val lifecycleCoroutineScope: LifecycleCoroutineScope) : WelcomeAction {
object Success : WelcomeAction
data class Error(val error: TangemError) : WelcomeAction
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.welcome.redux
import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure
import com.tangem.common.doOnResult
@ -40,7 +41,7 @@ internal class WelcomeMiddleware {
private fun handleAction(action: WelcomeAction, state: WelcomeState) {
when (action) {
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics(state)
is WelcomeAction.ProceedWithCard -> proceedWithCard(state)
is WelcomeAction.ProceedWithCard -> proceedWithCard(state, action.lifecycleCoroutineScope)
is WelcomeAction.ClearUserWallets -> disableUserWalletsSaving()
else -> Unit
}
@ -77,25 +78,27 @@ internal class WelcomeMiddleware {
}
}
private fun proceedWithCard(state: WelcomeState) = scope.launch {
scanCardInternal { scanResponse ->
val userWallet = UserWalletBuilder(scanResponse).build() ?: return@scanCardInternal
private fun proceedWithCard(state: WelcomeState, lifecycleCoroutineScope: LifecycleCoroutineScope) {
lifecycleCoroutineScope.launch {
scanCardInternal { scanResponse ->
val userWallet = UserWalletBuilder(scanResponse).build() ?: return@scanCardInternal
userWalletsListManager.save(userWallet, canOverride = true)
.doOnFailure { error ->
Timber.e(error, "Unable to save user wallet")
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error))
}
.doOnSuccess {
store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card))
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success)
store.onUserWalletSelected(userWallet = userWallet)
state.intent?.let {
WalletConnectLinkIntentHandler().handleIntent(it)
userWalletsListManager.save(userWallet, canOverride = true)
.doOnFailure { error ->
Timber.e(error, "Unable to save user wallet")
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error))
}
}
.doOnSuccess {
store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card))
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success)
store.onUserWalletSelected(userWallet = userWallet)
state.intent?.let {
WalletConnectLinkIntentHandler().handleIntent(it)
}
}
}
}
}

View file

@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.res.TangemTheme
@ -65,7 +66,7 @@ internal class WelcomeFragment : ComposeFragment() {
showUnlockProgress = state.showUnlockWithBiometricsProgress,
showScanCardProgress = state.showUnlockWithCardProgress,
onUnlockClick = viewModel::unlockWallets,
onScanCardClick = viewModel::scanCard,
onScanCardClick = { viewModel.scanCard(lifecycleCoroutineScope = lifecycleScope) },
)
SnackbarHost(

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.welcome.ui
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ViewModel
import com.tangem.common.core.TangemError
import com.tangem.core.analytics.Analytics
@ -30,9 +31,9 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
store.dispatch(WelcomeAction.ProceedWithBiometrics)
}
fun scanCard() {
fun scanCard(lifecycleCoroutineScope: LifecycleCoroutineScope) {
Analytics.send(SignIn.ButtonCardSignIn())
store.dispatch(WelcomeAction.ProceedWithCard)
store.dispatch(WelcomeAction.ProceedWithCard(lifecycleCoroutineScope))
}
fun closeError() {

View file

@ -40,7 +40,7 @@ class UserWalletManagerImpl(
val userTokensRepository =
requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" }
return if (card != null) {
userTokensRepository.getUserTokens(card)
userTokensRepository.getUserTokens(card, null) // refactor in [REDACTED_JIRA]
.filter {
val checkCustom = if (isExcludeCustom) {
!it.isCustomCurrency(null)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 266 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

After

Width:  |  Height:  |  Size: 512 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

View file

Before

Width:  |  Height:  |  Size: 818 KiB

After

Width:  |  Height:  |  Size: 818 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="146"
android:viewportHeight="146">
<group
android:scaleX="0.33"
android:scaleY="0.33"
android:translateX="48.91"
android:translateY="48.91">
<path
android:fillColor="#1E1E1E"
android:pathData="M103.72,0H42.28C32.59,0 27.75,0 24.05,1.89C20.79,3.55 18.14,6.19 16.49,9.45C14.6,13.15 14.6,17.99 14.6,27.68V37.96H131.4V27.68C131.4,17.99 131.4,13.15 129.51,9.45C127.85,6.19 125.21,3.54 121.96,1.89C118.25,0 113.41,0 103.72,0Z" />
<path
android:fillColor="#1E1E1E"
android:pathData="M92.46,70.08H131.4V118.32C131.4,128.01 131.4,132.85 129.51,136.55C127.85,139.81 125.21,142.46 121.95,144.11C118.25,146 113.4,146 103.71,146H92.47L92.46,70.08Z" />
<path
android:fillColor="#1E1E1E"
android:pathData="M14.6,70.08H53.53V146H42.28C32.59,146 27.75,146 24.05,144.11C20.79,142.46 18.15,139.81 16.49,136.55C14.6,132.85 14.6,128.01 14.6,118.32V70.08Z" />
</group>
</vector>

View file

@ -1,33 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="44dp"
android:height="13dp"
android:viewportWidth="44"
android:viewportHeight="13">
<path
android:pathData="M5.3925,5.4421L5.3925,9.8495L7.366,9.8495C7.4425,9.8487 7.5156,9.8187 7.5697,9.766C7.6238,9.7133 7.6545,9.6421 7.6554,9.5676L7.6554,5.4421L5.3925,5.4421Z"
android:fillColor="@android:color/white"/>
<path
android:pathData="M0.9796,9.5676C0.9805,9.6421 1.0112,9.7133 1.0653,9.766C1.1194,9.8187 1.1925,9.8487 1.2691,9.8495L3.2425,9.8495L3.2425,5.4421L0.9796,5.4421L0.9796,9.5676Z"
android:fillColor="@android:color/white"/>
<path
android:pathData="M7.6554,3.182L7.6554,1.2109C7.6554,1.1343 7.6255,1.0607 7.5721,1.0065C7.5187,0.9523 7.4463,0.9219 7.3708,0.9219L1.2644,0.9219C1.1889,0.9219 1.1166,0.9523 1.0632,1.0065C1.0098,1.0607 0.9798,1.1343 0.9798,1.2109L0.9798,3.182L7.6554,3.182Z"
android:fillColor="@android:color/white"/>
<path
android:pathData="M18.2145,3.8623C17.8055,3.6911 17.3623,3.6145 16.9187,3.6383C16.3325,3.6367 15.7479,3.7001 15.1759,3.8271L15.1759,4.8288C15.6319,4.775 16.0903,4.744 16.5494,4.736C16.7956,4.7249 17.0421,4.7487 17.2816,4.8064C17.3582,4.8291 17.4278,4.8702 17.4843,4.9261C17.5409,4.9819 17.5826,5.0507 17.6055,5.1264C17.6777,5.3956 17.7082,5.674 17.6961,5.9521L17.6961,6.2466C17.1551,6.2178 16.7243,6.2018 16.4295,6.2018C16.1151,6.1865 15.8016,6.2469 15.516,6.3778C15.2912,6.5041 15.1233,6.71 15.0463,6.9539C14.9395,7.3099 14.8914,7.6805 14.9038,8.0516C14.864,8.5294 14.9773,9.0075 15.2277,9.4182C15.3603,9.5688 15.5273,9.686 15.7149,9.76C15.9025,9.8341 16.1054,9.8628 16.3065,9.8439C16.543,9.8439 17.2783,9.8246 17.6023,9.4022C17.6559,9.335 17.7015,9.262 17.7383,9.1846L17.7739,9.7479L18.966,9.7479L18.966,6.0226C18.9834,5.5406 18.93,5.0588 18.8073,4.592C18.7638,4.437 18.6888,4.2923 18.5869,4.1669C18.485,4.0414 18.3583,3.9378 18.2145,3.8623ZM17.6832,7.4595C17.7167,7.7981 17.6586,8.1394 17.5148,8.4485C17.4445,8.5375 17.3484,8.6034 17.2394,8.6373C17.1162,8.6717 16.9885,8.6879 16.8604,8.6853C16.6603,8.7107 16.458,8.6579 16.2968,8.5381C16.1883,8.3545 16.1428,8.1412 16.1671,7.93C16.16,7.7598 16.1775,7.5896 16.219,7.4244C16.2316,7.3765 16.2543,7.3319 16.2855,7.2933C16.3168,7.2547 16.3559,7.223 16.4004,7.2003C16.5227,7.1521 16.6544,7.1312 16.7859,7.1395L17.6832,7.1395L17.6832,7.4595Z"
android:fillColor="@android:color/white"/>
<path
android:pathData="M28.5312,4.2026C28.5065,4.1598 28.4792,4.1187 28.4495,4.0794C28.0566,3.5603 27.1199,3.6382 27.1199,3.6382C26.8105,3.6281 26.5062,3.7214 26.2524,3.9042C25.996,4.1515 25.8305,4.4827 25.784,4.8417C25.5997,6.0976 25.5997,7.3749 25.784,8.6308C25.8287,8.9857 25.9895,9.3142 26.2398,9.5619C26.5131,9.765 26.8468,9.8626 27.1828,9.8376C27.1828,9.8376 27.2016,9.8376 27.2141,9.8376C27.2141,9.8376 28.0597,9.9057 28.4714,9.471C28.4795,9.826 28.4564,10.1811 28.4023,10.5318C28.3884,10.6253 28.3525,10.7138 28.2977,10.7896C28.2429,10.8654 28.1709,10.9262 28.088,10.9666C27.8262,11.0582 27.5499,11.0967 27.2739,11.0801L25.9757,11.0574L25.9757,12.0728C26.2149,12.1187 26.4562,12.1523 26.6987,12.1734C26.9973,12.2025 27.2802,12.2155 27.5505,12.2155C28.0537,12.2535 28.5569,12.1409 28.9996,11.8911C29.1544,11.7808 29.2862,11.6396 29.3872,11.4757C29.4881,11.3119 29.5562,11.1288 29.5873,10.9373C29.688,10.3136 29.7311,9.6814 29.7162,9.0493L29.7162,3.7323L28.5595,3.7323L28.5312,4.2026ZM28.3866,8.141C28.3673,8.3042 28.2886,8.4537 28.1666,8.5594C28.0239,8.6484 27.8583,8.6904 27.6919,8.6795C27.5059,8.6952 27.3195,8.6558 27.1544,8.5659C27.0258,8.4343 26.9514,8.2564 26.947,8.0695C26.8758,7.1909 26.8758,6.3076 26.947,5.4289C26.9515,5.2481 27.0236,5.076 27.1482,4.9488C27.3164,4.8621 27.5046,4.8251 27.6919,4.8417C27.8692,4.827 28.0458,4.8763 28.1917,4.9812C28.3215,5.1224 28.3994,5.306 28.4117,5.5003C28.4593,5.9169 28.4782,6.3364 28.4683,6.7558C28.4839,7.2191 28.4565,7.683 28.3866,8.141Z"
android:fillColor="@android:color/white"/>
<path
android:pathData="M34.5404,3.8346C34.1306,3.5968 33.6597,3.4902 33.1893,3.5287C32.7005,3.494 32.2126,3.6073 31.787,3.8541C31.4771,4.0813 31.2687,4.4243 31.2075,4.8078C31.1063,5.4337 31.0634,6.0679 31.0794,6.7021C31.0627,7.3375 31.1078,7.9731 31.2139,8.5996C31.2467,8.7891 31.3169,8.9698 31.4204,9.1309C31.5238,9.2921 31.6583,9.4302 31.8158,9.5369C32.2648,9.7724 32.7686,9.8782 33.2726,9.8428C33.5935,9.8421 33.9141,9.8236 34.2331,9.7875C34.4977,9.7629 34.7599,9.7172 35.0175,9.6509L35.0175,8.6321C34.4284,8.6939 33.9449,8.7265 33.5671,8.7265C33.2881,8.7431 33.0084,8.709 32.7411,8.6256C32.6588,8.5899 32.5865,8.5342 32.5306,8.4632C32.4748,8.3922 32.4371,8.3082 32.421,8.2187C32.3635,7.8595 32.3399,7.4955 32.3505,7.1317L35.1456,7.1317L35.1456,6.689C35.1609,6.0364 35.1277,5.3836 35.0463,4.7362C35.0005,4.3798 34.8188,4.0561 34.5404,3.8346ZM33.9321,6.1683L32.3505,6.1683C32.3503,5.8308 32.3728,5.4937 32.4178,5.1593C32.424,5.0798 32.4465,5.0024 32.484,4.9324C32.5216,4.8623 32.5732,4.801 32.6355,4.7524C32.9642,4.6181 33.3312,4.6181 33.66,4.7524C33.7208,4.8026 33.7708,4.8649 33.8072,4.9354C33.8435,5.0059 33.8654,5.0832 33.8713,5.1626C33.9211,5.4953 33.9414,5.8319 33.9321,6.1683Z"
android:fillColor="@android:color/white"/>
<path
android:pathData="M43.0589,4.5379C43.0049,4.2729 42.8609,4.0339 42.6507,3.8605C42.3989,3.6963 42.0997,3.6177 41.7986,3.6369C41.7986,3.6369 41.7693,3.6369 41.7499,3.6369C41.7499,3.6369 40.7779,3.5826 40.3761,4.0746C40.3324,4.13 40.2934,4.1887 40.2595,4.2503C40.1947,4.0846 40.0821,3.9413 39.9355,3.8382C39.6825,3.693 39.3918,3.624 39.0996,3.64C39.0996,3.64 38.1275,3.5826 37.7258,4.0778C37.6809,4.1323 37.6418,4.1912 37.6091,4.2535L37.5767,3.7359L36.3941,3.7359L36.3941,9.7365L37.6901,9.7365L37.6901,6.6084C37.6834,6.233 37.6975,5.8576 37.7323,5.4837C37.7406,5.2996 37.81,5.1233 37.9299,4.982C38.0732,4.866 38.2573,4.8109 38.4418,4.8286C38.6058,4.8156 38.7704,4.8465 38.9181,4.9181C38.9727,4.9699 39.0163,5.0319 39.0463,5.1004C39.0764,5.169 39.0923,5.2428 39.0931,5.3175C39.1272,5.6872 39.1412,6.0583 39.1351,6.4295L39.1351,9.7365L40.4312,9.7365L40.4312,6.6084C40.4312,6.0876 40.4312,5.7137 40.4701,5.4837C40.4788,5.2985 40.5507,5.1217 40.6742,4.982C40.8206,4.8672 41.0063,4.8123 41.1926,4.8286C41.3577,4.8157 41.5233,4.8466 41.6721,4.9181C41.7262,4.9702 41.7692,5.0323 41.7986,5.1008C41.8281,5.1694 41.8435,5.2431 41.8439,5.3175C41.8797,5.6871 41.8937,6.0583 41.886,6.4295L41.886,9.7365L43.182,9.7365L43.182,5.784C43.1892,5.3654 43.148,4.9473 43.0589,4.5379Z"
android:fillColor="@android:color/white"/>
<path
android:pathData="M23.9733,3.8677C23.7029,3.6964 23.3827,3.6156 23.0609,3.6376C23.0609,3.6376 23.0346,3.6376 23.0215,3.6376C23.0215,3.6376 22.0369,3.5865 21.6332,4.0755C21.6044,4.11 21.5781,4.1462 21.5544,4.1842L21.5249,3.7335L20.3269,3.7335L20.3269,9.7365L21.6398,9.7365L21.6398,6.6071C21.6328,6.2337 21.647,5.8602 21.6824,5.4883C21.6921,5.3027 21.7662,5.1258 21.8925,4.9865C22.0509,4.8661 22.2504,4.809 22.4504,4.8267C22.625,4.8143 22.8,4.8451 22.9592,4.9162C23.0182,4.9673 23.0659,5.0297 23.0992,5.0995C23.1325,5.1694 23.1507,5.2452 23.1528,5.3221C23.1903,5.6896 23.2057,6.0589 23.1988,6.4281L23.1988,9.7365L24.5116,9.7365L24.5116,5.84C24.5219,5.4113 24.4856,4.9827 24.4032,4.5614C24.3496,4.2876 24.1971,4.0415 23.9733,3.8677Z"
android:fillColor="@android:color/white"/>
<path
android:pathData="M13.3195,1.9385L11.99,1.9385L11.99,3.6595L11.1628,3.6595L11.1628,4.7597L11.99,4.7597L11.99,9.7361L13.3195,9.7361L13.3195,4.7597L14.3309,4.7597L14.3309,3.6595L13.3195,3.6595L13.3195,1.9385Z"
android:fillColor="@android:color/white"/>
android:width="51dp"
android:height="11dp"
android:viewportWidth="74"
android:viewportHeight="16">
<path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M2.924,0H9.413C10.436,0 10.948,0 11.339,0.199C11.683,0.374 11.962,0.654 12.137,0.998C12.337,1.389 12.337,1.9 12.337,2.924V2.924V4.009H0V2.924C0,1.9 0,1.389 0.199,0.998C0.374,0.654 0.654,0.375 0.998,0.199C1.389,0 1.9,0 2.924,0ZM12.337,7.402H8.224L8.225,7.971V15.421H9.413H9.413C10.436,15.421 10.948,15.421 11.339,15.221C11.683,15.047 11.962,14.767 12.137,14.423C12.337,14.032 12.337,13.52 12.337,12.497V7.402ZM4.111,7.402H0V12.497C0,13.52 0,14.032 0.199,14.423C0.375,14.767 0.654,15.047 0.998,15.221C1.389,15.421 1.9,15.421 2.924,15.421H2.924H4.111V7.402ZM22.85,12.479C21.201,12.479 20.346,11.591 20.346,10.022V5.488H19.236V4.046H20.346V2.223H22.264V4.046H24.087V5.488H22.264V9.848C22.264,10.577 22.612,10.91 23.215,10.91C23.595,10.91 23.881,10.862 24.15,10.751V12.273C23.849,12.384 23.437,12.479 22.85,12.479ZM28.298,11.116C29.471,11.116 30.343,10.498 30.343,9.436V8.611H29.36C27.901,8.611 26.966,8.944 26.966,9.959C26.966,10.656 27.347,11.116 28.298,11.116ZM27.838,12.495C26.316,12.495 25.064,11.766 25.064,10.038C25.064,8.104 26.934,7.406 29.312,7.406H30.343V6.978C30.343,5.869 29.962,5.314 28.837,5.314C27.838,5.314 27.347,5.789 27.251,6.614H25.397C25.555,4.68 27.093,3.871 28.948,3.871C30.802,3.871 32.261,4.632 32.261,6.899V12.337H30.374V11.322C29.835,12.035 29.106,12.495 27.838,12.495ZM33.841,4.046V12.337H35.759V7.454C35.759,6.138 36.631,5.472 37.709,5.472C38.85,5.472 39.358,6.043 39.358,7.295V12.337H41.276V7.121C41.276,4.838 40.103,3.871 38.438,3.871C37.075,3.871 36.155,4.553 35.759,5.361V4.046H33.841ZM46.72,10.529C48.051,10.529 49.018,9.578 49.018,7.993V7.882C49.018,6.313 48.162,5.377 46.783,5.377C45.356,5.377 44.532,6.408 44.532,7.914V8.041C44.532,9.578 45.483,10.529 46.72,10.529ZM46.656,15.38C44.056,15.38 42.883,14.207 42.661,12.622H44.595C44.754,13.446 45.388,13.922 46.64,13.922C48.13,13.922 48.955,13.177 48.955,11.687V10.466C48.495,11.259 47.465,12.004 46.244,12.004C44.167,12.004 42.566,10.45 42.566,8.056V7.945C42.566,5.615 44.151,3.871 46.292,3.871C47.655,3.871 48.479,4.49 48.955,5.314V4.046H50.873V11.718C50.857,14.16 49.224,15.38 46.656,15.38ZM52.249,8.278C52.249,10.894 54.009,12.495 56.45,12.495C58.574,12.495 59.985,11.544 60.255,9.8H58.4C58.257,10.609 57.655,11.068 56.498,11.068C55.071,11.068 54.278,10.181 54.215,8.611H60.286V8.056C60.286,5.092 58.431,3.871 56.371,3.871C54.009,3.871 52.249,5.583 52.249,8.151V8.278ZM58.384,7.327H54.246C54.437,6.043 55.213,5.266 56.371,5.266C57.56,5.266 58.289,5.9 58.384,7.327ZM61.651,12.337V4.046H63.569V5.314C63.965,4.537 64.869,3.871 66.089,3.871C67.167,3.871 68.023,4.331 68.436,5.361C69.07,4.331 70.211,3.871 71.241,3.871C72.763,3.871 74,4.807 74,7.089V12.337H72.081V7.216C72.081,5.996 71.558,5.472 70.607,5.472C69.656,5.472 68.784,6.107 68.784,7.375V12.337H66.866V7.216C66.866,5.996 66.327,5.472 65.392,5.472C64.441,5.472 63.569,6.107 63.569,7.375V12.337H61.651Z" />
</vector>

View file

@ -1,15 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="96dp"
android:height="96dp"
android:viewportWidth="96"
android:viewportHeight="96">
android:viewportWidth="98"
android:viewportHeight="98">
<path
android:fillColor="#1E1E1E"
android:pathData="M56.27,47.27V78H69.56C70.07,78 70.57,77.8 70.93,77.43C71.3,77.07 71.5,76.57 71.5,76.06V47.27H56.27Z" />
android:pathData="M59.285,24.464H38.715C35.469,24.464 33.848,24.464 32.607,25.095C31.517,25.652 30.629,26.536 30.076,27.626C29.444,28.867 29.444,30.488 29.444,33.734V37.175H68.555V33.734C68.555,30.488 68.555,28.867 67.924,27.626C67.367,26.536 66.483,25.648 65.393,25.095C64.152,24.464 62.531,24.464 59.285,24.464Z" />
<path
android:fillColor="#1E1E1E"
android:pathData="M25.5,76.06C25.5,76.57 25.71,77.07 26.07,77.43C26.43,77.8 26.93,78 27.44,78H40.73V47.27H25.5V76.06Z" />
android:pathData="M55.516,47.93H68.554V64.082C68.554,67.328 68.554,68.949 67.923,70.19C67.366,71.28 66.482,72.168 65.392,72.721C64.151,73.353 62.53,73.353 59.284,73.353H55.52L55.516,47.93Z" />
<path
android:fillColor="#1E1E1E"
android:pathData="M71.5,32.2V18.94C71.5,18.43 71.29,17.93 70.93,17.57C70.56,17.2 70.07,17 69.55,17H27.45C26.94,17 26.44,17.2 26.07,17.57C25.71,17.93 25.5,18.43 25.5,18.94V32.2H71.5Z" />
android:pathData="M29.444,47.93H42.479V73.353H38.715C35.469,73.353 33.848,73.353 32.607,72.721C31.517,72.168 30.633,71.28 30.076,70.19C29.444,68.949 29.444,67.328 29.444,64.082V47.93Z" />
</vector>

View file

@ -1,15 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="96dp"
android:height="96dp"
android:viewportWidth="96"
android:viewportHeight="96">
android:viewportWidth="450"
android:viewportHeight="450">
<path
android:pathData="M56.271,47.274V78H69.557C70.073,78 70.567,77.795 70.931,77.431C71.295,77.066 71.5,76.572 71.5,76.056V47.274H56.271Z"
android:fillColor="#ffffff"/>
android:fillColor="#ffffff"
android:pathData="M284.96,82H165.04C146.12,82 136.67,82 129.44,85.68C123.08,88.93 117.91,94.08 114.68,100.44C111,107.67 111,117.12 111,136.04V156.1H339V136.04C339,117.12 339,107.67 335.32,100.44C332.07,94.08 326.92,88.91 320.56,85.68C313.33,82 303.88,82 284.96,82Z" />
<path
android:pathData="M25.5,76.056C25.5,76.572 25.705,77.066 26.069,77.431C26.433,77.795 26.927,78 27.443,78H40.729V47.274H25.5V76.056Z"
android:fillColor="#ffffff"/>
android:fillColor="#ffffff"
android:pathData="M262.99,218.8H338.99V312.96C338.99,331.88 338.99,341.33 335.31,348.56C332.07,354.92 326.91,360.09 320.56,363.32C313.32,367 303.87,367 284.95,367H263.01L262.99,218.8Z" />
<path
android:pathData="M71.5,32.204V18.94C71.5,18.425 71.294,17.932 70.928,17.568C70.562,17.204 70.065,17 69.547,17H27.453C26.935,17 26.438,17.204 26.072,17.568C25.706,17.932 25.5,18.425 25.5,18.94V32.204H71.5Z"
android:fillColor="#ffffff"/>
android:fillColor="#ffffff"
android:pathData="M111,218.8H186.99V367H165.04C146.12,367 136.67,367 129.44,363.32C123.08,360.09 117.93,354.92 114.68,348.56C111,341.33 111,331.88 111,312.96V218.8Z" />
</vector>

View file

@ -8,7 +8,6 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_wallet_main_action"
style="@style/TapPrimaryIconButton"
android:layout_width="204dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/home_bottom_fl_scan_card_container_margin_bottom"
app:layout_constraintBottom_toBottomOf="parent"
@ -20,7 +19,6 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_wallet_alternative_action"
style="@style/TapTextButton"
android:layout_width="204dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/home_bottom_btn_alternative_action_margin_bottom"
app:layout_constraintBottom_toBottomOf="parent"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 984 B

After

Width:  |  Height:  |  Size: 542 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 B

After

Width:  |  Height:  |  Size: 422 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 716 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1,012 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 B

After

Width:  |  Height:  |  Size: 52 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="BaseAppTheme">
<item name="android:forceDarkAllowed">false</item>
</style>
</resources>

View file

@ -68,10 +68,11 @@
<item name="cornerRadius">14dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">@font/roboto_regular</item>
<item name="android:letterSpacing">0</item>
<item name="android:fontFamily">@font/roboto_medium</item>
<item name="android:letterSpacing">0.01</item>
<item name="fontWeight">500</item>
<item name="lineHeight">16sp</item>
<item name="iconPadding">4dp</item>
</style>
<style name="TapPrimaryButton" parent="BaseTapButton">

View file

@ -3,6 +3,9 @@
<string name="address_type_default">По умолчанию</string>
<string name="address_type_legacy">Устаревший</string>
<string name="common_fee_error">Не удалось получить комиссию</string>
<string name="common_utxo_validate_withdrawal_message_warning">Из-за ограничений %1$s в одну транзакцию может поместиться только %2$d UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму.</string>
<string name="eth_gas_required_exceeds_allowance">Недостаточно средств для совершения транзакции. Пожалуйста, пополните свой аккаунт.</string>
<string name="generic_error_code">Произошла ошибка. Код: %s.</string>
<string name="kaspa_withdrawal_message_warning">Из-за ограничений Kaspa в одну транзакцию может поместиться только %1$d UTXO. Это означает, что вы можете отправить только %2$s или меньше. Вам нужно уменьшить сумму.</string>
<string name="no_account_generic">Пополните счет на %1$s+ %2$s, чтобы создать аккаунт</string>
<string name="no_account_polkadot">Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта.</string>

View file

@ -577,5 +577,5 @@
<string name="welcome_unlock">Войти с %s</string>
<string name="welcome_unlock_card">Сканировать карту</string>
<string name="welcome_unlock_description">Используйте %s или отсканируйте карту для входа в приложение</string>
<string name="welcome_unlock_title">C возвращением!</string>
<string name="welcome_unlock_title">С возвращением!</string>
</resources>

View file

@ -80,7 +80,6 @@
<string name="common_origin_card">主卡片</string>
<string name="common_reject">拒絕</string>
<string name="common_rename">重新命名</string>
<string name="common_reset">重置</string>
<string name="common_save_changes">保存設置</string>
<string name="common_search">搜索</string>
<string name="common_search_tokens">搜尋代幣</string>

View file

@ -3,6 +3,9 @@
<string name="address_type_default">Default</string>
<string name="address_type_legacy">Legacy</string>
<string name="common_fee_error">Failed to get fee</string>
<string name="common_utxo_validate_withdrawal_message_warning">Due to %1$s limitations only %2$d UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount.</string>
<string name="eth_gas_required_exceeds_allowance">Not enough funds for the transaction. Please top up your account.</string>
<string name="generic_error_code">An error occurred. Code: %s.</string>
<string name="kaspa_withdrawal_message_warning">Due to Kaspa limitations only %1$d UTXOs can fit in a single transaction. This means you can only send %2$s or less. You need to reduce the amount.</string>
<string name="no_account_generic">Load %1$s+ %2$s to create account</string>
<string name="no_account_polkadot">Destination account is not active. Send %s or more to activate the account.</string>

View file

@ -1,12 +1,20 @@
package com.tangem.core.ui.components.buttons.common
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.requiredSizeIn
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.res.TangemTheme
@Suppress("LongParameterList")
@ -30,67 +38,77 @@ fun TangemButton(
elevation = elevation,
shape = size.toShape(),
colors = colors,
contentPadding = if (showProgress) ButtonDefaults.ContentPadding else size.toContentPadding(icon = icon),
contentPadding = size.toContentPadding(icon = icon),
) {
ButtonContent(
text = text,
textStyle = textStyle,
ButtonContentContainer(
buttonIcon = icon,
colors = colors,
iconPadding = size.toIconPadding(),
showProgress = showProgress,
enabled = enabled,
size = size,
progressIndicator = {
CircularProgressIndicator(
modifier = Modifier.buttonContentSize(textStyle),
color = colors.contentColor(enabled = enabled).value,
strokeWidth = TangemTheme.dimens.size4,
)
},
text = {
Text(
modifier = Modifier.alignByBaseline(),
text = text,
style = textStyle,
color = colors.contentColor(enabled = enabled).value,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
icon = { iconResId ->
Icon(
modifier = Modifier.buttonContentSize(textStyle),
painter = painterResource(id = iconResId),
tint = colors.contentColor(enabled = enabled).value,
contentDescription = null,
)
},
)
}
}
@Suppress("LongParameterList")
@Composable
private fun ButtonContent(
text: String,
textStyle: TextStyle,
private inline fun RowScope.ButtonContentContainer(
buttonIcon: TangemButtonIconPosition,
colors: ButtonColors,
size: TangemButtonSize,
enabled: Boolean,
iconPadding: Dp,
showProgress: Boolean,
progressIndicator: @Composable RowScope.() -> Unit,
text: @Composable RowScope.() -> Unit,
icon: @Composable RowScope.(Int) -> Unit,
) {
val icon = @Composable { iconResId: Int ->
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
painter = painterResource(id = iconResId),
tint = colors.contentColor(enabled = enabled).value,
contentDescription = null,
)
if (showProgress) {
progressIndicator()
} else {
if (buttonIcon is TangemButtonIconPosition.Start) {
icon(buttonIcon.iconResId)
SpacerW(width = iconPadding)
}
text()
if (buttonIcon is TangemButtonIconPosition.End) {
SpacerW(width = iconPadding)
icon(buttonIcon.iconResId)
}
}
}
private fun Modifier.buttonContentSize(buttonTextStyle: TextStyle): Modifier = composed {
val minContentElementSize = TangemTheme.dimens.size20
val maxContentElementSize = remember(key1 = buttonTextStyle.lineHeight) {
buttonTextStyle.lineHeight.value.dp + 4.dp
}
if (showProgress) {
Box(modifier = Modifier.wrapContentSize()) {
CircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.size(TangemTheme.dimens.size24),
color = colors.contentColor(enabled = enabled).value,
strokeWidth = TangemTheme.dimens.size4,
)
}
} else {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()),
) {
if (buttonIcon is TangemButtonIconPosition.Start) {
icon(buttonIcon.iconResId)
}
Text(
text = text,
style = textStyle,
color = colors.contentColor(enabled = enabled).value,
maxLines = 1,
)
if (buttonIcon is TangemButtonIconPosition.End) {
icon(buttonIcon.iconResId)
}
}
}
this.requiredSizeIn(
minWidth = minContentElementSize,
minHeight = minContentElementSize,
maxWidth = maxContentElementSize,
maxHeight = maxContentElementSize,
)
}

View file

@ -40,7 +40,7 @@ internal fun TangemButtonSize.toShape(): Shape = when (this) {
@Composable
@ReadOnlyComposable
internal fun TangemButtonSize.toIconPadding(): Dp = when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
TangemButtonSize.Default -> TangemTheme.dimens.spacing4
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
TangemButtonSize.Selector -> 0.dp
TangemButtonSize.Action,

View file

@ -5,12 +5,12 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M4,3.9C4,3.403 4.421,3 4.941,3H19.059C19.579,3 20,3.403 20,3.9V8.4H4V3.9Z" />
android:pathData="M15.682,3.25H8.318C7.157,3.25 6.576,3.25 6.132,3.476C5.742,3.675 5.424,3.992 5.226,4.382C5,4.826 5,5.407 5,6.568V7.8H19V6.568C19,5.407 19,4.826 18.774,4.382C18.575,3.992 18.258,3.674 17.868,3.476C17.424,3.25 16.844,3.25 15.682,3.25Z"
android:fillColor="#1E1E1E"/>
<path
android:fillColor="#000000"
android:pathData="M4,12H9.647V21H4.941C4.421,21 4,20.597 4,20.1V12Z" />
android:pathData="M14.333,11.65H19V17.432C19,18.594 19,19.174 18.773,19.618C18.574,20.008 18.258,20.326 17.868,20.524C17.423,20.75 16.843,20.75 15.681,20.75H14.334L14.333,11.65Z"
android:fillColor="#1E1E1E"/>
<path
android:fillColor="#000000"
android:pathData="M20,12H14.353V21H19.059C19.579,21 20,20.597 20,20.1V12Z" />
android:pathData="M5,11.65H9.666V20.75H8.318C7.157,20.75 6.576,20.75 6.132,20.524C5.742,20.326 5.425,20.008 5.226,19.618C5,19.174 5,18.594 5,17.432V11.65Z"
android:fillColor="#1E1E1E"/>
</vector>

View file

@ -12,6 +12,6 @@ interface CardSdkLifecycleObserver {
/** Callback of creating activity [context] */
fun onCreate(context: Context)
/** Callback of destroying activity */
fun onDestroy()
/** Callback of destroying activity [context] */
fun onDestroy(context: Context)
}

View file

@ -7,6 +7,7 @@ import com.tangem.common.CardFilter
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.Config
import com.tangem.sdk.extensions.initWithBiometrics
import java.lang.ref.WeakReference
import javax.inject.Inject
import javax.inject.Singleton
@ -23,14 +24,22 @@ internal class DefaultCardSdkProvider @Inject constructor() : CardSdkProvider, C
private var _sdk: TangemSdk? = null
/** Weak reference of context that uses to create [TangemSdk] */
private var contextRef: WeakReference<Context> = WeakReference(null)
override fun onCreate(context: Context) {
if (_sdk == null) {
_sdk = TangemSdk.initWithBiometrics(activity = context as FragmentActivity, config = config)
}
contextRef = WeakReference(context)
_sdk = TangemSdk.initWithBiometrics(activity = context as FragmentActivity, config = config)
}
override fun onDestroy() {
_sdk = null
override fun onDestroy(context: Context) {
/*
*/
if (contextRef.get() == context) {
_sdk = null
}
}
private companion object {

View file

@ -399,6 +399,12 @@ class DemoConfig {
"AB02000000048063",
"AB02000000023736",
"AB02000000058187",
// Wallet 2
"AF04000000012006",
"AF04000000012014",
"AF04000000012022",
"AF04000000012030",
)
@Suppress("ClassOrdering")
@ -406,6 +412,7 @@ class DemoConfig {
"FB20000000000186", // Note ETH
"FB10000000000196", // Note BTC
"FB30000000000176", // Wallet
"FB04000000000152", // Wallet 2
)
@Suppress("ClassOrdering")

View file

@ -2,6 +2,7 @@ package com.tangem.domain.common
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import java.util.*
@ -13,6 +14,7 @@ object TapWorkarounds {
private const val TEST_CARD_BATCH = "99FF"
private const val TEST_CARD_ID_STARTS_WITH = "FF99"
private val backupRequiredFirmwareVersion = FirmwareVersion(major = 6, minor = 21)
private val demoConfig = DemoConfig()
val CardDTO.isTangemTwins: Boolean
get() = TwinsHelper.getTwinCardNumber(cardId) != null
@ -25,7 +27,7 @@ object TapWorkarounds {
// for cards 6.21 and higher backup is not skippable
val CardDTO.canSkipBackup: Boolean
get() = this.firmwareVersion < backupRequiredFirmwareVersion
get() = this.firmwareVersion < backupRequiredFirmwareVersion || demoConfig.isDemoCardId(cardId)
val CardDTO.useOldStyleDerivation: Boolean
get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95"

View file

@ -9,8 +9,8 @@ object Wallet2CardConfig : CardConfig {
get() = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bip0340,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Bip0340,
EllipticCurve.Ed25519Slip0010,
)

View file

@ -141,7 +141,7 @@ sealed class CryptoCurrency : Serializable {
require(name.isNotBlank()) { "Crypto currency name must not be blank" }
require(symbol.isNotBlank()) { "Crypto currency symbol must not be blank" }
require(iconUrl?.isNotBlank() ?: true) { "Crypto currency icon URL must not be blank" }
require(decimals > 0) { "Crypto currency decimal must not be less then zero, but it is: $decimals" }
require(decimals >= 0) { "Crypto currency decimal must not be less then zero, but it is: $decimals" }
require(derivationPath?.isNotBlank() ?: true) { "Crypto currency derivation path must not be blank" }
}
}

View file

@ -23,10 +23,18 @@ sealed class SeedPhraseEvents(
}
}
object OnboardingSeedButtonOtherOptions : AnalyticsEvent(
category = "Onboarding / Create Wallet",
event = "Button - Other Options",
)
sealed class CreateWalletEvents(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(ONBOARDING_CREATE_WALLET, event, params) {
object OnboardingSeedButtonCreateWallet : CreateWalletEvents("Button - Create Wallet")
object OnboardingSeedButtonOtherOptions : CreateWalletEvents("Button - Other Options")
companion object {
const val ONBOARDING_CREATE_WALLET = "Onboarding / Create Wallet"
}
}
enum class SeedPhraseSource {
IMPORTED, GENERATED

View file

@ -79,6 +79,13 @@ class StateBuilder(
onBackClick = uiActions.menuNavigateBackClick,
)
fun setCardArtwork(uiState: OnboardingSeedPhraseState, cardArtwork: String): OnboardingSeedPhraseState =
uiState.copy(
introState = uiState.introState.copy(
cardImageUrl = cardArtwork,
),
)
fun generateMnemonicComponents(uiState: OnboardingSeedPhraseState): OnboardingSeedPhraseState {
return uiState.copy(
aboutState = uiState.aboutState.copy(

View file

@ -21,11 +21,11 @@ class SeedPhraseRouter(
fun navigateBack() {
_currentScreen.value = when (_currentScreen.value) {
SeedPhraseScreen.CheckSeedPhrase -> SeedPhraseScreen.YourSeedPhrase
SeedPhraseScreen.Intro,
SeedPhraseScreen.AboutSeedPhrase,
SeedPhraseScreen.YourSeedPhrase,
SeedPhraseScreen.AboutSeedPhrase -> SeedPhraseScreen.Intro
SeedPhraseScreen.ImportSeedPhrase,
-> {
SeedPhraseScreen.YourSeedPhrase,
-> SeedPhraseScreen.AboutSeedPhrase
SeedPhraseScreen.Intro -> {
onBack.invoke()
return
}

View file

@ -13,7 +13,7 @@ import com.tangem.data.common.locale.LocaleProvider
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.domain.SeedPhraseError
import com.tangem.feature.onboarding.domain.SeedPhraseInteractor
import com.tangem.feature.onboarding.presentation.wallet2.analytics.OnboardingSeedButtonOtherOptions
import com.tangem.feature.onboarding.presentation.wallet2.analytics.CreateWalletEvents
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseEvents
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
import com.tangem.feature.onboarding.presentation.wallet2.model.*
@ -88,10 +88,11 @@ class SeedPhraseViewModel @Inject constructor(
router.currentScreen.onEach { screen ->
when (screen) {
SeedPhraseScreen.Intro -> {
analyticsEventHandler.send(SeedPhraseEvents.IntroScreenOpened)
/* no-op */
}
SeedPhraseScreen.AboutSeedPhrase -> {
analyticsEventHandler.send(SeedPhraseEvents.IntroScreenOpened)
mediator.allowScreenshots(true)
}
@ -116,6 +117,12 @@ class SeedPhraseViewModel @Inject constructor(
this.mediator = mediator
}
fun setCardArtworkUri(cardArtworkUri: String) {
if (uiState.introState.cardImageUrl != cardArtworkUri) {
uiState = uiBuilder.setCardArtwork(uiState, cardArtworkUri)
}
}
private fun createUiActions(): UiActions = UiActions(
introActions = IntroUiAction(
buttonCreateWalletClick = ::buttonCreateWalletClick,
@ -265,6 +272,7 @@ class SeedPhraseViewModel @Inject constructor(
// region ButtonClickHandlers
private fun buttonCreateWalletClick() {
viewModelScope.launch(dispatchers.io) {
analyticsEventHandler.send(CreateWalletEvents.OnboardingSeedButtonCreateWallet)
mediator.createWallet(::handleWalletCreationResult)
}
}
@ -299,7 +307,7 @@ class SeedPhraseViewModel @Inject constructor(
}
private fun buttonOtherOptionsClick() {
analyticsEventHandler.send(OnboardingSeedButtonOtherOptions)
analyticsEventHandler.send(CreateWalletEvents.OnboardingSeedButtonOtherOptions)
router.openScreen(SeedPhraseScreen.AboutSeedPhrase)
}
@ -368,7 +376,6 @@ class SeedPhraseViewModel @Inject constructor(
}
private fun buttonContinueClick() {
analyticsEventHandler.send(SeedPhraseEvents.CheckingScreenOpened)
router.openScreen(SeedPhraseScreen.CheckSeedPhrase)
}

View file

@ -80,9 +80,9 @@ okHttp-prettyLogging = "3.1.0"
# endregion Other libraries
# region Tangem
tangemBlockchainSdk = "develop-340"
tangemBlockchainSdk = "release-app_4.11-344"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-293"
tangemCardSdk = "release-app_4.11-294"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds
# endregion Tangem