diff --git a/.idea/dictionaries/romanpotapov.xml b/.idea/dictionaries/romanpotapov.xml new file mode 100644 index 0000000000..22eae8bc66 --- /dev/null +++ b/.idea/dictionaries/romanpotapov.xml @@ -0,0 +1,8 @@ + + + + blockchain + tangem + + + \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index e55d54f88e..45e7e92da3 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -4,6 +4,7 @@ import android.app.Application import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.appReducer import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.persistence.PreferencesStorage import com.tangem.wallet.BuildConfig import org.rekotlin.Store import timber.log.Timber @@ -13,10 +14,12 @@ val store = Store( middleware = AppState.getMiddleware(), state = AppState() ) +lateinit var preferencesStorage: PreferencesStorage class TapApplication : Application() { override fun onCreate() { super.onCreate() + preferencesStorage = PreferencesStorage(this) if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) diff --git a/app/src/main/java/com/tangem/tap/common/entities/TapCurrency.kt b/app/src/main/java/com/tangem/tap/common/entities/TapCurrency.kt index 9e00a33525..1aa3f5c351 100644 --- a/app/src/main/java/com/tangem/tap/common/entities/TapCurrency.kt +++ b/app/src/main/java/com/tangem/tap/common/entities/TapCurrency.kt @@ -5,6 +5,6 @@ package com.tangem.tap.common.entities */ class TapCurrency { companion object{ - val main = "USD" + const val DEFAULT_FIAT_CURRENCY = "USD" } } \ No newline at end of file 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 1890cabce0..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,8 @@ 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 import com.tangem.tap.features.wallet.ui.WalletFragment @@ -36,5 +38,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.Home -> HomeFragment() 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/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt index d4d8847066..bf905a0e9e 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt @@ -6,6 +6,8 @@ import com.google.zxing.BarcodeFormat import com.google.zxing.EncodeHintType import com.google.zxing.qrcode.QRCodeWriter import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.network.coinmarketcap.FiatCurrency import java.math.BigDecimal import java.math.RoundingMode import java.text.DecimalFormat @@ -46,10 +48,10 @@ fun BigDecimal.toFormattedString(decimals: Int): String { return df.format(bd) } -fun BigDecimal.toFiatString(rateValue: BigDecimal): String? { +fun BigDecimal.toFiatString(rateValue: BigDecimal, fiatCurrencyName: FiatCurrencyName): String? { var fiatValue = rateValue.multiply(this) fiatValue = fiatValue.setScale(2, RoundingMode.DOWN) - return "≈ USD  $fiatValue" + return "≈ ${fiatCurrencyName}  $fiatValue" } fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString() @@ -67,4 +69,6 @@ fun BigDecimal.isGreaterThanOrEqual(value: BigDecimal): Boolean { fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean { val compareResult = this.compareTo(value) return compareResult == -1 || compareResult == 0 -} \ No newline at end of file +} + +fun FiatCurrency.toFormattedString(): String = "${this.name} (${this.symbol}) - ${this.sign}" \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index 68a809b4ec..c02184fd51 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common.redux import com.tangem.tap.common.redux.global.globalReducer import com.tangem.tap.common.redux.navigation.NavigationReducer import com.tangem.tap.features.send.redux.reducers.SendScreenReducer +import com.tangem.tap.features.details.redux.DetailsReducer import com.tangem.tap.features.wallet.redux.WalletReducer import org.rekotlin.Action @@ -11,10 +12,11 @@ fun appReducer(action: Action, state: AppState?): AppState { if (action is AppAction.RestoreState) return action.state return AppState( - navigationState = NavigationReducer.reduce(action, state), - globalState = globalReducer(action, state), - walletState = WalletReducer.reduce(action, state), - sendState = SendScreenReducer.reduce(action, state.sendState) + navigationState = NavigationReducer.reduce(action, state), + globalState = globalReducer(action, state), + walletState = WalletReducer.reduce(action, state), + sendState = SendScreenReducer.reduce(action, state.sendState), + detailsState = DetailsReducer.reduce(action, state) ) } 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 9e3bdf3e9b..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,11 @@ 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 import com.tangem.tap.features.send.redux.states.SendState @@ -12,17 +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 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/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 8a4412c114..b262647600 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -7,5 +7,11 @@ import java.math.BigDecimal sealed class GlobalAction : Action { data class SaveScanNoteResponse(val scanNoteResponse: ScanNoteResponse) : GlobalAction() - data class SetFiatRate(val fiatRates: Pair) : GlobalAction() + data class SetFiatRate( + val fiatRates: Pair + ) : GlobalAction() + data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction() + object RestoreAppCurrency : GlobalAction() { + data class Success(val appCurrency: FiatCurrencyName) : GlobalAction() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt new file mode 100644 index 0000000000..d7ec41960f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.common.redux.global + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.preferencesStorage +import com.tangem.tap.store +import org.rekotlin.Middleware + +val globalMiddleware: Middleware = { dispatch, appState -> + { nextDispatch -> + { action -> + when (action) { + is GlobalAction.RestoreAppCurrency -> { + store.dispatch(GlobalAction.RestoreAppCurrency.Success( + preferencesStorage.getAppCurrency() + )) + } + } + nextDispatch(action) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 71fa1cfb50..b3f496719c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -13,9 +13,15 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.SaveScanNoteResponse -> newState = newState.copy(scanNoteResponse = action.scanNoteResponse) is GlobalAction.SetFiatRate -> { - val rates = newState.fiatRates.rates.toMutableMap() + val rates = newState.conversionRates.rates.toMutableMap() rates[action.fiatRates.first] = action.fiatRates.second - newState = newState.copy(fiatRates = FiatRates(rates)) + newState = newState.copy(conversionRates = ConversionRates(rates)) + } + is GlobalAction.ChangeAppCurrency -> { + newState = newState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf())) + } + is GlobalAction.RestoreAppCurrency.Success -> { + newState = newState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf())) } } return newState diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 6ea19cc94e..9da6906d9c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -1,22 +1,33 @@ package com.tangem.tap.common.redux.global +import com.tangem.commands.common.network.TangemService +import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY +import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import org.rekotlin.StateType import java.math.BigDecimal data class GlobalState( val scanNoteResponse: ScanNoteResponse? = null, val tapWalletManager: TapWalletManager = TapWalletManager(), - val fiatRates: FiatRates = FiatRates(emptyMap()), + val payIdManager: PayIdManager = PayIdManager(), + val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(), + val tangemService: TangemService = TangemService(), + val conversionRates: ConversionRates = ConversionRates(emptyMap()), + val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY ) : StateType -data class FiatRates( - val rates: Map +data class ConversionRates( + val rates: Map, ) { - fun getRateForCryptoCurrency(currency: String): BigDecimal? { - return rates[currency] + + fun getRate(cryptoCurrency: CryptoCurrencyName): BigDecimal? { + return rates[cryptoCurrency] } } +typealias CryptoCurrencyName = String +typealias FiatCurrencyName = String 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 7fcff63909..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 } \ 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/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 9fdfca3cd7..3c9b613d01 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -3,6 +3,8 @@ package com.tangem.tap.domain import androidx.activity.ComponentActivity import com.tangem.* import com.tangem.commands.CommandResponse +import com.tangem.commands.PurgeWalletCommand +import com.tangem.commands.PurgeWalletResponse import com.tangem.common.CompletionResult import com.tangem.common.extensions.CardType import com.tangem.tangem_sdk_new.extensions.init @@ -28,6 +30,10 @@ class TangemSdkManager(val activity: ComponentActivity) { return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask()) } + suspend fun eraseWallet(): CompletionResult { + return runTaskAsyncReturnOnMain(PurgeWalletCommand()) + } + private suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null ): CompletionResult = diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 373d4d5f41..30e36dfd81 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -8,6 +8,8 @@ import com.tangem.commands.common.network.Result import com.tangem.commands.common.network.TangemService import com.tangem.common.extensions.toHexString import com.tangem.tap.TapConfig +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.features.wallet.redux.PayIdState @@ -54,15 +56,15 @@ class TapWalletManager { result?.let { handlePayIdResult(it) } } - suspend fun loadFiatRate() { + suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName) { val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet val blockchainCurrency = wallet?.blockchain?.currency val tokenCurrency = wallet?.token?.symbol - val blockchainRate = blockchainCurrency?.let { coinMarketCapService.getRate(it) } - val tokenRate = tokenCurrency?.let { coinMarketCapService.getRate(it) } + val blockchainRate = blockchainCurrency?.let { coinMarketCapService.getRate(it, fiatCurrency) } + val tokenRate = tokenCurrency?.let { coinMarketCapService.getRate(it, fiatCurrency) } - val results = mutableListOf?>>() + val results = mutableListOf?>>() if (blockchainCurrency != null) results.add(blockchainCurrency to blockchainRate) if (tokenCurrency != null) results.add(tokenCurrency to tokenRate) @@ -83,7 +85,7 @@ class TapWalletManager { store.dispatch(WalletAction.LoadWallet) store.dispatch(WalletAction.LoadFiatRate) store.dispatch(WalletAction.LoadPayId) - } else if (data.card.status == CardStatus.Empty){ + } else if (data.card.status == CardStatus.Empty) { store.dispatch(WalletAction.EmptyWallet) } else { store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) @@ -153,7 +155,7 @@ class TapWalletManager { } } - private suspend fun handleFiatRatesResult(results: List?>>) { + private suspend fun handleFiatRatesResult(results: List?>>) { withContext(Dispatchers.Main) { results.map { when (it.second) { 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 new file mode 100644 index 0000000000..bddb0b3d6c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -0,0 +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 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 new file mode 100644 index 0000000000..50a0d1d452 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -0,0 +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 new file mode 100644 index 0000000000..ba2a2b0387 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -0,0 +1,96 @@ +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 + +class DetailsReducer { + companion object { + fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state) + } +} + +private fun internalReduce(action: Action, state: AppState): DetailsState { + + if (action !is DetailsAction) return state.detailsState + + var detailsState = state.detailsState + when (action) { + 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 + val signedHashes = this.walletSignedHashes ?: return null + return CardInfo(cardId, issuer, signedHashes) +} \ No newline at end of file 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 new file mode 100644 index 0000000000..304e7902c8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -0,0 +1,31 @@ +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 wallet: Wallet? = null, + val cardInfo: CardInfo? = null, + val appCurrencyState: AppCurrencyState = AppCurrencyState(), + val eraseWalletState: EraseWalletState? = null, + val confirmScreenState: ConfirmScreenState? = null, +) : StateType + +data class CardInfo( + val cardId: String, + 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/CurrencySelectionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/CurrencySelectionDialog.kt new file mode 100644 index 0000000000..a2267999e0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/CurrencySelectionDialog.kt @@ -0,0 +1,45 @@ +package com.tangem.tap.features.details.ui + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.tangem.tap.common.extensions.toFormattedString +import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.tap.network.coinmarketcap.FiatCurrency +import com.tangem.tap.store +import com.tangem.wallet.R + +class CurrencySelectionDialog { + + var dialog: AlertDialog? = null + + fun show(currencies: List, currentAppCurrency: FiatCurrencyName, context: Context) { + + if (dialog == null) { + val currenciesToShow = currencies.map { it.toFormattedString() }.toTypedArray() + var currentSelection = currencies.indexOfFirst { it.symbol == currentAppCurrency } + + dialog = MaterialAlertDialogBuilder(context) + .setTitle(context.getString(R.string.details_currency)) + .setNeutralButton(context.getString(R.string.generic_cancel)) { _, _ -> + store.dispatch(DetailsAction.AppCurrencyAction.Cancel) + } + .setPositiveButton(context.getString(R.string.generic_done)) { _, _ -> + val selectedCurrency = currencies[currentSelection] + store.dispatch(DetailsAction.AppCurrencyAction.SelectAppCurrency(selectedCurrency.symbol)) + } + .setOnDismissListener { + store.dispatch(DetailsAction.AppCurrencyAction.Cancel) + } + .setSingleChoiceItems(currenciesToShow, currentSelection) { _, which -> + currentSelection = which + }.show() + } + } + + fun clear() { + dialog = null + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt new file mode 100644 index 0000000000..a13937adbf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt @@ -0,0 +1,77 @@ +package com.tangem.tap.features.details.ui + +import android.os.Bundle +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.tangem.tap.common.extensions.getDrawable +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.ConfirmScreenState +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 +import kotlinx.android.synthetic.main.fragment_details_confirm.* +import org.rekotlin.StoreSubscriber + +class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm), + StoreSubscriber { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.detailsState == newState.detailsState + }.select { it.detailsState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + toolbar.setNavigationOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + } + + + override fun newState(state: DetailsState) { + if (activity == null) return + + when (state.confirmScreenState) { + ConfirmScreenState.EraseWallet -> { + toolbar.title = getString(R.string.details_erase_wallet) + btn_confirm.text = getString(R.string.details_erase_wallet) + btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds( + null, null, getDrawable(R.drawable.ic_send), null + ) + btn_confirm.setOnClickListener { store.dispatch(DetailsAction.EraseWallet.Confirm) } + } + ConfirmScreenState.LongTap -> TODO() + ConfirmScreenState.AccessCode -> TODO() + ConfirmScreenState.PassCode -> TODO() + null -> TODO() + } + + + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..eeca408c37 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -0,0 +1,90 @@ +package com.tangem.tap.features.details.ui + +import android.os.Bundle +import android.view.View +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 +import kotlinx.android.synthetic.main.fragment_details.* +import kotlinx.android.synthetic.main.fragment_wallet.toolbar +import org.rekotlin.StoreSubscriber + +class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber { + + private var currencySelectionDialog = CurrencySelectionDialog() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.detailsState == newState.detailsState + }.select { it.detailsState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + toolbar.setNavigationOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + } + + + override fun newState(state: DetailsState) { + if (activity == null) return + + + if (state.cardInfo != null) { + tv_card_id.text = state.cardInfo.cardId + tv_issuer.text = state.cardInfo.issuer + tv_signed_hashes.text = state.cardInfo.signedHashes.toString() + } + + tv_erase_wallet.setOnClickListener { + store.dispatch(DetailsAction.EraseWallet.Check) + store.dispatch(DetailsAction.EraseWallet.Proceed) + } + + tv_app_currency.text = state.appCurrencyState.fiatCurrencyName + + tv_app_currency_title.setOnClickListener { + store.dispatch(DetailsAction.AppCurrencyAction.ChooseAppCurrency) + } + + if (state.appCurrencyState.showAppCurrencyDialog && + !state.appCurrencyState.fiatCurrencies.isNullOrEmpty()) { + currencySelectionDialog.show( + state.appCurrencyState.fiatCurrencies, + state.appCurrencyState.fiatCurrencyName, + requireContext() + ) + } else { + currencySelectionDialog.clear() + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 6a25e7251d..bbb05df3e4 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -5,6 +5,7 @@ import android.net.Uri import androidx.core.content.ContextCompat.startActivity 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.scope @@ -25,6 +26,7 @@ val homeMiddleware: Middleware = { dispatch, state -> withContext(Dispatchers.Main) { when (result) { is CompletionResult.Success -> { + store.dispatch(GlobalAction.RestoreAppCurrency) store.state.globalState.tapWalletManager.onCardScanned(result.data) store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt index efeae4552c..5129a95646 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt @@ -2,7 +2,6 @@ package com.tangem.tap.features.send.redux.reducers import com.tangem.common.extensions.isZero import com.tangem.tap.common.CurrencyConverter -import com.tangem.tap.common.entities.TapCurrency import com.tangem.tap.common.extensions.isNegative import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.features.send.redux.AmountAction @@ -13,6 +12,7 @@ import com.tangem.tap.features.send.redux.states.AmountState import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.redux.states.SendState import com.tangem.tap.features.send.redux.states.Value +import com.tangem.tap.store import java.math.BigDecimal /** @@ -42,7 +42,7 @@ class AmountReducer : SendInternalReducer { state.copy( viewAmountValue = fiatToSend.stripZeroPlainString(), viewBalanceValue = converter.toFiat(state.balanceCrypto).stripZeroPlainString(), - mainCurrency = Value(MainCurrencyType.FIAT, TapCurrency.main), + mainCurrency = Value(MainCurrencyType.FIAT, store.state.globalState.appCurrency), maxLengthOfAmount = sendState.getDecimals(action.mainCurrency), cursorAtTheSamePosition = false ) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt index 0776c99fe4..91726cb199 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt @@ -3,11 +3,11 @@ package com.tangem.tap.features.send.redux.reducers import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Wallet import com.tangem.tap.common.CurrencyConverter -import com.tangem.tap.common.entities.TapCurrency import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt import com.tangem.tap.features.send.redux.SendScreenAction import com.tangem.tap.features.send.redux.states.* +import com.tangem.tap.store /** [REDACTED_AUTHOR] @@ -163,7 +163,7 @@ class ReceiptReducer : SendInternalReducer { private fun determineSymbols(wallet: Wallet): ReceiptSymbols { return ReceiptSymbols( - fiat = TapCurrency.main, + fiat = store.state.globalState.appCurrency, crypto = wallet.blockchain.currency, token = wallet.amounts[AmountType.Token]?.currencySymbol ) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 6d932b4654..858b626b15 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -65,7 +65,7 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer { } private fun createCurrencyConverter(walletManager: WalletManager): CurrencyConverter { - val rate = store.state.globalState.fiatRates.getRateForCryptoCurrency(walletManager.wallet.blockchain.currency) + val rate = store.state.globalState.conversionRates.getRate(walletManager.wallet.blockchain.currency) return if (rate == null) CurrencyConverter(BigDecimal.ONE) else CurrencyConverter(rate) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index 4d3cc3cd98..7d3df6c928 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -60,7 +60,7 @@ enum class SendButtonState { data class AmountState( val viewAmountValue: String = BigDecimal.ZERO.toPlainString(), val viewBalanceValue: String = BigDecimal.ZERO.toPlainString(), - val mainCurrency: Value = Value(MainCurrencyType.FIAT, TapCurrency.main), + val mainCurrency: Value = Value(MainCurrencyType.FIAT, TapCurrency.DEFAULT_FIAT_CURRENCY), val typeOfAmount: AmountType = AmountType.Coin, val amountToSendCrypto: BigDecimal = BigDecimal.ZERO, val balanceCrypto: BigDecimal = BigDecimal.ZERO, diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 5665e0ffa2..8201b03f38 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -197,7 +197,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { private fun restoreMainCurrency(): MainCurrencyType { val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE) - val mainCurrency = sp.getString("mainCurrency", TapCurrency.main) + val mainCurrency = sp.getString("mainCurrency", TapCurrency.DEFAULT_FIAT_CURRENCY) val foundType = MainCurrencyType.values() .firstOrNull { it.name.toLowerCase() == mainCurrency!!.toLowerCase() } ?: MainCurrencyType.FIAT return foundType diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index af4908547c..4e1d6ea32e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Wallet import com.tangem.commands.Card import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.NotificationAction +import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.TapError import com.tangem.wallet.R import org.rekotlin.Action @@ -24,7 +25,7 @@ sealed class WalletAction : Action { } object LoadFiatRate : WalletAction() { - data class Success(val fiatRates: Pair) : WalletAction() + data class Success(val fiatRates: Pair) : WalletAction() object Failure : WalletAction() } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt index 0bd11d47b6..558f348582 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -42,7 +42,7 @@ val walletMiddleware: Middleware = { dispatch, state -> } is WalletAction.LoadFiatRate -> { scope.launch { - store.state.globalState.tapWalletManager.loadFiatRate() + store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency) } } is WalletAction.LoadArtwork -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt index f0b29211a5..d015483883 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt @@ -86,10 +86,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) } is WalletAction.LoadWallet.Success -> { + val fiatCurrencySymbol = state.globalState.appCurrency val token = action.wallet.amounts[AmountType.Token] val tokenData = if (token != null) { - val tokenFiatRate = state.globalState.fiatRates.getRateForCryptoCurrency(token.currencySymbol) - val tokenFiatAmount = tokenFiatRate?.let { token.value?.toFiatString(it) } + val tokenFiatRate = state.globalState.conversionRates.getRate(token.currencySymbol) + val tokenFiatAmount = tokenFiatRate?.let { token.value?.toFiatString(it, fiatCurrencySymbol) } TokenData( token.value?.toFormattedString(token.decimals) ?: "", token.currencySymbol, tokenFiatAmount) @@ -97,8 +98,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState { null } val amount = action.wallet.amounts[AmountType.Coin]?.value - val fiatRate = state.globalState.fiatRates.getRateForCryptoCurrency(action.wallet.blockchain.currency) - val fiatAmount = fiatRate?.let { amount?.toFiatString(it) } + val fiatRate = state.globalState.conversionRates.getRate(action.wallet.blockchain.currency) + val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) } val pendingTransactions = action.wallet.transactions .toPendingTransactions(action.wallet.address) @@ -135,16 +136,24 @@ private fun internalReduce(action: Action, state: AppState): WalletState { errorMessage = action.errorMessage ) ) + is WalletAction.LoadFiatRate -> { + newState.copy(currencyData = newState.currencyData.copy( + fiatAmount = null, + token = newState.currencyData.token?.copy(fiatAmount = null)) + ) + } is WalletAction.LoadFiatRate.Success -> { - val rate = action.fiatRates.second + val rate = action.fiatRates.second ?: return newState val currency = action.fiatRates.first val fiatAmount = if (currency == newState.wallet?.blockchain?.currency) { - newState.wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatString(rate) + newState.wallet?.amounts?.get(AmountType.Coin)?.value + ?.toFiatString(rate, state.globalState.appCurrency) } else { newState.currencyData.fiatAmount } val tokenFiatAmount = if (currency == newState.wallet?.token?.symbol) { - newState.wallet?.amounts?.get(AmountType.Token)?.value?.toFiatString(rate) + newState.wallet?.amounts?.get(AmountType.Token)?.value + ?.toFiatString(rate, state.globalState.appCurrency) } else { newState.currencyData.token?.fiatAmount } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index d461023e6d..8aa533ea8d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -3,8 +3,12 @@ package com.tangem.tap.features.wallet.ui import android.app.Dialog import android.graphics.Bitmap import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import androidx.activity.OnBackPressedCallback +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.TransitionInflater @@ -12,7 +16,9 @@ import com.google.android.material.snackbar.Snackbar import com.tangem.tap.common.extensions.getDrawable import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog import com.tangem.tap.features.wallet.ui.dialogs.PayIdDialog @@ -35,6 +41,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), 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) @@ -211,4 +219,25 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + store.state.globalState.scanNoteResponse?.card?.let { card -> + store.dispatch(DetailsAction.PrepareScreen( + card, store.state.walletState.wallet, + store.state.globalState.appCurrency + )) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Details)) + true + } + false + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.wallet, menu) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt index 0c356d478e..47889ff06d 100644 --- a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt @@ -12,9 +12,14 @@ interface CoinMarketCapApi { @GET("v1/tools/price-conversion") suspend fun getRateInfo( @Query("amount") amount: Int, - @Query("symbol") cryptoId: String + @Query("symbol") cryptoCurrencyName: String, + @Query("convert") fiatCurrencyName: String? = null ): RateInfoResponse + @GET("v1/fiat/map") + suspend fun getFiatMap(): FiatMapResponse + + companion object { private const val baseUrl = "https://pro-api.coinmarketcap.com/" diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt index 5350bad116..4dc7931623 100644 --- a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt @@ -2,6 +2,7 @@ package com.tangem.tap.network.coinmarketcap import com.tangem.commands.common.network.Result import com.tangem.commands.common.network.performRequest +import com.tangem.tap.common.redux.global.FiatCurrencyName import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.math.BigDecimal @@ -9,10 +10,20 @@ import java.math.BigDecimal class CoinMarketCapService { private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create() } - suspend fun getRate(currency: String): Result = withContext(Dispatchers.IO) { - val response = performRequest { api.getRateInfo(1, currency) } + suspend fun getRate( + currency: String, fiatCurrency: FiatCurrencyName? = null + ): Result = withContext(Dispatchers.IO) { + val response = performRequest { api.getRateInfo(1, currency, fiatCurrency) } return@withContext when (response) { - is Result.Success -> Result.Success(response.data.data.quote.usd.price) + is Result.Success -> Result.Success(response.data.data.getRate()) + is Result.Failure -> response + } + } + + suspend fun getFiatCurrencies(): Result> = withContext(Dispatchers.IO) { + val response = performRequest { api.getFiatMap() } + return@withContext when (response) { + is Result.Success -> Result.Success(response.data.data.sortedBy { it.name }) is Result.Failure -> response } } diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/Response.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/Response.kt index f4ec9fa804..7320d9467c 100644 --- a/app/src/main/java/com/tangem/tap/network/coinmarketcap/Response.kt +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/Response.kt @@ -5,21 +5,14 @@ import com.squareup.moshi.JsonClass import java.math.BigDecimal @JsonClass(generateAdapter = true) -data class RateInfoResponse( - val status: Status, - val data: RateData -) +class RateInfoResponse : CoinMarketResponse() @JsonClass(generateAdapter = true) data class RateData( - val quote: Quote -) - -@JsonClass(generateAdapter = true) -data class Quote( - @Json(name = "USD") - val usd: CurrencyRate -) + val quote: Map +) { + fun getRate(): BigDecimal = quote.values.first().price +} @JsonClass(generateAdapter = true) data class CurrencyRate( @@ -38,4 +31,21 @@ data class Status( @Json(name = "credit_count") val creditCount: Int, val notice: String? -) \ No newline at end of file +) + +@JsonClass(generateAdapter = true) +class FiatMapResponse : CoinMarketResponse>() + +@JsonClass(generateAdapter = true) +open class CoinMarketResponse { + lateinit var status: Status + lateinit var data: T +} + +@JsonClass(generateAdapter = true) +data class FiatCurrency( + val id: Int, + val name: String, + val sign: String, + val symbol: String +) diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt new file mode 100644 index 0000000000..bbaa2aa7b2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -0,0 +1,54 @@ +package com.tangem.tap.persistence + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +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 + + +class PreferencesStorage(applicationContext: Application) { + + private val preferences: SharedPreferences by lazy { + applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + } + + private val fiatCurrenciesAdapter: JsonAdapter> by lazy { + val moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + val type = Types.newParameterizedType(List::class.java, FiatCurrency::class.java) + moshi.adapter(type) + } + + fun getAppCurrency(): FiatCurrencyName { + return preferences.getString(APP_CURRENCY_KEY, DEFAULT_FIAT_CURRENCY) + ?: DEFAULT_FIAT_CURRENCY + } + + fun saveAppCurrency(fiatCurrencyName: FiatCurrencyName) { + return preferences.edit().putString(APP_CURRENCY_KEY, fiatCurrencyName).apply() + } + + fun getFiatCurrencies(): List? { + val json = preferences.getString(FIAT_CURRENCIES_KEY, "") + return if (json.isNullOrBlank()) null else fiatCurrenciesAdapter.fromJson(json) as List + } + + fun saveFiatCurrencies(currencies: List) { + val json: String = fiatCurrenciesAdapter.toJson(currencies) + return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply() + } + + companion object { + private const val PREFERENCES_NAME = "tapPrefs" + private const val APP_CURRENCY_KEY = "appCurrency" + private const val FIAT_CURRENCIES_KEY = "fiatCurrencies" + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml new file mode 100644 index 0000000000..e15c215225 --- /dev/null +++ b/app/src/main/res/layout/fragment_details.xml @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details_confirm.xml b/app/src/main/res/layout/fragment_details_confirm.xml new file mode 100644 index 0000000000..528dc0aa4c --- /dev/null +++ b/app/src/main/res/layout/fragment_details_confirm.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index ccaf8611ff..66d8297ff7 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -20,6 +20,7 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" + app:menu="@menu/wallet" app:navigationIcon="@drawable/ic_baseline_arrow_back_24" app:title="@string/wallet_toolbar_title" /> @@ -36,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" /> + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0eaa3b368..a70f1fea25 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,6 +3,7 @@ Tangem Tap Done + Cancel Retry and @@ -74,4 +75,18 @@ Maximum amount 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