diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 9c7b3cf106..bd8de9b6be 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -4,6 +4,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.features.details.ui.DetailsConfirmFragment import com.tangem.tap.features.details.ui.DetailsFragment import com.tangem.tap.features.home.HomeFragment import com.tangem.tap.features.send.ui.SendFragment @@ -38,5 +39,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.Wallet -> WalletFragment() AppScreen.Send -> SendFragment() AppScreen.Details -> DetailsFragment() + AppScreen.DetailsConfirm -> DetailsConfirmFragment() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index 125482a2f9..47a422958f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -1,8 +1,10 @@ package com.tangem.tap.common.redux import com.tangem.tap.common.redux.global.GlobalState +import com.tangem.tap.common.redux.global.globalMiddleware import com.tangem.tap.common.redux.navigation.NavigationState import com.tangem.tap.common.redux.navigation.navigationMiddleware +import com.tangem.tap.features.details.redux.DetailsMiddleware import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.home.redux.homeMiddleware import com.tangem.tap.features.send.redux.middlewares.sendMiddleware @@ -13,18 +15,19 @@ import org.rekotlin.Middleware import org.rekotlin.StateType data class AppState( - val navigationState: NavigationState = NavigationState(), - val globalState: GlobalState = GlobalState(), - val walletState: WalletState = WalletState(), - val sendState: SendState = SendState(), - val detailsState: DetailsState = DetailsState() + val navigationState: NavigationState = NavigationState(), + val globalState: GlobalState = GlobalState(), + val walletState: WalletState = WalletState(), + val sendState: SendState = SendState(), + val detailsState: DetailsState = DetailsState() ) : StateType { companion object { fun getMiddleware(): List> { return listOf( - logMiddleware, navigationMiddleware, notificationsMiddleware, - homeMiddleware, walletMiddleware, sendMiddleware + logMiddleware, navigationMiddleware, notificationsMiddleware, globalMiddleware, + homeMiddleware, walletMiddleware, sendMiddleware, + DetailsMiddleware().detailsMiddleware ) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index c1b700abec..60dba22fbc 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -9,4 +9,4 @@ data class NavigationState( val activity: WeakReference? = null ) : StateType -enum class AppScreen { Home, Wallet, Send, Details } \ No newline at end of file +enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 01128b15c7..bddb0b3d6c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -1,10 +1,44 @@ package com.tangem.tap.features.details.redux +import com.tangem.blockchain.common.Wallet import com.tangem.commands.Card +import com.tangem.tap.common.redux.NotificationAction +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.network.coinmarketcap.FiatCurrency +import com.tangem.wallet.R import org.rekotlin.Action sealed class DetailsAction : Action { - data class SetCard(val card: Card): DetailsAction() + data class PrepareScreen( + val card: Card, + val wallet: Wallet?, + val fiatCurrencyName: FiatCurrencyName, + val fiatCurrencies: List? = null, + ): DetailsAction() + + + sealed class EraseWallet : DetailsAction() { + object Check : EraseWallet() + object Proceed : EraseWallet() { + object NotAllowedByCard: EraseWallet(), NotificationAction { + override val messageResource = R.string.details_notification_erase_wallet_not_allowed + } + object NotEmpty: EraseWallet(), NotificationAction { + override val messageResource = R.string.details_notification_erase_wallet_not_possible + } + } + object Confirm : EraseWallet() + object Cancel : EraseWallet() + object Failure : EraseWallet() + object Success : EraseWallet() + } + + sealed class AppCurrencyAction : DetailsAction() { + data class SetCurrencies(val currencies: List) : AppCurrencyAction() + object ChooseAppCurrency : AppCurrencyAction() + object Cancel: AppCurrencyAction() + data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName): AppCurrencyAction() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 78d6334eea..50a0d1d452 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -1 +1,81 @@ package com.tangem.tap.features.details.redux + +import com.tangem.commands.common.network.Result +import com.tangem.common.CompletionResult +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.network.coinmarketcap.CoinMarketCapService +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.rekotlin.Middleware + +class DetailsMiddleware { + val detailsMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + when (action) { + is DetailsAction.PrepareScreen -> { + scope.launch { + val loadedCurrencies = preferencesStorage.getFiatCurrencies() + if (loadedCurrencies.isNullOrEmpty()) { + val response = CoinMarketCapService().getFiatCurrencies() + withContext(Dispatchers.Main) { + when (response) { + is Result.Success -> { + preferencesStorage.saveFiatCurrencies(response.data) + store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(response.data)) + } + } + } + } else { + withContext(Dispatchers.Main) { + store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(loadedCurrencies)) + } + } + } + + } + is DetailsAction.EraseWallet.Proceed -> { + when (store.state.detailsState.eraseWalletState) { + EraseWalletState.Allowed -> + store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) + EraseWalletState.NotAllowedByCard -> + store.dispatch(DetailsAction.EraseWallet.Proceed.NotAllowedByCard) + EraseWalletState.NotEmpty -> + store.dispatch(DetailsAction.EraseWallet.Proceed.NotEmpty) + } + } + is DetailsAction.EraseWallet.Cancel -> { + store.dispatch(NavigationAction.PopBackTo()) + } + is DetailsAction.EraseWallet.Confirm -> { + scope.launch { + val result = tangemSdkManager.eraseWallet() + withContext(Dispatchers.Main) { + when (result) { + is CompletionResult.Success -> { + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + } + } + } + is DetailsAction.AppCurrencyAction.SelectAppCurrency -> { + preferencesStorage.saveAppCurrency(action.fiatCurrencyName) + store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName)) + store.dispatch(WalletAction.LoadFiatRate) + } + } + next(action) + } + } + } +} diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 711b3e67a9..ba2a2b0387 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -1,6 +1,8 @@ package com.tangem.tap.features.details.redux import com.tangem.commands.Card +import com.tangem.commands.Settings +import com.tangem.common.extensions.isZero import com.tangem.tap.common.redux.AppState import org.rekotlin.Action @@ -16,13 +18,76 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { var detailsState = state.detailsState when (action) { - is DetailsAction.SetCard -> { - detailsState = DetailsState(card = action.card, cardInfo = action.card.toCardInfo()) + is DetailsAction.PrepareScreen -> { + detailsState = DetailsState( + card = action.card, wallet = action.wallet, + cardInfo = action.card.toCardInfo(), + appCurrencyState = AppCurrencyState( + action.fiatCurrencyName + ) + ) + } + is DetailsAction.EraseWallet -> { + detailsState = handleEraseWallet(action, detailsState) + } + is DetailsAction.AppCurrencyAction -> { + detailsState = handleAppCurrencyAction(action, detailsState) } } return detailsState } +private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsState): DetailsState { + return when (action) { + DetailsAction.EraseWallet.Check -> { + val notAllowedByCard = state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true + val notEmpty = state.wallet?.transactions?.isNullOrEmpty() != true || + state.wallet.amounts.toList().unzip().second.map { it.value?.isZero() }.contains(false) + val eraseWalletState = when { + notAllowedByCard -> EraseWalletState.NotAllowedByCard + notEmpty -> EraseWalletState.NotEmpty + else -> EraseWalletState.Allowed + } + state.copy(eraseWalletState = eraseWalletState) + } + DetailsAction.EraseWallet.Proceed -> { + if (state.eraseWalletState == EraseWalletState.Allowed) { + state.copy(confirmScreenState = ConfirmScreenState.EraseWallet) + } else { + state + } + } + DetailsAction.EraseWallet.Cancel -> state.copy(eraseWalletState = null) + DetailsAction.EraseWallet.Failure -> state.copy(eraseWalletState = null) + DetailsAction.EraseWallet.Success -> state.copy(eraseWalletState = null) + else -> state + } +} + +private fun handleAppCurrencyAction( + action: DetailsAction.AppCurrencyAction, state: DetailsState +): DetailsState { + return when (action) { + is DetailsAction.AppCurrencyAction.SetCurrencies -> { + state.copy(appCurrencyState = state.appCurrencyState.copy(fiatCurrencies = action.currencies)) + } + DetailsAction.AppCurrencyAction.ChooseAppCurrency -> { + state.copy(appCurrencyState = state.appCurrencyState.copy(showAppCurrencyDialog = true)) + } + DetailsAction.AppCurrencyAction.Cancel -> { + state.copy(appCurrencyState = state.appCurrencyState.copy(showAppCurrencyDialog = false)) + } + is DetailsAction.AppCurrencyAction.SelectAppCurrency -> { + state.copy( + appCurrencyState = state.appCurrencyState.copy( + fiatCurrencyName = action.fiatCurrencyName, showAppCurrencyDialog = false + ) + ) + } + else -> state + } +} + private fun Card.toCardInfo(): CardInfo? { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.cardData?.issuerName ?: return null diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 0ef9e23e9a..304e7902c8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -1,11 +1,19 @@ package com.tangem.tap.features.details.redux +import com.tangem.blockchain.common.Wallet import com.tangem.commands.Card +import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.network.coinmarketcap.FiatCurrency import org.rekotlin.StateType data class DetailsState( val card: Card? = null, - val cardInfo: CardInfo? = null + val wallet: Wallet? = null, + val cardInfo: CardInfo? = null, + val appCurrencyState: AppCurrencyState = AppCurrencyState(), + val eraseWalletState: EraseWalletState? = null, + val confirmScreenState: ConfirmScreenState? = null, ) : StateType data class CardInfo( @@ -13,3 +21,11 @@ data class CardInfo( val issuer: String, val signedHashes: Int ) + +enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty } +enum class ConfirmScreenState { EraseWallet, LongTap, AccessCode, PassCode } +data class AppCurrencyState( + val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, + val showAppCurrencyDialog: Boolean = false, + val fiatCurrencies: List? = null, +) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt index 8ba573d72a..4875ef6449 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -6,6 +6,7 @@ import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store import com.tangem.wallet.R @@ -15,6 +16,8 @@ import org.rekotlin.StoreSubscriber class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber { + var currencySelectionDialog = CurrencySelectionDialog() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { @@ -50,9 +53,6 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber R.string.wallet_button_send is WalletMainButton.CreateWalletButton -> R.string.wallet_button_create_wallet } - btn_main.text = getString(buttonTitle) - btn_main.isEnabled = state.mainButton.enabled + btn_confirm.text = getString(buttonTitle) + btn_confirm.isEnabled = state.mainButton.enabled - btn_main.setOnClickListener { + btn_confirm.setOnClickListener { when (state.mainButton) { is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send()) is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet) @@ -223,7 +223,10 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { store.state.globalState.scanNoteResponse?.card?.let { card -> - store.dispatch(DetailsAction.SetCard(card)) + store.dispatch(DetailsAction.PrepareScreen( + card, store.state.walletState.wallet, + store.state.globalState.appCurrency + )) store.dispatch(NavigationAction.NavigateTo(AppScreen.Details)) true } diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 792fa1942c..e15c215225 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -48,7 +48,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingBottom="14dp" - android:text="Card ID" + android:text="@string/details_card_id" android:textColor="@color/darkGray6" android:textSize="16sp" @@ -71,7 +71,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingBottom="14dp" - android:text="Issuer" + android:text="@string/details_issuer" android:textColor="@color/darkGray6" android:textSize="16sp" app:layout_constraintStart_toStartOf="parent" @@ -92,8 +92,7 @@ android:id="@+id/tv_signed_hashes_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingBottom="14dp" - android:text="Signed" + android:text="@string/details_signed_hashes" android:textColor="@color/darkGray6" android:textSize="16sp" app:layout_constraintStart_toStartOf="parent" @@ -103,13 +102,136 @@ android:id="@+id/tv_signed_hashes" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingBottom="14dp" android:textColor="@color/darkGray1" android:textSize="16sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_issuer" tools:text="48 hashes" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index 1864b49550..66d8297ff7 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -20,8 +20,8 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - app:navigationIcon="@drawable/ic_baseline_arrow_back_24" app:menu="@menu/wallet" + app:navigationIcon="@drawable/ic_baseline_arrow_back_24" app:title="@string/wallet_toolbar_title" /> @@ -37,8 +37,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" - android:overScrollMode="never" - > + android:overScrollMode="never"> + app:layout_constraintTop_toBottomOf="@id/iv_card" /> Tangem Tap Done + Cancel Retry and @@ -75,6 +76,17 @@ Transaction was signed and sent to the blockchain Details + Card settings prohibits from erasing wallet + You balance on this wallet is not zero, or you have unconfirmed transactions + Card ID + Issuer + Signed + Settings + App currency + Card + Validate card + Manage security + Erase wallet \ No newline at end of file