Updated on 2026-08-14
This commit is contained in:
commit
be02ae3fa3
91 changed files with 577 additions and 296 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
) {}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 -> {}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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) -> {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue