Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-11 10:33:31 +03:00
commit be02ae3fa3
91 changed files with 577 additions and 296 deletions

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

@ -28,7 +28,6 @@ import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.common.LogConfig
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
@ -152,8 +151,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
@ -238,7 +237,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

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

@ -123,7 +123,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.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.features.tokens.legacy.redux.TokensAction
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
@ -176,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)
@ -422,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 {

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.common_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
@ -79,7 +73,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 {
@ -109,7 +103,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() {
@ -141,7 +135,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)
@ -208,27 +206,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)
@ -268,7 +266,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)