Updated on 2026-08-14
This commit is contained in:
commit
e9b4e8b4f0
26 changed files with 291 additions and 77 deletions
|
|
@ -38,6 +38,7 @@ android {
|
|||
}
|
||||
debug_beta {
|
||||
initWith release
|
||||
debuggable true
|
||||
versionNameSuffix "-beta"
|
||||
applicationIdSuffix ".debug"
|
||||
buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"prod\"'
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ fun Picasso.loadCurrenciesIcon(
|
|||
imageView: ImageView,
|
||||
textView: TextView,
|
||||
token: Token? = null,
|
||||
blockchain: Blockchain?,
|
||||
blockchain: Blockchain,
|
||||
) {
|
||||
val blockchain = blockchain ?: Blockchain.Ethereum
|
||||
|
||||
val url = if (token != null) {
|
||||
IconsUtil.getTokenIconUri(blockchain, token)
|
||||
|
|
|
|||
|
|
@ -42,12 +42,12 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
|
||||
fun saveAddedTokens(cardId: String, tokens: Collection<Token>) {
|
||||
saveTokens(cardId, loadSavedTokens(cardId) + tokens)
|
||||
saveTokens(cardId, loadSavedTokens(cardId) + tokens.distinct())
|
||||
}
|
||||
|
||||
fun saveAddedBlockchain(cardId: String, blockchain: Blockchain) {
|
||||
val blockchains = loadSavedBlockchains(cardId) + blockchain
|
||||
saveBlockchains(cardId, blockchains)
|
||||
saveBlockchains(cardId, blockchains.distinct())
|
||||
}
|
||||
|
||||
fun removeToken(cardId: String, token: Token) {
|
||||
|
|
@ -79,7 +79,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
|
||||
private fun saveTokens(cardId: String, tokens: List<Token>) {
|
||||
val json = tokensAdapter.toJson(tokens.map { TokenDao.fromToken(it) }.distinct())
|
||||
val json = tokensAdapter.toJson(tokens.distinct().map { TokenDao.fromToken(it) })
|
||||
context.rewriteFile(json, getFileNameForTokens(cardId))
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
|
||||
private fun saveBlockchains(cardId: String, blockchains: List<Blockchain>) {
|
||||
val json = blockchainsAdapter.toJson(blockchains)
|
||||
val json = blockchainsAdapter.toJson(blockchains.distinct())
|
||||
context.rewriteFile(json, getFileNameForBlockchains(cardId))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ import com.tangem.common.KeyPair
|
|||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.operations.wallet.CreateWalletCommand
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.PurgeWalletCommand
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class CreateSecondTwinWalletTask(
|
||||
private val firstPublicKey: String,
|
||||
|
|
@ -25,7 +26,7 @@ class CreateSecondTwinWalletTask(
|
|||
val publicKey = card?.getSingleWallet()?.publicKey
|
||||
if (publicKey != null) {
|
||||
if (!card.cardId.startsWith(TwinsHelper.getPairCardSeries(firstCardId) ?: "")) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.WrongCardType()))
|
||||
callback(CompletionResult.Failure(WrongTwinCard()))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -65,4 +66,10 @@ class CreateSecondTwinWalletTask(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WrongTwinCard : TangemError {
|
||||
override val code: Int = 50005
|
||||
override var customMessage: String = code.toString()
|
||||
override val messageResId = R.string.twins_wrong_card_error
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ class AdditionalEmailInfo {
|
|||
|
||||
// wallets
|
||||
internal val walletsInfo = mutableListOf<EmailWalletInfo>()
|
||||
internal val tokens = mutableMapOf<Blockchain, Collection<Token>>()
|
||||
internal var onSendErrorWalletInfo: EmailWalletInfo? = null
|
||||
var signedHashesCount: String = ""
|
||||
|
||||
|
|
@ -146,6 +147,7 @@ class AdditionalEmailInfo {
|
|||
|
||||
fun setWalletsInfo(walletManagers: List<WalletManager>) {
|
||||
walletsInfo.clear()
|
||||
tokens.clear()
|
||||
walletManagers.forEach { manager ->
|
||||
walletsInfo.add(
|
||||
EmailWalletInfo(
|
||||
|
|
@ -155,6 +157,9 @@ class AdditionalEmailInfo {
|
|||
host = manager.currentHost
|
||||
)
|
||||
)
|
||||
if (manager.cardTokens.isNotEmpty()) {
|
||||
tokens[manager.wallet.blockchain] = manager.cardTokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -308,6 +313,14 @@ class FeedbackEmail : EmailData {
|
|||
}
|
||||
appendBlankLine(builder)
|
||||
|
||||
infoHolder.tokens.forEach { tokens ->
|
||||
appendDelimiter(builder)
|
||||
builder.appendKeyValue("Blockchain", tokens.key.fullName)
|
||||
builder.appendKeyValue("Tokens", tokens.value.map { "${it.name} - ${it.symbol}" }.toString())
|
||||
}
|
||||
appendDelimiter(builder)
|
||||
appendBlankLine(builder)
|
||||
|
||||
// appendKeyValue("Outputs count", infoHolder.outputsCount)
|
||||
builder.appendKeyValue("Phone model", infoHolder.phoneModel)
|
||||
builder.appendKeyValue("OS version", infoHolder.osVersion)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ class CreateWalletInterruptDialog {
|
|||
companion object {
|
||||
fun create(state: TwinCardsAction.Wallet.ShowInterruptDialog, context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.twins_recreate_alert)
|
||||
.setPositiveButton(R.string.common_ok) { _, _ -> state.onOk() }
|
||||
.setNegativeButton(R.string.common_cancel) { _, _ -> }
|
||||
.setMessage(R.string.onboarding_twin_exit_warning)
|
||||
.setPositiveButton(R.string.warning_button_ok) { _, _ -> }
|
||||
.setOnDismissListener { store.dispatchDialogHide() }
|
||||
.create()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ sealed class TransactionExtrasAction : SendScreenActionUi {
|
|||
data class HandleUserInput(val data: String) : XlmMemo()
|
||||
}
|
||||
|
||||
sealed class BinanceMemo : TransactionExtrasAction() {
|
||||
data class HandleUserInput(val data: String) : BinanceMemo()
|
||||
}
|
||||
|
||||
sealed class XrpDestinationTag : TransactionExtrasAction() {
|
||||
data class HandleUserInput(val data: String) : XrpDestinationTag()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ class TransactionExtrasReducer : SendInternalReducer {
|
|||
return when (action) {
|
||||
is Prepare -> handleInitialization(action, sendState)
|
||||
Release -> handleRelease(action, sendState)
|
||||
is XlmMemo -> handleMemo(action, sendState, sendState.transactionExtrasState)
|
||||
is XlmMemo -> handleXlmMemo(action, sendState, sendState.transactionExtrasState)
|
||||
is BinanceMemo -> handleBinanceMemo(action, sendState, sendState.transactionExtrasState)
|
||||
is XrpDestinationTag -> handleXrpTag(action, sendState, sendState.transactionExtrasState)
|
||||
else -> sendState
|
||||
}
|
||||
|
|
@ -40,6 +41,7 @@ class TransactionExtrasReducer : SendInternalReducer {
|
|||
}
|
||||
}
|
||||
Blockchain.Stellar -> TransactionExtrasState(xlmMemo = XlmMemoState())
|
||||
Blockchain.Binance -> TransactionExtrasState(binanceMemo = BinanceMemoState())
|
||||
else -> emptyResult
|
||||
}
|
||||
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||
|
|
@ -50,7 +52,7 @@ class TransactionExtrasReducer : SendInternalReducer {
|
|||
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||
}
|
||||
|
||||
private fun handleMemo(
|
||||
private fun handleXlmMemo(
|
||||
action: XlmMemo,
|
||||
sendState: SendState,
|
||||
infoState: TransactionExtrasState,
|
||||
|
|
@ -94,6 +96,26 @@ class TransactionExtrasReducer : SendInternalReducer {
|
|||
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||
}
|
||||
|
||||
private fun handleBinanceMemo(
|
||||
action: BinanceMemo,
|
||||
sendState: SendState,
|
||||
infoState: TransactionExtrasState,
|
||||
): SendState {
|
||||
val result = when (action) {
|
||||
is BinanceMemo.HandleUserInput -> {
|
||||
val tag = action.data.toBigIntegerOrNull()
|
||||
if (tag != null) {
|
||||
val input = InputViewValue(action.data, true)
|
||||
val tagState = BinanceMemoState(input, tag)
|
||||
infoState.copy(binanceMemo = tagState)
|
||||
} else {
|
||||
infoState
|
||||
}
|
||||
}
|
||||
}
|
||||
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||
}
|
||||
|
||||
private fun handleXrpTag(
|
||||
action: XrpDestinationTag,
|
||||
sendState: SendState,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ data class AddressPayIdState(
|
|||
|
||||
data class TransactionExtrasState(
|
||||
val xlmMemo: XlmMemoState? = null,
|
||||
val binanceMemo: BinanceMemoState? = null,
|
||||
val xrpDestinationTag: XrpDestinationTagState? = null
|
||||
) : IdStateHolder {
|
||||
override val stateId: StateId = StateId.TRANSACTION_EXTRAS
|
||||
|
|
@ -54,6 +55,16 @@ data class XlmMemoState(
|
|||
}
|
||||
}
|
||||
|
||||
data class BinanceMemoState(
|
||||
val viewFieldValue: InputViewValue = InputViewValue(""),
|
||||
val memo: BigInteger? = null,
|
||||
val error: TransactionExtraError? = null
|
||||
) {
|
||||
companion object {
|
||||
val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16)
|
||||
}
|
||||
}
|
||||
|
||||
// tag must contains only digits
|
||||
data class XrpDestinationTagState(
|
||||
val viewFieldValue: InputViewValue = InputViewValue(""),
|
||||
|
|
@ -67,5 +78,6 @@ data class XrpDestinationTagState(
|
|||
|
||||
enum class TransactionExtraError {
|
||||
INVALID_DESTINATION_TAG,
|
||||
INVALID_XLM_MEMO
|
||||
INVALID_XLM_MEMO,
|
||||
INVALID_BINANCE_MEMO
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
}
|
||||
|
||||
private fun setupTransactionExtrasLayout() {
|
||||
etMemo.inputtedTextAsFlow()
|
||||
etXlmMemo.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.filter {
|
||||
val info = store.state.sendState.transactionExtrasState
|
||||
|
|
@ -132,6 +132,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
}
|
||||
.onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) }
|
||||
.launchIn(mainScope)
|
||||
|
||||
etBinanceMemo.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.filter {
|
||||
val info = store.state.sendState.transactionExtrasState
|
||||
info.binanceMemo?.viewFieldValue?.value != it
|
||||
}
|
||||
.onEach { store.dispatch(TransactionExtrasAction.BinanceMemo.HandleUserInput(it)) }
|
||||
.launchIn(mainScope)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.text.SpannableStringBuilder
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.bold
|
||||
import com.tangem.common.extensions.remove
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.getMessageString
|
||||
import com.tangem.tap.common.text.DecimalDigitsInputFilter
|
||||
|
|
@ -64,19 +65,20 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
}
|
||||
showView(fg.xlmMemoContainer, infoState.xlmMemo)
|
||||
showView(fg.xrpDestinationTagContainer, infoState.xrpDestinationTag)
|
||||
showView(fg.binanceMemoContainer, infoState.binanceMemo)
|
||||
|
||||
infoState.xlmMemo?.let {
|
||||
fg.etMemo.inputType = when (it.selectedMemoType) {
|
||||
fg.etXlmMemo.inputType = when (it.selectedMemoType) {
|
||||
XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
|
||||
}
|
||||
if (!it.viewFieldValue.isFromUserInput) fg.etMemo.setText(it.viewFieldValue.value)
|
||||
if (!it.viewFieldValue.isFromUserInput) fg.etXlmMemo.setText(it.viewFieldValue.value)
|
||||
if (it.error != null) {
|
||||
if (it.error == TransactionExtraError.INVALID_XLM_MEMO) {
|
||||
fg.tilMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
|
||||
fg.tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
|
||||
}
|
||||
} else {
|
||||
fg.tilMemo.error = null
|
||||
fg.tilXlmMemo.error = null
|
||||
}
|
||||
}
|
||||
infoState.xrpDestinationTag?.let {
|
||||
|
|
@ -91,6 +93,18 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
fg.etDestinationTag.setText(it.viewFieldValue.value)
|
||||
}
|
||||
}
|
||||
infoState.binanceMemo?.let {
|
||||
if (infoState.binanceMemo.error != null) {
|
||||
if (infoState.binanceMemo.error == TransactionExtraError.INVALID_BINANCE_MEMO) {
|
||||
fg.tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
|
||||
}
|
||||
} else {
|
||||
fg.tilBinanceMemo.error = null
|
||||
}
|
||||
if (!it.viewFieldValue.isFromUserInput) {
|
||||
fg.etBinanceMemo.setText(it.viewFieldValue.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendScreen(fg: BaseStoreFragment, state: SendState) {
|
||||
|
|
@ -187,9 +201,13 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
fg.tvAmountCurrency.update(state.mainCurrency.currencySymbol)
|
||||
(fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type)
|
||||
|
||||
val balanceText = fg.getString(R.string.send_balance_subtitle_format,
|
||||
state.mainCurrency.currencySymbol,
|
||||
state.viewBalanceValue)
|
||||
val balanceText = when (state.mainCurrency.type) {
|
||||
MainCurrencyType.FIAT -> fg.getString(R.string.send_balance_subtitle_format,
|
||||
state.viewBalanceValue, state.mainCurrency.currencySymbol).remove(":")
|
||||
MainCurrencyType.CRYPTO -> fg.getString(R.string.send_balance_subtitle_format,
|
||||
state.mainCurrency.currencySymbol, state.viewBalanceValue)
|
||||
}
|
||||
|
||||
fg.tvBalance.update(balanceText)
|
||||
|
||||
fg.tilAmountToSend.isEnabled = state.inputIsEnabled
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.wallet.R
|
||||
|
||||
sealed class CurrencyListItem {
|
||||
data class TokenListItem(val token: Token) : CurrencyListItem()
|
||||
data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
|
||||
data class TitleListItem(
|
||||
@StringRes val titleResId: Int,
|
||||
var isContentShown: Boolean = true,
|
||||
val blockchain: Blockchain? = null,
|
||||
) : CurrencyListItem()
|
||||
|
||||
companion object {
|
||||
fun createListOfCurrencies(
|
||||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>,
|
||||
): List<CurrencyListItem> {
|
||||
val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
|
||||
val ethereumTokensTitle = R.string.add_tokens_subtitle_ethereum_tokens
|
||||
val bscTokensTitle = R.string.add_tokens_subtitle_bsc_tokens
|
||||
val binanceTokensTitle = R.string.add_tokens_subtitle_binance_tokens
|
||||
|
||||
val ethereumTokens = tokens.filter { it.blockchain == Blockchain.Ethereum }
|
||||
val bscTokens = tokens.filter { it.blockchain == Blockchain.BSC }
|
||||
val binanceTokens = tokens.filter { it.blockchain == Blockchain.Binance }
|
||||
return listOf(TitleListItem(blockchainsTitle)) +
|
||||
blockchains.map { BlockchainListItem(it) } +
|
||||
listOf(TitleListItem(ethereumTokensTitle, blockchain = Blockchain.Ethereum)) +
|
||||
ethereumTokens.map { TokenListItem(it) } +
|
||||
listOf(TitleListItem(bscTokensTitle, blockchain = Blockchain.BSC)) +
|
||||
bscTokens.map { TokenListItem(it) } +
|
||||
listOf(TitleListItem(binanceTokensTitle, blockchain = Blockchain.Binance)) +
|
||||
binanceTokens.map { TokenListItem(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.removeTokensForBlockchain(blockchain: Blockchain): List<CurrencyListItem> {
|
||||
return filterNot {
|
||||
it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain
|
||||
}
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.addTokensForBlockchain(
|
||||
blockchain: Blockchain, fullCurrenciesList: List<CurrencyListItem>,
|
||||
): List<CurrencyListItem> {
|
||||
val tokensToAdd = fullCurrenciesList.filter {
|
||||
it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain
|
||||
}
|
||||
val indexToInsert = indexOfFirst {
|
||||
it is CurrencyListItem.TitleListItem && it.blockchain == blockchain
|
||||
} + 1
|
||||
return this.toMutableList().apply { addAll(indexToInsert, tokensToAdd) }
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.toggleHeaderContentShownValue(blockchain: Blockchain) {
|
||||
map {
|
||||
if (it is CurrencyListItem.TitleListItem && it.blockchain == blockchain) {
|
||||
it.isContentShown = !it.isContentShown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -17,5 +17,6 @@ sealed class TokensAction : Action {
|
|||
|
||||
data class SetAddedCurrencies(val wallets: List<WalletData>) : TokensAction()
|
||||
|
||||
data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction()
|
||||
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.tokens.redux
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
|
|||
val tokensState = state.tokensState
|
||||
return when (action) {
|
||||
is TokensAction.LoadCurrencies.Success -> {
|
||||
tokensState.copy(currencies = action.currencies)
|
||||
tokensState.copy(currencies = action.currencies, shownCurrencies = action.currencies)
|
||||
}
|
||||
is TokensAction.SetAddedCurrencies -> {
|
||||
tokensState.copy(addedCurrencies = action.wallets.toCardCurrencies())
|
||||
|
|
@ -28,6 +28,20 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
|
|||
action.tokens.map { TokenWithAmount(it, null) }
|
||||
))
|
||||
}
|
||||
|
||||
is TokensAction.ToggleShowTokensForBlockchain -> {
|
||||
if (action.isShown) {
|
||||
val shownCurrencies = tokensState.shownCurrencies
|
||||
.removeTokensForBlockchain(action.blockchain)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
tokensState.copy(shownCurrencies = shownCurrencies)
|
||||
} else {
|
||||
val shownCurrencies = tokensState.shownCurrencies
|
||||
.addTokensForBlockchain(action.blockchain, tokensState.currencies)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
tokensState.copy(shownCurrencies = shownCurrencies)
|
||||
}
|
||||
}
|
||||
else -> tokensState
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@ package com.tangem.tap.features.tokens.redux
|
|||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class TokensState(
|
||||
val addedTokens: LinkedHashSet<TokenWithAmount> = LinkedHashSet(),
|
||||
val addedCurrencies: CardCurrencies? = null,
|
||||
val currencies: List<CurrencyListItem> = emptyList(),
|
||||
val shownCurrencies: List<CurrencyListItem> = emptyList(),
|
||||
) : StateType
|
||||
|
||||
data class TokenWithAmount(
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
override fun newState(state: TokensState) {
|
||||
if (activity == null) return
|
||||
viewAdapter.addedCurrencies = state.addedCurrencies
|
||||
viewAdapter.submitUnfilteredList(state.currencies)
|
||||
viewAdapter.submitUnfilteredList(state.shownCurrencies)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
|
|
|
|||
|
|
@ -3,18 +3,17 @@ package com.tangem.tap.features.tokens.ui.adapters
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.loadCurrenciesIcon
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.features.tokens.redux.CurrencyListItem
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -68,11 +67,11 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
|
|||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<CurrencyListItem>() {
|
||||
override fun areContentsTheSame(
|
||||
oldItem: CurrencyListItem, newItem: CurrencyListItem
|
||||
oldItem: CurrencyListItem, newItem: CurrencyListItem,
|
||||
) = oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: CurrencyListItem, newItem: CurrencyListItem
|
||||
oldItem: CurrencyListItem, newItem: CurrencyListItem,
|
||||
) = oldItem == newItem
|
||||
}
|
||||
|
||||
|
|
@ -161,38 +160,21 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
|
|||
class TitleViewHolder(val view: View) :
|
||||
RecyclerView.ViewHolder(view) {
|
||||
fun bind(title: CurrencyListItem.TitleListItem) {
|
||||
view.tv_subtitle.text = view.getString(title.titleResId).toUpperCase(Locale.US)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class CurrencyListItem {
|
||||
data class TokenListItem(val token: Token) : CurrencyListItem()
|
||||
data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
|
||||
data class TitleListItem(@StringRes val titleResId: Int) : CurrencyListItem()
|
||||
|
||||
companion object {
|
||||
fun createListOfCurrencies(
|
||||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>
|
||||
): List<CurrencyListItem> {
|
||||
val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
|
||||
val ethereumTokensTitle = R.string.add_tokens_subtitle_ethereum_tokens
|
||||
val bscTokensTitle = R.string.add_tokens_subtitle_bsc_tokens
|
||||
val binanceTokensTitle = R.string.add_tokens_subtitle_binance_tokens
|
||||
|
||||
val ethereumTokens = tokens.filter { it.blockchain == Blockchain.Ethereum }
|
||||
val bscTokens = tokens.filter { it.blockchain == Blockchain.BSC }
|
||||
val binanceTokens = tokens.filter { it.blockchain == Blockchain.Binance }
|
||||
return listOf(TitleListItem(blockchainsTitle)) +
|
||||
blockchains.map { BlockchainListItem(it) } +
|
||||
listOf(TitleListItem(ethereumTokensTitle)) +
|
||||
ethereumTokens.map { TokenListItem(it) } +
|
||||
listOf(TitleListItem(bscTokensTitle)) +
|
||||
bscTokens.map { TokenListItem(it) } +
|
||||
listOf(TitleListItem(binanceTokensTitle)) +
|
||||
binanceTokens.map { TokenListItem(it) }
|
||||
view.tv_subtitle.text = view.getString(title.titleResId).uppercase()
|
||||
|
||||
if (title.blockchain != null) {
|
||||
view.cl_subtitle_container.setOnClickListener {
|
||||
val rotation = if (title.isContentShown) -90f else 0f
|
||||
store.dispatch(TokensAction.ToggleShowTokensForBlockchain(
|
||||
title.isContentShown, title.blockchain
|
||||
))
|
||||
view.iv_toggle_sublist_visibility.animate().rotation(rotation)
|
||||
}
|
||||
view.iv_toggle_sublist_visibility.show()
|
||||
view.iv_toggle_sublist_visibility.setImageResource(R.drawable.ic_arrow_angle_down)
|
||||
} else {
|
||||
view.iv_toggle_sublist_visibility.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
|
|||
Picasso.get().loadCurrenciesIcon(
|
||||
imageView = iv_currency,
|
||||
textView = tv_token_letter,
|
||||
blockchain = wallet.currency?.blockchain,
|
||||
blockchain = wallet.currency.blockchain,
|
||||
token = (wallet.currency as? Currency.Token)?.token
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class WalletAdapter
|
|||
view.card_wallet.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
|
||||
}
|
||||
val blockchain = wallet.currency?.blockchain
|
||||
val blockchain = wallet.currency.blockchain
|
||||
val token = (wallet.currency as? Currency.Token)?.token
|
||||
|
||||
Picasso.get().loadCurrenciesIcon(
|
||||
|
|
|
|||
5
app/src/main/res/drawable/ic_arrow_angle_down.xml
Normal file
5
app/src/main/res/drawable/ic_arrow_angle_down.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#1C1C1E" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_arrow_angle_right.xml
Normal file
11
app/src/main/res/drawable/ic_arrow_angle_right.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="#1C1C1E"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6 -1.41,-1.41z" />
|
||||
</vector>
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout 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/cl_subtitle_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="10dp"
|
||||
|
|
@ -20,4 +21,19 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="BLOCKCHAINS" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_toggle_sublist_visibility"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/tv_subtitle"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tv_subtitle"
|
||||
tools:src="@drawable/ic_arrow_angle_right" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -4,9 +4,9 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="52dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:background="@android:color/white">
|
||||
android:background="@android:color/white"
|
||||
android:minHeight="60dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_currency"
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
android:layout_height="40dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:contentDescription="@string/token_icon"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
@ -37,7 +38,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.5"/>
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_currency_name"
|
||||
|
|
@ -50,27 +51,27 @@
|
|||
android:textColor="@color/darkGray6"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="@id/guideline"
|
||||
app:layout_constraintEnd_toStartOf="@id/barrier_chip_button"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintVertical_bias="1"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_currency"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/guideline"
|
||||
app:layout_constraintVertical_bias="1"
|
||||
tools:text="NODLE" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_currency_symbol"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/guideline"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_currency"
|
||||
app:layout_constraintTop_toTopOf="@id/guideline"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
tools:text="NODLE" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
|
|
@ -90,11 +91,11 @@
|
|||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/btn_token_added"
|
||||
android:enabled="false"
|
||||
style="@style/Widget.MaterialComponents.Chip.Action"
|
||||
android:layout_width="102dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:enabled="false"
|
||||
android:src="@drawable/ic_inactive"
|
||||
android:text="@string/add_token_added"
|
||||
android:textAlignment="center"
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@
|
|||
<!-- </com.google.android.material.chip.ChipGroup>-->
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilMemo"
|
||||
android:id="@+id/tilXlmMemo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/send_extras_hint_memo"
|
||||
|
|
@ -148,7 +148,7 @@
|
|||
app:errorIconDrawable="@null">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etMemo"
|
||||
android:id="@+id/etXlmMemo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
|
|
@ -163,6 +163,38 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/binanceMemoContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilBinanceMemo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/send_extras_hint_memo"
|
||||
app:boxBackgroundColor="@color/backgroundLightGray"
|
||||
app:errorIconDrawable="@null">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etBinanceMemo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:ellipsize="end"
|
||||
android:inputType="number"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:singleLine="true"
|
||||
android:textSize="16sp"
|
||||
tools:text="123" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/xrpDestinationTagContainer"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
<string name="twins_recreate_subtitle" translatable="false">The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning.</string>
|
||||
<string name="twins_recreate_button_format" translatable="false">Tap the card #%s</string>
|
||||
<string name="twins_recreate_alert" translatable="false">You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet.</string>
|
||||
<string name="twins_wrong_card_error" translatable="false">You\'ve tapped the same card. To create twin wallet you need to tap a second twin card.</string>
|
||||
|
||||
<string name="notification_twins_recreate_success" translatable="false">The twin address was successfully created</string>
|
||||
|
||||
|
|
@ -90,6 +91,7 @@
|
|||
<string name="custom_token_token_symbol_input_placeholder" translatable="false">ex. USDC</string>
|
||||
<string name="add_token_added" translatable="false">Added</string>
|
||||
<string name="remove_token" translatable="false">Remove token</string>
|
||||
<string name="token_icon" translatable="false">Token icon</string>
|
||||
|
||||
//Details
|
||||
<string name="details_section_title_blockchain" translatable="false">Blockchain</string>
|
||||
|
|
@ -198,10 +200,12 @@
|
|||
<string name="address_qr_code_message_token_format" translatable="false">Send only %s (%s) from %s network to this address. Sending any other currency will result in its irreversible loss.</string>
|
||||
|
||||
<string name="onboarding_twins_interrupt_warning" translatable="false">If the process of re-creating the wallet gets interrupted in any way, you’ll have to start over.</string>
|
||||
<string name="onboarding_twin_exit_warning" translatable="false">The twinning process is partly complete. You can’t exit it now.</string>
|
||||
|
||||
<string name="onboarding_error_create_primary_wallet" translatable="false">Internal error: can\'t create wallet manager</string>
|
||||
|
||||
<string name="wallet_balance_blockchain_unreachable_try_later" translatable="false">Blockchain is unreachable. Try later</string>
|
||||
|
||||
<string name="warning_button_ok" translatable="false">Ok, Got it!</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue