Updated on 2026-08-14
This commit is contained in:
commit
01e4ad8abb
40 changed files with 1100 additions and 71 deletions
8
.idea/dictionaries/romanpotapov.xml
generated
Normal file
8
.idea/dictionaries/romanpotapov.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="romanpotapov">
|
||||
<words>
|
||||
<w>blockchain</w>
|
||||
<w>tangem</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ package com.tangem.tap.common.entities
|
|||
*/
|
||||
class TapCurrency {
|
||||
companion object{
|
||||
val main = "USD"
|
||||
const val DEFAULT_FIAT_CURRENCY = "USD"
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
fun FiatCurrency.toFormattedString(): String = "${this.name} (${this.symbol}) - ${this.sign}"
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Middleware<AppState>> {
|
||||
return listOf(
|
||||
logMiddleware, navigationMiddleware, notificationsMiddleware,
|
||||
homeMiddleware, walletMiddleware, sendMiddleware
|
||||
logMiddleware, navigationMiddleware, notificationsMiddleware, globalMiddleware,
|
||||
homeMiddleware, walletMiddleware, sendMiddleware,
|
||||
DetailsMiddleware().detailsMiddleware
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, BigDecimal>) : GlobalAction()
|
||||
data class SetFiatRate(
|
||||
val fiatRates: Pair<CryptoCurrencyName, BigDecimal>
|
||||
) : GlobalAction()
|
||||
data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction()
|
||||
object RestoreAppCurrency : GlobalAction() {
|
||||
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AppState> = { dispatch, appState ->
|
||||
{ nextDispatch ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is GlobalAction.RestoreAppCurrency -> {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency.Success(
|
||||
preferencesStorage.getAppCurrency()
|
||||
))
|
||||
}
|
||||
}
|
||||
nextDispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String, BigDecimal>
|
||||
data class ConversionRates(
|
||||
val rates: Map<CryptoCurrencyName, BigDecimal>,
|
||||
) {
|
||||
fun getRateForCryptoCurrency(currency: String): BigDecimal? {
|
||||
return rates[currency]
|
||||
|
||||
fun getRate(cryptoCurrency: CryptoCurrencyName): BigDecimal? {
|
||||
return rates[cryptoCurrency]
|
||||
}
|
||||
}
|
||||
|
||||
typealias CryptoCurrencyName = String
|
||||
typealias FiatCurrencyName = String
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ data class NavigationState(
|
|||
val activity: WeakReference<FragmentActivity>? = null
|
||||
) : StateType
|
||||
|
||||
enum class AppScreen { Home, Wallet, Send }
|
||||
enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm }
|
||||
|
|
@ -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<PurgeWalletResponse> {
|
||||
return runTaskAsyncReturnOnMain(PurgeWalletCommand())
|
||||
}
|
||||
|
||||
private suspend fun <T : CommandResponse> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null
|
||||
): CompletionResult<T> =
|
||||
|
|
|
|||
|
|
@ -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<Pair<String, Result<BigDecimal>?>>()
|
||||
val results = mutableListOf<Pair<CryptoCurrencyName, Result<BigDecimal>?>>()
|
||||
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<Pair<String, Result<BigDecimal>?>>) {
|
||||
private suspend fun handleFiatRatesResult(results: List<Pair<CryptoCurrencyName, Result<BigDecimal>?>>) {
|
||||
withContext(Dispatchers.Main) {
|
||||
results.map {
|
||||
when (it.second) {
|
||||
|
|
|
|||
|
|
@ -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<FiatCurrencyName>? = 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<FiatCurrency>) : AppCurrencyAction()
|
||||
object ChooseAppCurrency : AppCurrencyAction()
|
||||
object Cancel: AppCurrencyAction()
|
||||
data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName): AppCurrencyAction()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<AppState> = { 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<FiatCurrency>? = null,
|
||||
)
|
||||
|
|
@ -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<FiatCurrency>, 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<DetailsState> {
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<DetailsState> {
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<AppState> = { 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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<MainCurrencyType> = Value(MainCurrencyType.FIAT, TapCurrency.main),
|
||||
val mainCurrency: Value<MainCurrencyType> = Value(MainCurrencyType.FIAT, TapCurrency.DEFAULT_FIAT_CURRENCY),
|
||||
val typeOfAmount: AmountType = AmountType.Coin,
|
||||
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
|
||||
val balanceCrypto: BigDecimal = BigDecimal.ZERO,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String, BigDecimal>) : WalletAction()
|
||||
data class Success(val fiatRates: Pair<CryptoCurrencyName, BigDecimal?>) : WalletAction()
|
||||
object Failure : WalletAction()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
}
|
||||
is WalletAction.LoadFiatRate -> {
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.loadFiatRate()
|
||||
store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency)
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadArtwork -> {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Walle
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
|
|
@ -61,6 +68,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
|
||||
toolbar.setNavigationOnClickListener {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
|
|
@ -148,10 +156,10 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
is WalletMainButton.SendButton -> 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<Walle
|
|||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.details_menu -> {
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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/"
|
||||
|
||||
|
|
|
|||
|
|
@ -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<BigDecimal> = withContext(Dispatchers.IO) {
|
||||
val response = performRequest { api.getRateInfo(1, currency) }
|
||||
suspend fun getRate(
|
||||
currency: String, fiatCurrency: FiatCurrencyName? = null
|
||||
): Result<BigDecimal> = 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<List<FiatCurrency>> = 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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<RateData>()
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RateData(
|
||||
val quote: Quote
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Quote(
|
||||
@Json(name = "USD")
|
||||
val usd: CurrencyRate
|
||||
)
|
||||
val quote: Map<String, CurrencyRate>
|
||||
) {
|
||||
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?
|
||||
)
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class FiatMapResponse : CoinMarketResponse<List<FiatCurrency>>()
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
open class CoinMarketResponse<T : Any> {
|
||||
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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<List<FiatCurrency>> 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<FiatCurrency>? {
|
||||
val json = preferences.getString(FIAT_CURRENCIES_KEY, "")
|
||||
return if (json.isNullOrBlank()) null else fiatCurrenciesAdapter.fromJson(json) as List<FiatCurrency>
|
||||
}
|
||||
|
||||
fun saveFiatCurrencies(currencies: List<FiatCurrency>) {
|
||||
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"
|
||||
}
|
||||
|
||||
}
|
||||
240
app/src/main/res/layout/fragment_details.xml
Normal file
240
app/src/main/res/layout/fragment_details.xml
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:fitsSystemWindows="true"
|
||||
app:liftOnScroll="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
|
||||
app:title="@string/details_toolbar_title" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/sv_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="35dp"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="never"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cl_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="33dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_card_id_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="14dp"
|
||||
android:text="@string/details_card_id"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_card_id"
|
||||
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_toTopOf="parent"
|
||||
tools:text="0000 0000 0000 0000" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_issuer_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="14dp"
|
||||
android:text="@string/details_issuer"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_card_id_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_issuer"
|
||||
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_card_id"
|
||||
tools:text="Tangem" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_signed_hashes_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/details_signed_hashes"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_issuer_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_signed_hashes"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_issuer"
|
||||
tools:text="48 hashes" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_settings_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="@string/details_settings"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/colorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes_title" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/separatorGrey2"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_settings_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_app_currency"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:drawablePadding="15dp"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="16sp"
|
||||
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_settings_title"
|
||||
tools:text="USD" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_app_currency_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="@string/details_currency"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_settings_title" />
|
||||
|
||||
<!-- <androidx.constraintlayout.widget.Group-->
|
||||
<!-- android:id="@+id/group_app_currency"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- app:constraint_referenced_ids="tv_app_currency_title, tv_app_currency" />-->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_card_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="22dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="@string/details_card"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/colorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_app_currency_title" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/separatorGrey2"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_card_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_validate_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:drawablePadding="15dp"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_card_title"
|
||||
android:text="@string/details_validate_card" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_security"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:drawablePadding="15dp"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="16sp"
|
||||
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_validate_card"
|
||||
tools:text="Long Tap" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_security_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/details_manage_security"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_validate_card" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_erase_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:drawablePadding="15dp"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_security_title"
|
||||
android:text="@string/details_erase_wallet" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
103
app/src/main/res/layout/fragment_details_confirm.xml
Normal file
103
app/src/main/res/layout/fragment_details_confirm.xml
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:fitsSystemWindows="true"
|
||||
app:liftOnScroll="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_baseline_arrow_back_24" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cl_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="33dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_warning"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginTop="110dp"
|
||||
android:src="@drawable/ic_warning"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_warning"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Warning"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="32sp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:textColor="@color/warning"
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_warning"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/tv_warning_description"
|
||||
android:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Et quis vitae dictumst consequat."
|
||||
android:textSize="16sp"
|
||||
android:layout_marginTop="22dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_warning"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<Space
|
||||
android:id="@+id/space"
|
||||
android:layout_width="93dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_confirm"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_confirm"
|
||||
style="@style/TapBlackButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
tools:text="@string/wallet_button_send"
|
||||
tools:icon="@drawable/ic_send"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/space"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -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">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cl_wallet"
|
||||
|
|
@ -59,13 +59,13 @@
|
|||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_marginTop="12dp"
|
||||
android:id="@+id/rv_pending_transaction"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:overScrollMode="never"
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_card"/>
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_card" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_card_balance"
|
||||
|
|
@ -93,7 +93,7 @@
|
|||
android:text="@string/wallet_button_scan"
|
||||
app:icon="@drawable/ic_scan"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_main"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_confirm"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_main"
|
||||
android:id="@+id/btn_confirm"
|
||||
style="@style/TapButtonWithIcon"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="48dp"
|
||||
|
|
|
|||
8
app/src/main/res/menu/wallet.xml
Normal file
8
app/src/main/res/menu/wallet.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/details_menu"
|
||||
android:title="@string/details_toolbar_title"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
<string name="app_name" translatable="false">Tangem Tap</string>
|
||||
|
||||
<string name="generic_done">Done</string>
|
||||
<string name="generic_cancel">Cancel</string>
|
||||
<string name="generic_retry">Retry</string>
|
||||
<string name="generic_and">and</string>
|
||||
|
||||
|
|
@ -74,4 +75,18 @@
|
|||
<string name="send_set_maximum_amount">Maximum amount</string>
|
||||
<string name="send_transaction_complete">Transaction was signed and sent to the blockchain</string>
|
||||
|
||||
<string name="details_toolbar_title">Details</string>
|
||||
<string name="details_notification_erase_wallet_not_allowed">Card settings prohibits from erasing wallet</string>
|
||||
<string name="details_notification_erase_wallet_not_possible">You balance on this wallet is not zero, or you have unconfirmed transactions</string>
|
||||
<string name="details_card_id">Card ID</string>
|
||||
<string name="details_issuer">Issuer</string>
|
||||
<string name="details_signed_hashes">Signed</string>
|
||||
<string name="details_settings">Settings</string>
|
||||
<string name="details_currency">App currency</string>
|
||||
<string name="details_card">Card</string>
|
||||
<string name="details_validate_card">Validate card</string>
|
||||
<string name="details_manage_security">Manage security</string>
|
||||
<string name="details_erase_wallet">Erase wallet</string>
|
||||
|
||||
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue