Updated on 2026-08-14
This commit is contained in:
commit
b76e1bbc45
219 changed files with 4585 additions and 5271 deletions
|
|
@ -291,12 +291,12 @@ enum class DerivationPathSelectorType {
|
|||
internal sealed class AddCustomTokenWarning(val description: TextReference) {
|
||||
|
||||
/** Potential scam warning */
|
||||
object PotentialScamToken : AddCustomTokenWarning(
|
||||
data object PotentialScamToken : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.custom_token_validation_error_not_found),
|
||||
)
|
||||
|
||||
/** Token already added warning */
|
||||
object TokenAlreadyAdded : AddCustomTokenWarning(
|
||||
data object TokenAlreadyAdded : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.custom_token_validation_error_already_added),
|
||||
)
|
||||
|
||||
|
|
@ -305,7 +305,7 @@ internal sealed class AddCustomTokenWarning(val description: TextReference) {
|
|||
description = TextReference.Res(R.string.alert_manage_tokens_unsupported_message, networkName),
|
||||
)
|
||||
|
||||
object WrongDerivationPath : AddCustomTokenWarning(
|
||||
data object WrongDerivationPath : AddCustomTokenWarning(
|
||||
description = TextReference.Res(R.string.custom_token_invalid_derivation_path),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ 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
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.*
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
|
|
@ -674,7 +673,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
private fun createDerivationPathOrNull(rawPath: String): DerivationPath? {
|
||||
return try {
|
||||
DerivationPath(rawPath)
|
||||
} catch (error: HDWalletError) {
|
||||
} catch (error: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ import com.tangem.domain.common.extensions.withMainContext
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletconnect.WalletConnectActions
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.feature.qrscanning.SourceType
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.feedback.GetSupportFeedbackEmailUseCase
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
|
|
@ -24,11 +27,26 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
|
|||
@Inject
|
||||
lateinit var walletsRepository: WalletsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var getSupportFeedbackEmailUseCase: GetSupportFeedbackEmailUseCase
|
||||
|
||||
@Inject
|
||||
lateinit var emailSender: EmailSender
|
||||
|
||||
private lateinit var detailsViewModel: DetailsViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
detailsViewModel = DetailsViewModel(store, walletsRepository)
|
||||
detailsViewModel = DetailsViewModel(
|
||||
store = store,
|
||||
walletsRepository = walletsRepository,
|
||||
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
|
||||
getSupportFeedbackEmailUseCase = getSupportFeedbackEmailUseCase,
|
||||
emailSender = emailSender,
|
||||
)
|
||||
Analytics.send(Settings.ScreenOpened())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.feedback.GetSupportFeedbackEmailUseCase
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
|
|
@ -25,6 +28,7 @@ import com.tangem.tap.features.details.redux.DetailsState
|
|||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
|
@ -36,6 +40,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -43,6 +48,9 @@ import timber.log.Timber
|
|||
internal class DetailsViewModel(
|
||||
private val store: Store<AppState>,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
|
||||
private val getSupportFeedbackEmailUseCase: GetSupportFeedbackEmailUseCase,
|
||||
private val emailSender: EmailSender,
|
||||
) {
|
||||
|
||||
var detailsScreenState: MutableState<DetailsScreenState> = mutableStateOf(updateState(store.state.detailsState))
|
||||
|
|
@ -139,7 +147,21 @@ internal class DetailsViewModel(
|
|||
|
||||
private fun sendFeedback() {
|
||||
Analytics.send(Basic.ButtonSupport())
|
||||
store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail()))
|
||||
if (feedbackManagerFeatureToggles.isLocalLogsEnabled) {
|
||||
mainScope.launch {
|
||||
val email = getSupportFeedbackEmailUseCase()
|
||||
emailSender.send(
|
||||
email = EmailSender.Email(
|
||||
address = email.address,
|
||||
subject = email.subject,
|
||||
message = email.message,
|
||||
attachment = email.file,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToAppSettings() {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.tangem.tap.features.details.ui.walletconnect
|
|||
|
||||
import androidx.lifecycle.*
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.feature.qrscanning.SourceType
|
||||
import com.tangem.feature.qrscanning.usecase.ListenToQrScanningUseCase
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.feature.qrscanning.SourceType
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.home.redux
|
|||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -29,6 +28,4 @@ sealed class HomeAction : Action {
|
|||
data class GoToShop(val userCountryCode: String?) : HomeAction()
|
||||
|
||||
data class UpdateCountryCode(val userCountryCode: String) : HomeAction()
|
||||
|
||||
data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction()
|
||||
}
|
||||
|
|
@ -13,12 +13,10 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.userwallets.UserWalletBuilder
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
||||
import com.tangem.tap.features.send.redux.states.ButtonState
|
||||
import com.tangem.tap.features.signin.redux.SignInAction
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
@ -31,6 +29,8 @@ import org.rekotlin.Action
|
|||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
private const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
object HomeMiddleware {
|
||||
val handler = homeMiddleware
|
||||
|
||||
|
|
@ -84,17 +84,16 @@ private suspend fun readCard(analyticsEvent: AnalyticsEvent?) {
|
|||
analyticsEvent = analyticsEvent,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (showProgress) {
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = true))
|
||||
} else {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
},
|
||||
onScanStateChange = { scanInProgress ->
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress))
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Unable to scan card")
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
proceedWithScanResponse(scanResponse)
|
||||
|
|
@ -113,7 +112,7 @@ private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
|
|||
Timber.e(error, "Unable to save user wallet")
|
||||
}
|
||||
.doOnSuccess {
|
||||
scope.launch { store.onUserWalletSelected(userWallet = userWallet) }
|
||||
scope.launch { store.onUserWalletSelected(userWallet) }
|
||||
}
|
||||
.doOnResult {
|
||||
store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card))
|
||||
|
|
@ -123,10 +122,6 @@ private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
|
|||
|
||||
private suspend fun navigateTo(appScreen: AppScreen) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(appScreen))
|
||||
delay(timeMillis = 200)
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
}
|
||||
|
||||
private fun changeButtonState(state: ButtonState) {
|
||||
store.dispatchOnMain(HomeAction.ChangeScanCardButtonState(IndeterminateProgressButton(state)))
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
|
|
@ -22,9 +22,6 @@ private fun internalReduce(action: Action, appState: AppState): HomeState {
|
|||
is HomeAction.ScanInProgress -> {
|
||||
state = state.copy(scanInProgress = action.scanInProgress)
|
||||
}
|
||||
is HomeAction.ChangeScanCardButtonState -> {
|
||||
state = state.copy(btnScanState = action.state)
|
||||
}
|
||||
is HomeAction.UpdateCountryCode -> {
|
||||
state.onCountryCodeUpdate(state, action.userCountryCode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
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 org.rekotlin.StateType
|
||||
import java.util.Locale
|
||||
|
||||
@Immutable
|
||||
data class HomeState(
|
||||
val scanInProgress: Boolean = false,
|
||||
val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED),
|
||||
val stories: List<Stories> = initDefaultStories(),
|
||||
) : StateType {
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings
|
|||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
|
||||
import com.tangem.tap.features.main.model.MainScreenState
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -17,12 +18,14 @@ import kotlinx.coroutines.flow.*
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class MainViewModel @Inject constructor(
|
||||
private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase,
|
||||
private val listenToFlipsUseCase: ListenToFlipsUseCase,
|
||||
private val reduxNavController: ReduxNavController,
|
||||
private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase,
|
||||
private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
) : ViewModel(), MainIntents {
|
||||
|
|
@ -41,6 +44,10 @@ internal class MainViewModel @Inject constructor(
|
|||
observeFlips()
|
||||
displayBalancesHidingStatusToast()
|
||||
displayHiddenBalancesModalNotification()
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
deleteDeprecatedLogsUseCase()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAppCurrencies() {
|
||||
|
|
|
|||
|
|
@ -72,13 +72,14 @@ object OnboardingHelper {
|
|||
scanResponse: ScanResponse,
|
||||
accessCode: String? = null,
|
||||
backupCardsIds: List<String>? = null,
|
||||
hasBackupError: Boolean = false,
|
||||
) {
|
||||
Analytics.setContext(scanResponse)
|
||||
scope.launch {
|
||||
when {
|
||||
// When should save user wallets, then save card without navigate to save wallet screen
|
||||
store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds)
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
|
||||
store.dispatchOnMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
|
|
@ -92,7 +93,7 @@ object OnboardingHelper {
|
|||
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
|
||||
// then open save wallet screen
|
||||
tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds)
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
|
||||
delay(timeMillis = 1_200)
|
||||
|
||||
|
|
@ -109,7 +110,7 @@ object OnboardingHelper {
|
|||
}
|
||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||
else -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds)
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
|
|
@ -129,8 +130,13 @@ object OnboardingHelper {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse, backupCardsIds: List<String>?) {
|
||||
val userWallet = UserWalletBuilder(scanResponse)
|
||||
private suspend fun proceedWithScanResponse(
|
||||
scanResponse: ScanResponse,
|
||||
backupCardsIds: List<String>?,
|
||||
hasBackupError: Boolean,
|
||||
) {
|
||||
val userWallet = UserWalletBuilder(scanResponse = scanResponse)
|
||||
.hasBackupError(hasBackupError)
|
||||
.backupCardsIds(backupCardsIds?.toSet())
|
||||
.build()
|
||||
.guard {
|
||||
|
|
|
|||
|
|
@ -9,23 +9,23 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import org.rekotlin.Action
|
||||
|
||||
sealed class OnboardingWalletAction : Action {
|
||||
object Init : OnboardingWalletAction()
|
||||
object GetToCreateWalletStep : OnboardingWalletAction()
|
||||
object CreateWallet : OnboardingWalletAction()
|
||||
data object Init : OnboardingWalletAction()
|
||||
data object GetToCreateWalletStep : OnboardingWalletAction()
|
||||
data object CreateWallet : OnboardingWalletAction()
|
||||
data class WalletWasCreated(
|
||||
val shouldSendAnalyticsEvent: Boolean,
|
||||
val result: CompletionResult<CreateProductWalletTaskResponse>,
|
||||
) : OnboardingWalletAction()
|
||||
|
||||
object Done : OnboardingWalletAction()
|
||||
data object Done : OnboardingWalletAction()
|
||||
data class FinishOnboarding(val scope: CoroutineScope) : OnboardingWalletAction()
|
||||
|
||||
object ResumeBackup : OnboardingWalletAction()
|
||||
data object ResumeBackup : OnboardingWalletAction()
|
||||
|
||||
data class LoadArtwork(val cardArtworkUriForUnfinishedBackup: Uri? = null) : OnboardingWalletAction()
|
||||
class SetArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||
|
||||
object OnBackPressed : OnboardingWalletAction()
|
||||
data object OnBackPressed : OnboardingWalletAction()
|
||||
}
|
||||
|
||||
sealed class OnboardingWallet2Action : OnboardingWalletAction() {
|
||||
|
|
@ -49,45 +49,46 @@ sealed class OnboardingWallet2Action : OnboardingWalletAction() {
|
|||
|
||||
sealed class BackupAction : Action {
|
||||
|
||||
object IntroduceBackup : BackupAction()
|
||||
object StartBackup : BackupAction()
|
||||
object SkipBackup : BackupAction()
|
||||
data object IntroduceBackup : BackupAction()
|
||||
data object StartBackup : BackupAction()
|
||||
data object SkipBackup : BackupAction()
|
||||
|
||||
object StartAddingPrimaryCard : BackupAction()
|
||||
object ScanPrimaryCard : BackupAction()
|
||||
data object ErrorInBackupCard : BackupAction()
|
||||
data object StartAddingPrimaryCard : BackupAction()
|
||||
data object ScanPrimaryCard : BackupAction()
|
||||
|
||||
/**
|
||||
* Check for unfinished backup of standard Wallets
|
||||
* See more GlobalAction.Onboarding.StartForUnfinishedBackup
|
||||
*/
|
||||
object CheckForUnfinishedBackup : BackupAction()
|
||||
data object CheckForUnfinishedBackup : BackupAction()
|
||||
|
||||
object StartAddingBackupCards : BackupAction()
|
||||
object AddBackupCard : BackupAction() {
|
||||
object Success : BackupAction()
|
||||
data object StartAddingBackupCards : BackupAction()
|
||||
data object AddBackupCard : BackupAction() {
|
||||
data object Success : BackupAction()
|
||||
data class ChangeButtonLoading(val isLoading: Boolean) : BackupAction()
|
||||
}
|
||||
|
||||
object FinishAddingBackupCards : BackupAction()
|
||||
data object FinishAddingBackupCards : BackupAction()
|
||||
|
||||
object ShowAccessCodeInfoScreen : BackupAction()
|
||||
object ShowEnterAccessCodeScreen : BackupAction()
|
||||
data object ShowAccessCodeInfoScreen : BackupAction()
|
||||
data object ShowEnterAccessCodeScreen : BackupAction()
|
||||
data class CheckAccessCode(val accessCode: String) : BackupAction()
|
||||
data class SetAccessCodeError(val error: AccessCodeError?) : BackupAction()
|
||||
data class SaveFirstAccessCode(val accessCode: String) : BackupAction()
|
||||
data class SaveAccessCodeConfirmation(val accessCodeConfirmation: String) : BackupAction()
|
||||
object OnAccessCodeDialogClosed : BackupAction()
|
||||
data object OnAccessCodeDialogClosed : BackupAction()
|
||||
|
||||
object PrepareToWritePrimaryCard : BackupAction()
|
||||
object WritePrimaryCard : BackupAction()
|
||||
data object PrepareToWritePrimaryCard : BackupAction()
|
||||
data object WritePrimaryCard : BackupAction()
|
||||
data class PrepareToWriteBackupCard(val cardNumber: Int) : BackupAction()
|
||||
data class WriteBackupCard(val cardNumber: Int) : BackupAction()
|
||||
|
||||
data class FinishBackup(val withAnalytics: Boolean = true) : BackupAction()
|
||||
|
||||
object DiscardBackup : BackupAction()
|
||||
object DiscardSavedBackup : BackupAction()
|
||||
object ResumeFoundUnfinishedBackup : BackupAction()
|
||||
data object DiscardBackup : BackupAction()
|
||||
data object DiscardSavedBackup : BackupAction()
|
||||
data object ResumeFoundUnfinishedBackup : BackupAction()
|
||||
|
||||
data class ResetBackupCard(val cardId: String) : BackupAction()
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import com.tangem.domain.userwallets.Artwork
|
|||
import com.tangem.domain.userwallets.UserWalletBuilder
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.*
|
||||
|
|
@ -173,6 +174,7 @@ private fun handleWalletAction(action: Action) {
|
|||
scanResponse = updatedScanResponse,
|
||||
accessCode = backupState.accessCode,
|
||||
backupCardsIds = backupState.backupCardIds,
|
||||
hasBackupError = backupState.hasBackupError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -484,6 +486,10 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
backupService.proceedBackup { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val backupValidator = BackupValidator()
|
||||
if (!backupValidator.isValid(CardDTO(result.data))) {
|
||||
store.dispatchOnMain(BackupAction.ErrorInBackupCard)
|
||||
}
|
||||
if (backupService.currentState == BackupService.State.Finished) {
|
||||
store.dispatchOnMain(BackupAction.FinishBackup())
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ private object BackupReducer {
|
|||
} else {
|
||||
state.copy(backupStep = BackupStep.WriteBackupCard(action.cardNumber))
|
||||
}
|
||||
is BackupAction.ErrorInBackupCard -> state.copy(hasBackupError = true)
|
||||
is BackupAction.SkipBackup -> state.copy(backupStep = BackupStep.Finished)
|
||||
is BackupAction.FinishBackup -> state.copy(backupStep = BackupStep.Finished)
|
||||
BackupAction.OnAccessCodeDialogClosed -> state.copy(backupStep = BackupStep.AddBackupCards)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ data class BackupState(
|
|||
val canSkipBackup: Boolean = true,
|
||||
val isInterruptedBackup: Boolean = false,
|
||||
val showBtnLoading: Boolean = false,
|
||||
val hasBackupError: Boolean = false,
|
||||
)
|
||||
|
||||
enum class AccessCodeError {
|
||||
|
|
|
|||
|
|
@ -25,10 +25,14 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.qrscanning.models.QrResult
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.feature.qrscanning.SourceType
|
||||
import com.tangem.feature.qrscanning.usecase.ListenToQrScanningUseCase
|
||||
import com.tangem.features.send.api.navigation.SendRouter.Companion.CRYPTO_CURRENCY_KEY
|
||||
import com.tangem.sdk.extensions.hideSoftKeyboard
|
||||
import com.tangem.tap.common.KeyboardObserver
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
|
|
@ -60,6 +64,7 @@ import kotlinx.coroutines.channels.awaitClose
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.text.DecimalFormatSymbols
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -82,11 +87,17 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
private val sendSubscriber = SendStateSubscriber(this)
|
||||
private lateinit var keyboardObserver: KeyboardObserver
|
||||
|
||||
private val cryptoCurrency: CryptoCurrency?
|
||||
get() = arguments?.getParcelable(CRYPTO_CURRENCY_KEY)
|
||||
|
||||
val binding: FragmentSendBinding by viewBinding(FragmentSendBinding::bind)
|
||||
|
||||
@Inject
|
||||
lateinit var listenToQrScanningUseCase: ListenToQrScanningUseCase
|
||||
|
||||
@Inject
|
||||
lateinit var parseQrCodeUseCase: ParseQrCodeUseCase
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycle.addObserver(viewModel)
|
||||
|
|
@ -177,13 +188,21 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
listenToQrScanningUseCase(SourceType.SEND)
|
||||
.getOrElse { emptyFlow() }
|
||||
.flowWithLifecycle(this@SendFragment.lifecycle, minActiveState = Lifecycle.State.CREATED)
|
||||
.collect {
|
||||
.collect { rawQr ->
|
||||
delay(200)
|
||||
|
||||
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
|
||||
// If do not use the delay, then etAmount error field is not displayed when
|
||||
// inserting an incorrect amount by shareUri
|
||||
onCodeScanned(it)
|
||||
cryptoCurrency?.let { cryptoCurrency ->
|
||||
parseQrCodeUseCase(rawQr, cryptoCurrency = cryptoCurrency).fold(
|
||||
ifLeft = {
|
||||
onCodeScanned(QrResult(address = rawQr))
|
||||
Timber.w(it)
|
||||
},
|
||||
ifRight = { onCodeScanned(it) },
|
||||
)
|
||||
} ?: onCodeScanned(QrResult(address = rawQr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -254,15 +273,18 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
.launchIn(mainScope)
|
||||
}
|
||||
|
||||
private fun onCodeScanned(scannedCode: String) {
|
||||
if (scannedCode.isEmpty()) return
|
||||
private fun onCodeScanned(parsedQr: QrResult) {
|
||||
if (parsedQr.address.isEmpty()) return
|
||||
|
||||
store.dispatch(
|
||||
PasteAddress(
|
||||
data = scannedCode,
|
||||
data = parsedQr.address,
|
||||
sourceType = Token.Send.AddressEntered.SourceType.QRCode,
|
||||
),
|
||||
)
|
||||
parsedQr.amount?.let { amount ->
|
||||
store.dispatchOnMain(AmountAction.SetAmount(amount, isUserInput = false))
|
||||
}
|
||||
store.dispatch(TruncateOrRestore(!binding.lSendAddress.etAddress.isFocused))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,20 @@ package com.tangem.tap.features.send.ui.dialogs
|
|||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object KaspaWarningDialog {
|
||||
|
||||
fun create(context: Context, dialog: SendAction.Dialog.KaspaWarningDialog): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.common_warning)
|
||||
setMessage(
|
||||
context.getString(
|
||||
R.string.kaspa_withdrawal_message_warning,
|
||||
R.string.common_utxo_validate_withdrawal_message_warning,
|
||||
Blockchain.Kaspa.fullName,
|
||||
dialog.maxOutputs,
|
||||
dialog.maxAmount.toPlainString(),
|
||||
),
|
||||
|
|
@ -23,7 +26,6 @@ object KaspaWarningDialog {
|
|||
setOnDismissListener {
|
||||
store.dispatch(SendAction.Dialog.Hide)
|
||||
}
|
||||
}
|
||||
.create()
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
package com.tangem.tap.features.shop.data
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.ShopResponse
|
||||
import com.tangem.domain.common.extensions.withIOContext
|
||||
import com.tangem.tap.features.shop.domain.ShopRepository
|
||||
import com.tangem.tap.features.shop.domain.models.SalesProduct
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Default implementation of shop feature repository
|
||||
*
|
||||
* @property tangemTechApi TangemTech API
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultShopRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ShopRepository {
|
||||
|
||||
private val salesProductConverter = SalesProductConverter()
|
||||
|
||||
override suspend fun isShopifyOrderingAvailable(): Boolean {
|
||||
return runCatching(dispatchers.io) { tangemTechApi.getShopInfo(name = SHOPIFY_NAME) }
|
||||
.fold(
|
||||
onSuccess = ShopResponse::isOrderingAvailable,
|
||||
onFailure = {
|
||||
Timber.e("Server error. isShopifyOrderingAvailable returns default value (true)")
|
||||
true
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getSalesProductInfo(): List<SalesProduct> {
|
||||
return withIOContext {
|
||||
val salesInfo = tangemTechApi.getSalesInfo(locale = getLocaleName(), shops = SHOPIFY_NAME)
|
||||
salesProductConverter.convert(salesInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLocaleName(): String {
|
||||
return if (Locale.getDefault().language == "ru") {
|
||||
RU_LOCALE
|
||||
} else {
|
||||
EN_LOCALE
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val SHOPIFY_NAME = "shopify"
|
||||
private const val RU_LOCALE = "ru"
|
||||
private const val EN_LOCALE = "en"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
package com.tangem.tap.features.shop.data
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.SalesResponse
|
||||
import com.tangem.tap.features.shop.domain.models.Notification
|
||||
import com.tangem.tap.features.shop.domain.models.ProductState
|
||||
import com.tangem.tap.features.shop.domain.models.ProductType
|
||||
import com.tangem.tap.features.shop.domain.models.SalesProduct
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class SalesProductConverter : Converter<SalesResponse, List<SalesProduct>> {
|
||||
|
||||
override fun convert(value: SalesResponse): List<SalesProduct> {
|
||||
return value.sales.map { sales ->
|
||||
val productState = when (sales.state) {
|
||||
"order" -> ProductState.ORDER
|
||||
"pre-order" -> ProductState.PRE_ORDER
|
||||
"sold-out" -> ProductState.SOLD_OUT
|
||||
else -> ProductState.SOLD_OUT
|
||||
}
|
||||
val productType = when (sales.product.code) {
|
||||
"pack2" -> ProductType.WALLET_2_CARDS
|
||||
"pack3" -> ProductType.WALLET_3_CARDS
|
||||
else -> ProductType.WALLET_3_CARDS
|
||||
}
|
||||
SalesProduct(
|
||||
id = sales.id,
|
||||
productType = productType,
|
||||
state = productState,
|
||||
name = sales.product.name,
|
||||
notification = sales.notification?.let { notification ->
|
||||
Notification(
|
||||
type = notification.type,
|
||||
title = notification.title,
|
||||
description = notification.description,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package com.tangem.tap.features.shop.di
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.tap.features.shop.data.DefaultShopRepository
|
||||
import com.tangem.tap.features.shop.domain.DefaultShopifyOrderingAvailabilityUseCase
|
||||
import com.tangem.tap.features.shop.domain.GetShopifySalesProductsUseCase
|
||||
import com.tangem.tap.features.shop.domain.ShopRepository
|
||||
import com.tangem.tap.features.shop.domain.ShopifyOrderingAvailabilityUseCase
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ViewModelComponent
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ViewModelComponent::class)
|
||||
internal object ShopUseCaseModule {
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideShopifyOrderingAvailabilityUseCase(shopRepository: ShopRepository): ShopifyOrderingAvailabilityUseCase {
|
||||
return DefaultShopifyOrderingAvailabilityUseCase(
|
||||
shopRepository = shopRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideGetShopifySalesProductsUseCase(shopRepository: ShopRepository): GetShopifySalesProductsUseCase {
|
||||
return GetShopifySalesProductsUseCase(
|
||||
shopRepository = shopRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideDefaultShopRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ShopRepository {
|
||||
return DefaultShopRepository(tangemTechApi = tangemTechApi, dispatchers = dispatchers)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.tap.features.shop.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.shop.toggles.DefaultShopifyFeatureToggleManager
|
||||
import com.tangem.tap.features.shop.toggles.ShopifyFeatureToggleManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object ShopifyTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDefaultShopifyFeatureToggleManager(
|
||||
featureToggleManager: FeatureTogglesManager,
|
||||
): ShopifyFeatureToggleManager {
|
||||
return DefaultShopifyFeatureToggleManager(featureToggleManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.tap.features.shop.domain
|
||||
|
||||
/**
|
||||
* Default implementation of use case to define shopify ordering availability
|
||||
*
|
||||
* @property shopRepository shop feature repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultShopifyOrderingAvailabilityUseCase(
|
||||
private val shopRepository: ShopRepository,
|
||||
) : ShopifyOrderingAvailabilityUseCase {
|
||||
|
||||
override suspend fun invoke() = shopRepository.isShopifyOrderingAvailable()
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.tap.features.shop.domain
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.tap.features.shop.domain.models.SalesError
|
||||
import com.tangem.tap.features.shop.domain.models.SalesProduct
|
||||
|
||||
/**
|
||||
* Use case to get shopify available products
|
||||
*
|
||||
* @property shopRepository shop feature repository
|
||||
*/
|
||||
class GetShopifySalesProductsUseCase(
|
||||
private val shopRepository: ShopRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<SalesError, List<SalesProduct>> {
|
||||
return try {
|
||||
shopRepository.getSalesProductInfo().right()
|
||||
} catch (e: Exception) {
|
||||
SalesError.DataError(e).left()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.features.shop.domain
|
||||
|
||||
import com.tangem.tap.features.shop.domain.models.SalesProduct
|
||||
|
||||
/**
|
||||
* Shop feature repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ShopRepository {
|
||||
|
||||
/** Get shopify ordering availability */
|
||||
suspend fun isShopifyOrderingAvailable(): Boolean
|
||||
|
||||
/** Get actual sales product info */
|
||||
suspend fun getSalesProductInfo(): List<SalesProduct>
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.tap.features.shop.domain
|
||||
|
||||
/**
|
||||
* Use case to define shopify ordering availability
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal interface ShopifyOrderingAvailabilityUseCase {
|
||||
|
||||
/** Get availability */
|
||||
suspend operator fun invoke(): Boolean
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.tap.features.shop.domain.models
|
||||
|
||||
private const val TANGEM_WALLET_2_CARDS_SKU = "TG115X2-S"
|
||||
private const val TANGEM_WALLET_3_CARDS_SKU = "TG115X3-S"
|
||||
|
||||
enum class ProductType(val sku: String) {
|
||||
|
||||
WALLET_2_CARDS(TANGEM_WALLET_2_CARDS_SKU),
|
||||
WALLET_3_CARDS(TANGEM_WALLET_3_CARDS_SKU),
|
||||
;
|
||||
|
||||
companion object {
|
||||
val SKUS_TO_DISPLAY = listOf(TANGEM_WALLET_2_CARDS_SKU, TANGEM_WALLET_3_CARDS_SKU)
|
||||
fun fromSku(sku: String): ProductType? {
|
||||
return when (sku) {
|
||||
WALLET_2_CARDS.sku -> WALLET_2_CARDS
|
||||
WALLET_3_CARDS.sku -> WALLET_3_CARDS
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package com.tangem.tap.features.shop.domain.models
|
||||
|
||||
sealed class SalesError {
|
||||
data class DataError(val cause: Throwable) : SalesError()
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.features.shop.domain.models
|
||||
|
||||
/**
|
||||
* Sales product
|
||||
*
|
||||
* @property id product id
|
||||
* @property productType shows TW2 cards or 3 cards
|
||||
* @property state state as order available etc
|
||||
* @property name product name
|
||||
* @property notification optional notification
|
||||
*/
|
||||
data class SalesProduct(
|
||||
val id: String,
|
||||
val productType: ProductType,
|
||||
val state: ProductState,
|
||||
val name: String,
|
||||
val notification: Notification?,
|
||||
)
|
||||
|
||||
data class Notification(
|
||||
val type: String,
|
||||
val title: String,
|
||||
val description: String,
|
||||
)
|
||||
|
||||
enum class ProductState {
|
||||
ORDER,
|
||||
SOLD_OUT,
|
||||
PRE_ORDER,
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package com.tangem.tap.features.shop.presentation
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.tap.features.shop.domain.GetShopifySalesProductsUseCase
|
||||
import com.tangem.tap.features.shop.domain.ShopifyOrderingAvailabilityUseCase
|
||||
import com.tangem.tap.features.shop.redux.ShopAction
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Shop screen view model
|
||||
*
|
||||
* @property shopifyOrderingAvailabilityUseCase use case to define shopify ordering availability
|
||||
* @property getShopifySalesProductsUseCase use case to get actual sales info
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
* @property appStateHolder redux state holder
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@HiltViewModel
|
||||
internal class ShopViewModel @Inject constructor(
|
||||
private val shopifyOrderingAvailabilityUseCase: ShopifyOrderingAvailabilityUseCase,
|
||||
private val getShopifySalesProductsUseCase: GetShopifySalesProductsUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val appStateHolder: AppStateHolder,
|
||||
) : ViewModel() {
|
||||
|
||||
/** Check ordering delay block visibility */
|
||||
fun checkOrderingDelayBlockVisibility() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val visibility = runCatching(dispatchers.io) { shopifyOrderingAvailabilityUseCase() }
|
||||
.fold(onSuccess = { !it }, onFailure = { false })
|
||||
|
||||
appStateHolder.mainStore?.dispatch(action = ShopAction.SetOrderingDelayBlockVisibility(visibility))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actual sales products info
|
||||
* to configure view dynamically
|
||||
*/
|
||||
fun getActualSalesInfo() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val action = getShopifySalesProductsUseCase().fold(
|
||||
ifLeft = {
|
||||
ShopAction.SalesProductsError
|
||||
},
|
||||
ifRight = {
|
||||
ShopAction.SalesProductsLoaded(it)
|
||||
},
|
||||
)
|
||||
appStateHolder.mainStore?.dispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package com.tangem.tap.features.shop.redux
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.features.shop.domain.models.ProductType
|
||||
import com.tangem.tap.common.shop.data.TangemProduct
|
||||
import com.tangem.tap.common.shop.googlepay.GooglePayService
|
||||
import com.tangem.tap.features.shop.domain.models.SalesProduct
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed interface ShopAction : Action {
|
||||
|
||||
object LoadProducts : ShopAction {
|
||||
data class Success(val products: List<TangemProduct>) : ShopAction
|
||||
object Failure : ShopAction, NotificationAction {
|
||||
override val messageResource = R.string.common_server_unavailable
|
||||
}
|
||||
}
|
||||
|
||||
data class ApplyPromoCode(val promoCode: String) : ShopAction {
|
||||
data class Success(val promoCode: String?, val products: List<TangemProduct>) : ShopAction
|
||||
object InvalidPromoCode : ShopAction
|
||||
}
|
||||
|
||||
object BuyWithGooglePay : ShopAction {
|
||||
object UserCancelled : ShopAction
|
||||
data class HandleGooglePayResponse(val resultCode: Int, val data: Intent?) : ShopAction
|
||||
|
||||
data class Failure(val exception: Throwable) : ShopAction
|
||||
object Success : ShopAction
|
||||
}
|
||||
|
||||
object StartWebCheckout : ShopAction
|
||||
|
||||
data class CheckIfGooglePayAvailable(val googlePayService: GooglePayService) : ShopAction {
|
||||
object Success : ShopAction
|
||||
object Failure : ShopAction
|
||||
}
|
||||
|
||||
data class SelectProduct(val productType: ProductType) : ShopAction
|
||||
|
||||
object FinishSuccessfulOrder : ShopAction
|
||||
|
||||
object ResetState : ShopAction
|
||||
|
||||
data class SetOrderingDelayBlockVisibility(val visibility: Boolean) : ShopAction
|
||||
|
||||
data class SalesProductsLoaded(val salesProducts: List<SalesProduct>) : ShopAction
|
||||
|
||||
object SalesProductsError : ShopAction
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
package com.tangem.tap.features.shop.redux
|
||||
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.shopService
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
class ShopMiddleware {
|
||||
|
||||
val shopMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handle(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handle(action: Action) {
|
||||
val shopState = store.state.shopState
|
||||
|
||||
if (action is NavigationAction.NavigateTo && action.screen == AppScreen.Shop) {
|
||||
store.dispatch(ShopAction.LoadProducts)
|
||||
}
|
||||
|
||||
if (action !is ShopAction) return
|
||||
|
||||
when (action) {
|
||||
is ShopAction.ApplyPromoCode -> {
|
||||
scope.launch {
|
||||
if (action.promoCode.isBlank() && shopState.promoCode == null) {
|
||||
store.dispatchOnMain(ShopAction.ApplyPromoCode.InvalidPromoCode)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val result = shopService.applyPromoCode(action.promoCode)
|
||||
|
||||
result.onSuccess { products ->
|
||||
store.dispatchOnMain(
|
||||
ShopAction.ApplyPromoCode.Success(
|
||||
promoCode = products.first { it.type == shopState.selectedProduct }.appliedDiscount,
|
||||
products = products,
|
||||
),
|
||||
)
|
||||
}
|
||||
result.onFailure { store.dispatchOnMain(ShopAction.ApplyPromoCode.InvalidPromoCode) }
|
||||
}
|
||||
}
|
||||
ShopAction.BuyWithGooglePay -> {
|
||||
shopService.buyWithGooglePay(shopState.selectedProduct)
|
||||
// shopService.subscribeToGooglePayResult(productType = shopState.selectedProduct) { result ->
|
||||
// result.onSuccess {
|
||||
// store.dispatch(ShopAction.BuyWithGooglePay.Success)
|
||||
// }
|
||||
// result.onFailure { error ->
|
||||
// if (error is TangemSdkError.UserCancelled) {
|
||||
// store.dispatch(ShopAction.BuyWithGooglePay.UserCancelled)
|
||||
// } else {
|
||||
// store.dispatch(ShopAction.BuyWithGooglePay.Failure(error))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
is ShopAction.BuyWithGooglePay.HandleGooglePayResponse -> {
|
||||
scope.launch {
|
||||
val result = shopService.handleGooglePayResult(
|
||||
action.resultCode,
|
||||
action.data,
|
||||
shopState.selectedProduct,
|
||||
)
|
||||
result.onSuccess {
|
||||
store.dispatchOnMain(ShopAction.BuyWithGooglePay.Success)
|
||||
}
|
||||
result.onFailure {
|
||||
store.dispatchOnMain(ShopAction.BuyWithGooglePay.Failure(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
ShopAction.LoadProducts -> {
|
||||
scope.launch {
|
||||
shopService.getProducts().fold(
|
||||
onSuccess = { store.dispatchOnMain(ShopAction.LoadProducts.Success(it)) },
|
||||
onFailure = {
|
||||
Timber.e(it)
|
||||
store.dispatchOnMain(ShopAction.LoadProducts.Failure)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
is ShopAction.CheckIfGooglePayAvailable -> {
|
||||
scope.launch {
|
||||
val isAvailable =
|
||||
shopService.checkIfGooglePayAvailable(action.googlePayService).getOrNull()
|
||||
?: false
|
||||
val newAction = if (isAvailable) {
|
||||
ShopAction.CheckIfGooglePayAvailable.Success
|
||||
} else {
|
||||
ShopAction.CheckIfGooglePayAvailable.Failure
|
||||
}
|
||||
store.dispatchOnMain(newAction)
|
||||
}
|
||||
}
|
||||
ShopAction.StartWebCheckout -> {
|
||||
Analytics.send(Shop.Redirected(null))
|
||||
store.dispatchOpenUrl(shopService.getCheckoutUrl(shopState.selectedProduct))
|
||||
store.dispatch(ShopAction.FinishSuccessfulOrder)
|
||||
}
|
||||
|
||||
is ShopAction.FinishSuccessfulOrder -> {
|
||||
scope.launch {
|
||||
shopService.waitForCheckout(shopState.selectedProduct)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package com.tangem.tap.features.shop.redux
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
object ShopReducer {
|
||||
fun reduce(action: Action, state: ShopState): ShopState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, state: ShopState): ShopState {
|
||||
if (action !is ShopAction) return state
|
||||
|
||||
return when (action) {
|
||||
is ShopAction.ApplyPromoCode -> state.copy(promoCode = action.promoCode, promoCodeLoading = true)
|
||||
is ShopAction.LoadProducts.Success -> state.copy(availableProducts = action.products)
|
||||
is ShopAction.ApplyPromoCode.InvalidPromoCode -> state.copy(promoCode = null, promoCodeLoading = false)
|
||||
is ShopAction.ApplyPromoCode.Success -> {
|
||||
state.copy(
|
||||
promoCode = action.promoCode,
|
||||
availableProducts = action.products,
|
||||
promoCodeLoading = false,
|
||||
)
|
||||
}
|
||||
is ShopAction.SelectProduct -> state.copy(selectedProduct = action.productType)
|
||||
|
||||
// TODO: change when we add support for GPay
|
||||
is ShopAction.CheckIfGooglePayAvailable.Failure -> state.copy(isGooglePayAvailable = false)
|
||||
is ShopAction.CheckIfGooglePayAvailable.Success -> state.copy(isGooglePayAvailable = false)
|
||||
|
||||
is ShopAction.ResetState -> ShopState()
|
||||
is ShopAction.SetOrderingDelayBlockVisibility -> state.copy(isOrderingDelayBlockVisible = action.visibility)
|
||||
is ShopAction.BuyWithGooglePay,
|
||||
is ShopAction.LoadProducts,
|
||||
is ShopAction.StartWebCheckout,
|
||||
is ShopAction.CheckIfGooglePayAvailable,
|
||||
is ShopAction.BuyWithGooglePay.Failure,
|
||||
is ShopAction.BuyWithGooglePay.HandleGooglePayResponse,
|
||||
is ShopAction.BuyWithGooglePay.Success,
|
||||
is ShopAction.BuyWithGooglePay.UserCancelled,
|
||||
is ShopAction.FinishSuccessfulOrder,
|
||||
is ShopAction.LoadProducts.Failure,
|
||||
-> state
|
||||
is ShopAction.SalesProductsLoaded -> state.copy(
|
||||
salesProducts = action.salesProducts,
|
||||
)
|
||||
is ShopAction.SalesProductsError -> state.copy(
|
||||
salesProducts = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package com.tangem.tap.features.shop.redux
|
||||
|
||||
import com.tangem.tap.features.shop.domain.models.ProductType
|
||||
import com.tangem.tap.common.shop.data.TangemProduct
|
||||
import com.tangem.tap.features.shop.domain.models.SalesProduct
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class ShopState(
|
||||
val availableProducts: List<TangemProduct> = emptyList(),
|
||||
val selectedProduct: ProductType = ProductType.WALLET_3_CARDS,
|
||||
val salesProducts: List<SalesProduct> = emptyList(),
|
||||
val promoCode: String? = null,
|
||||
val promoCodeLoading: Boolean = false,
|
||||
val isGooglePayAvailable: Boolean = false, // TODO: change when we add support for GPay
|
||||
val isOrderingDelayBlockVisible: Boolean = false,
|
||||
) : StateType {
|
||||
val total: String?
|
||||
get() = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum?.finalValue
|
||||
|
||||
val priceBeforeDiscount: String?
|
||||
get() {
|
||||
val totalSum = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum
|
||||
if (totalSum?.finalValue != totalSum?.beforeDiscount) {
|
||||
return totalSum?.beforeDiscount
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.tap.features.shop.toggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
internal class DefaultShopifyFeatureToggleManager(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : ShopifyFeatureToggleManager {
|
||||
|
||||
override val isDynamicSalesProductsEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("SHOPIFY_DYNAMIC_ENABLED")
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.features.shop.toggles
|
||||
|
||||
/**
|
||||
* Shopify feature toggle manager that provides info about shopify toggle availability
|
||||
*
|
||||
*/
|
||||
interface ShopifyFeatureToggleManager {
|
||||
|
||||
val isDynamicSalesProductsEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
package com.tangem.tap.features.shop.ui
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.OnFocusChangeListener
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.viewModels
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.tap.common.GlobalLayoutStateHandler
|
||||
import com.tangem.tap.common.KeyboardObserver
|
||||
import com.tangem.tap.common.extensions.getQuantityString
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.shop.domain.models.ProductState
|
||||
import com.tangem.tap.features.shop.domain.models.ProductType
|
||||
import com.tangem.tap.features.shop.domain.models.SalesProduct
|
||||
import com.tangem.tap.features.shop.presentation.ShopViewModel
|
||||
import com.tangem.tap.features.shop.redux.ShopAction
|
||||
import com.tangem.tap.features.shop.redux.ShopState
|
||||
import com.tangem.tap.features.shop.toggles.ShopifyFeatureToggleManager
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentShopBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<ShopState> {
|
||||
|
||||
@Inject
|
||||
lateinit var shopifyFeatureToggleManager: ShopifyFeatureToggleManager
|
||||
|
||||
private val binding: FragmentShopBinding by viewBinding(FragmentShopBinding::bind)
|
||||
private var cardTranslationY = 70f
|
||||
|
||||
private lateinit var keyboardObserver: KeyboardObserver
|
||||
|
||||
private val viewModel by viewModels<ShopViewModel>()
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.shopState == newState.shopState
|
||||
}.select { it.shopState }
|
||||
}
|
||||
storeSubscribersList.add(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (shopifyFeatureToggleManager.isDynamicSalesProductsEnabled) {
|
||||
viewModel.getActualSalesInfo()
|
||||
} else {
|
||||
viewModel.checkOrderingDelayBlockVisibility()
|
||||
}
|
||||
|
||||
activity?.onBackPressedDispatcher?.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(ShopAction.ResetState)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
keyboardObserver.unregisterListener()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupCardsImages()
|
||||
setupProductSelection()
|
||||
setupPromoCodeEditText()
|
||||
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
requireActivity().onBackPressed()
|
||||
}
|
||||
|
||||
keyboardObserver = KeyboardObserver(requireActivity()).apply {
|
||||
registerListener { isVisible ->
|
||||
binding.flCards.show(!isVisible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun setupCardsImages() {
|
||||
GlobalLayoutStateHandler(binding.imvSecond).apply {
|
||||
onStateChanged = {
|
||||
cardTranslationY = it.height * 0.15f
|
||||
binding.imvSecond.animate()
|
||||
.translationY(cardTranslationY)
|
||||
.scaleX(0.9f)
|
||||
.scaleY(0.9f)
|
||||
.start()
|
||||
binding.imvThird.animate()
|
||||
.translationY(cardTranslationY * 2)
|
||||
.scaleX(0.8f)
|
||||
.scaleY(0.8f)
|
||||
.start()
|
||||
detach()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupProductSelection() = with(binding) {
|
||||
chipProduct1.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_3_CARDS))
|
||||
}
|
||||
chipProduct2.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_2_CARDS))
|
||||
}
|
||||
chipProduct1.text = chipProduct1.getQuantityString(R.plurals.card_label_card_count, quantity = 3)
|
||||
chipProduct2.text = chipProduct2.getQuantityString(R.plurals.card_label_card_count, quantity = 2)
|
||||
}
|
||||
|
||||
private fun setupPromoCodeEditText() = with(binding) {
|
||||
etPromoCode.setOnEditorActionListener { view, actionId, event ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
val imm: InputMethodManager =
|
||||
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
view.clearFocus()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
|
||||
etPromoCode.onFocusChangeListener = OnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
store.dispatch(ShopAction.ApplyPromoCode(etPromoCode.text.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun newState(state: ShopState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
animateProductSelection(state.selectedProduct)
|
||||
handlePriceState(state)
|
||||
handlePromoCodeState(state)
|
||||
// TODO: https://tangem.slack.com/archives/C01HARKDLQ0/p1691421861756069
|
||||
// if (shopifyFeatureToggleManager.isDynamicSalesProductsEnabled) {
|
||||
// handleNotificationBlock(state)
|
||||
// } else {
|
||||
// handleOrderingDelayBlock(isVisible = state.isOrderingDelayBlockVisible)
|
||||
// }
|
||||
handleButtonsState(state)
|
||||
}
|
||||
|
||||
private fun animateProductSelection(selectedProduct: ProductType) {
|
||||
val show = when (selectedProduct) {
|
||||
ProductType.WALLET_2_CARDS -> false
|
||||
ProductType.WALLET_3_CARDS -> true
|
||||
}
|
||||
showOrHideThirdCardWithAnimation(show)
|
||||
}
|
||||
|
||||
private fun showOrHideThirdCardWithAnimation(show: Boolean) = with(binding) {
|
||||
val translationY = if (show) cardTranslationY * 2 else cardTranslationY
|
||||
if (show) imvThird.show()
|
||||
imvThird.animate()
|
||||
.translationY(translationY)
|
||||
.setListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
super.onAnimationEnd(animation)
|
||||
imvThird.show(show)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun handlePriceState(state: ShopState) = with(binding) {
|
||||
tvTotal.text = state.total
|
||||
tvTotalBeforeDiscount.text = state.priceBeforeDiscount
|
||||
|
||||
pbPrice.show(state.total == null)
|
||||
}
|
||||
|
||||
private fun handlePromoCodeState(state: ShopState) = with(binding) {
|
||||
if (state.promoCode == null && !etPromoCode.hasFocus()) {
|
||||
etPromoCode.setText("")
|
||||
}
|
||||
pbPromoCode.show(state.promoCodeLoading)
|
||||
}
|
||||
|
||||
// private fun handleOrderingDelayBlock(isVisible: Boolean) {
|
||||
// if (isVisible) binding.tvSoldOutDesc.show() else binding.tvSoldOutDesc.hide()
|
||||
// }
|
||||
//
|
||||
// private fun handleNotificationBlock(state: ShopState) {
|
||||
// if (isVisible) {
|
||||
// binding.tvSoldOutDesc.show()
|
||||
// getSelectedSalesProduct(state)?.notification?.let { notification ->
|
||||
// binding.tvSoldOutDesc.text = notification.description
|
||||
// }
|
||||
// } else {
|
||||
// binding.tvSoldOutDesc.hide()
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun handleButtonsState(state: ShopState) = with(binding) {
|
||||
btnPayGooglePay.root.show(state.isGooglePayAvailable)
|
||||
btnAlternativePayment.show(state.isGooglePayAvailable)
|
||||
btnMainAction.show(!state.isGooglePayAvailable)
|
||||
|
||||
if (state.total != null) {
|
||||
btnAlternativePayment.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
|
||||
btnPayGooglePay.root.setOnClickListener { store.dispatch(ShopAction.BuyWithGooglePay) }
|
||||
btnMainAction.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
|
||||
if (state.salesProducts.isNotEmpty()) {
|
||||
getSelectedSalesProduct(state)?.let { selectedProduct ->
|
||||
btnMainAction.text = getMainBtnTextByProductState(
|
||||
productState = selectedProduct.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(ShopAction.ResetState)
|
||||
super.handleOnBackPressed()
|
||||
}
|
||||
|
||||
private fun getSelectedSalesProduct(state: ShopState): SalesProduct? {
|
||||
return state.salesProducts.find {
|
||||
it.productType == state.selectedProduct
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMainBtnTextByProductState(productState: ProductState): String = when (productState) {
|
||||
ProductState.ORDER -> getString(R.string.shop_buy_now)
|
||||
ProductState.SOLD_OUT -> "Sold out" // getString(R.string.sold_out) // todo finalize in next PR
|
||||
ProductState.PRE_ORDER -> "Pre order" // getString(R.string.pre_order) // todo finalize in next PR
|
||||
}
|
||||
}
|
||||
|
|
@ -318,6 +318,7 @@ object TradeCryptoMiddleware {
|
|||
SendRouter.TRANSACTION_ID_KEY to txInfo?.transactionId,
|
||||
SendRouter.DESTINATION_ADDRESS_KEY to txInfo?.destinationAddress,
|
||||
SendRouter.AMOUNT_KEY to txInfo?.amount,
|
||||
SendRouter.TAG_KEY to txInfo?.tag,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue