Updated on 2026-08-14
This commit is contained in:
commit
9abedff8b5
80 changed files with 1567 additions and 3658 deletions
|
|
@ -1,6 +1,4 @@
|
|||
{
|
||||
"isWalletPayIdEnabled": true,
|
||||
"isSendingToPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": true
|
||||
}
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
{
|
||||
"isWalletPayIdEnabled": false,
|
||||
"isSendingToPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": true
|
||||
}
|
||||
|
|
@ -31,12 +31,14 @@ sealed class Basic(
|
|||
currency: AnalyticsParam.CardCurrency,
|
||||
batch: String,
|
||||
signInType: SignInType,
|
||||
walletsCount: String,
|
||||
) : Basic(
|
||||
event = "Signed in",
|
||||
params = mapOf(
|
||||
AnalyticsParam.CURRENCY to currency.value,
|
||||
AnalyticsParam.BATCH to batch,
|
||||
"Sign in type" to signInType.name,
|
||||
"Wallets Count" to walletsCount,
|
||||
),
|
||||
) {
|
||||
enum class SignInType {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import com.tangem.wallet.R
|
|||
fun Blockchain.getGreyedOutIconRes(): Int {
|
||||
return when (this) {
|
||||
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_no_color
|
||||
// Blockchain.Ducatus -> R.drawable.ic_ducatus
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> R.drawable.ic_bitcoin_no_color
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_no_color
|
||||
Blockchain.Litecoin -> R.drawable.ic_litecoin_no_color
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.tap.common.analytics.topup.TopUpController
|
|||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
|
|
@ -20,7 +19,6 @@ data class GlobalState(
|
|||
val onboardingState: OnboardingState = OnboardingState(),
|
||||
val cardVerifiedOnline: Boolean = false,
|
||||
val tapWalletManager: TapWalletManager = TapWalletManager(),
|
||||
val payIdManager: PayIdManager = PayIdManager(),
|
||||
val configManager: ConfigManager? = null,
|
||||
val warningManager: WarningMessagesManager? = null,
|
||||
val feedbackManager: FeedbackManager? = null,
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.tap.network.payid.PayIdVerifyService
|
||||
import com.tangem.tap.network.payid.VerifyPayIdResponse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
class PayIdManager {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result<VerifyPayIdResponse> =
|
||||
withContext(Dispatchers.IO) {
|
||||
val splitPayId = payId.split("\$")
|
||||
val user = splitPayId[0]
|
||||
val baseUrl = "https://${splitPayId[1]}/"
|
||||
return@withContext PayIdVerifyService(baseUrl).verifyAddress(user, blockchain.getPayIdNetwork())
|
||||
}
|
||||
|
||||
private fun Blockchain.getPayIdNetwork(): String {
|
||||
return when (this) {
|
||||
Blockchain.XRP -> "XRPL"
|
||||
Blockchain.RSK -> "RSK"
|
||||
else -> this.currency
|
||||
}.lowercase(Locale.getDefault())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val payIdRegExp = (
|
||||
"^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9]" +
|
||||
"(?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$"
|
||||
).toRegex()
|
||||
|
||||
val payIdSupported: EnumSet<Blockchain> = EnumSet.of(
|
||||
Blockchain.XRP,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.Bitcoin,
|
||||
Blockchain.Litecoin,
|
||||
Blockchain.Stellar,
|
||||
Blockchain.Cardano,
|
||||
Blockchain.CardanoShelley,
|
||||
Blockchain.Ducatus,
|
||||
Blockchain.BitcoinCash,
|
||||
Blockchain.Binance,
|
||||
Blockchain.RSK,
|
||||
Blockchain.Tezos,
|
||||
)
|
||||
|
||||
fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.isPayIdSupported(): Boolean {
|
||||
return PayIdManager.payIdSupported.contains(this)
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.core.analytics.Analytics
|
||||
|
|
@ -134,22 +137,13 @@ class TapWalletManager(
|
|||
|
||||
fun updateConfigManager(data: ScanResponse) {
|
||||
val configManager = store.state.globalState.configManager
|
||||
val blockchain = data.cardTypesResolver.getBlockchain()
|
||||
|
||||
if (data.cardTypesResolver.isStart2Coin()) {
|
||||
configManager?.turnOff(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED)
|
||||
configManager?.turnOff(ConfigManager.IS_TOP_UP_ENABLED)
|
||||
} else if (blockchain == Blockchain.Bitcoin ||
|
||||
data.walletData?.blockchain == Blockchain.Bitcoin.id
|
||||
) {
|
||||
configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED)
|
||||
configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED)
|
||||
} else {
|
||||
configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED)
|
||||
configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Wallet.getFirstToken(): Token? {
|
||||
return getTokens().toList().getOrNull(0)
|
||||
}
|
||||
fun Wallet.getFirstToken(): Token? = getTokens().toList().getOrNull(index = 0)
|
||||
|
|
@ -26,6 +26,11 @@ interface UserWalletsListManager {
|
|||
* */
|
||||
val hasUserWallets: Boolean
|
||||
|
||||
/**
|
||||
* Count of saved user wallets
|
||||
*/
|
||||
val walletsCount: Int
|
||||
|
||||
/**
|
||||
* Set [UserWallet] with provided [UserWalletId] as selected
|
||||
*
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ internal class BiometricUserWalletsListManager(
|
|||
override val hasUserWallets: Boolean
|
||||
get() = keysRepository.hasSavedEncryptionKeys()
|
||||
|
||||
override val walletsCount: Int
|
||||
get() = state.value.userWallets.size
|
||||
|
||||
override suspend fun unlock(): CompletionResult<UserWallet> {
|
||||
return unlockWithBiometryInternal()
|
||||
.mapFailure { error ->
|
||||
|
|
|
|||
|
|
@ -7,13 +7,7 @@ import com.tangem.tap.domain.model.UserWallet
|
|||
import com.tangem.tap.domain.userWalletList.UserWalletsListError
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.flow.updateAndGet
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
||||
|
|
@ -36,6 +30,12 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
|||
override val hasUserWallets: Boolean
|
||||
get() = state.value.userWallet != null
|
||||
|
||||
/**
|
||||
* only 1 wallet stored in runtime implementation
|
||||
*/
|
||||
override val walletsCount: Int
|
||||
get() = 1
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
state.value.userWallet
|
||||
?.takeIf { it.walletId == userWalletId }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.ToastNotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
|
|
@ -34,15 +33,14 @@ data class PrepareSendScreen(
|
|||
val tokenRate: BigDecimal? = null,
|
||||
) : SendScreenAction
|
||||
|
||||
// Address or PayId
|
||||
sealed class AddressPayIdActionUi : SendScreenActionUi {
|
||||
data class HandleUserInput(val data: String) : AddressPayIdActionUi()
|
||||
data class PasteAddressPayId(val data: String, val sourceType: AddressEntered.SourceType) : AddressPayIdActionUi()
|
||||
data class CheckClipboard(val data: String?) : AddressPayIdActionUi()
|
||||
data class CheckAddressPayId(val sourceType: AddressEntered.SourceType?) : AddressPayIdActionUi()
|
||||
data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi()
|
||||
data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi()
|
||||
data class ChangePayIdState(val sendingToPayIdEnabled: Boolean) : AddressPayIdActionUi()
|
||||
// Address
|
||||
sealed class AddressActionUi : SendScreenActionUi {
|
||||
data class HandleUserInput(val data: String) : AddressActionUi()
|
||||
data class PasteAddress(val data: String, val sourceType: AddressEntered.SourceType) : AddressActionUi()
|
||||
data class CheckClipboard(val data: String?) : AddressActionUi()
|
||||
data class CheckAddress(val sourceType: AddressEntered.SourceType?) : AddressActionUi()
|
||||
data class SetTruncateHandler(val handler: (String) -> String) : AddressActionUi()
|
||||
data class TruncateOrRestore(val truncate: Boolean) : AddressActionUi()
|
||||
}
|
||||
|
||||
sealed class TransactionExtrasAction : SendScreenActionUi {
|
||||
|
|
@ -76,27 +74,15 @@ sealed class TransactionExtrasAction : SendScreenActionUi {
|
|||
}
|
||||
}
|
||||
|
||||
sealed class AddressPayIdVerifyAction : SendScreenAction {
|
||||
sealed class AddressVerifyAction : SendScreenAction {
|
||||
enum class Error {
|
||||
PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN,
|
||||
PAY_ID_NOT_REGISTERED,
|
||||
PAY_ID_REQUEST_FAILED,
|
||||
ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN,
|
||||
ADDRESS_SAME_AS_WALLET,
|
||||
}
|
||||
|
||||
data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressPayIdVerifyAction()
|
||||
data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressVerifyAction()
|
||||
|
||||
sealed class PayIdVerification : AddressPayIdVerifyAction() {
|
||||
data class SetPayIdError(val error: Error?) : PayIdVerification()
|
||||
data class SetPayIdWalletAddress(
|
||||
val payId: String,
|
||||
val payIdWalletAddress: String,
|
||||
val isUserInput: Boolean,
|
||||
) : PayIdVerification()
|
||||
}
|
||||
|
||||
sealed class AddressVerification : AddressPayIdVerifyAction() {
|
||||
sealed class AddressVerification : AddressVerifyAction() {
|
||||
data class SetAddressError(val error: Error?) : AddressVerification()
|
||||
data class SetWalletAddress(val address: String, val isUserInput: Boolean) : AddressVerification()
|
||||
}
|
||||
|
|
@ -155,8 +141,6 @@ sealed class SendAction : SendScreenAction {
|
|||
override val messageResource: Int = R.string.send_transaction_success
|
||||
}
|
||||
|
||||
data class SendError(override val error: TapError) : SendAction(), ErrorAction
|
||||
|
||||
sealed class Dialog : SendAction(), StateDialog {
|
||||
data class TezosWarningDialog(
|
||||
val reduceCallback: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -2,49 +2,39 @@ package com.tangem.tap.features.send.redux.middlewares
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.isPayIdSupported
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetAddressError
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetWalletAddress
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification.SetAddressError
|
||||
import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification.SetWalletAddress
|
||||
import com.tangem.tap.features.send.redux.AddressVerifyAction.Error
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AddressPayIdMiddleware {
|
||||
internal class AddressMiddleware {
|
||||
|
||||
fun handle(action: AddressPayIdActionUi, appState: AppState?, dispatch: (Action) -> Unit) {
|
||||
fun handle(action: AddressActionUi, appState: AppState?, dispatch: (Action) -> Unit) {
|
||||
when (action) {
|
||||
is AddressPayIdActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch)
|
||||
is AddressPayIdActionUi.PasteAddressPayId -> pasteAddressPayId(action.data, action.sourceType, dispatch)
|
||||
is AddressPayIdActionUi.CheckClipboard -> verifyClipboard(action.data, appState, dispatch)
|
||||
is AddressPayIdActionUi.CheckAddressPayId -> verifyAddressPayId(action.sourceType, appState, dispatch)
|
||||
is AddressActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch)
|
||||
is AddressActionUi.PasteAddress -> pasteAddress(action.data, action.sourceType, dispatch)
|
||||
is AddressActionUi.CheckClipboard -> verifyClipboard(action.data, appState, dispatch)
|
||||
is AddressActionUi.CheckAddress -> verifyAddress(action.sourceType, appState, dispatch)
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUserInput(input: String, appState: AppState?, dispatch: DispatchFunction) {
|
||||
val sendState = appState?.sendState ?: return
|
||||
if (input == sendState.addressPayIdState.viewFieldValue.value) return
|
||||
if (input == sendState.addressState.viewFieldValue.value) return
|
||||
|
||||
setAddressAndCheck(data = input, sourceType = null, isUserInput = true, dispatch = dispatch)
|
||||
}
|
||||
|
||||
private fun pasteAddressPayId(data: String, sourceType: AddressEntered.SourceType, dispatch: (Action) -> Unit) {
|
||||
private fun pasteAddress(data: String, sourceType: AddressEntered.SourceType, dispatch: (Action) -> Unit) {
|
||||
setAddressAndCheck(data = data, sourceType = sourceType, isUserInput = false, dispatch = dispatch)
|
||||
}
|
||||
|
||||
|
|
@ -54,74 +44,27 @@ internal class AddressPayIdMiddleware {
|
|||
isUserInput: Boolean,
|
||||
dispatch: (Action) -> Unit,
|
||||
) {
|
||||
val potentialPayId = data.lowercase()
|
||||
if (isPayIdEnabled() && PayIdManager.isPayId(potentialPayId)) {
|
||||
dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput))
|
||||
} else {
|
||||
dispatch(SetWalletAddress(data, isUserInput))
|
||||
}
|
||||
dispatch(AddressPayIdActionUi.CheckAddressPayId(sourceType))
|
||||
dispatch(SetWalletAddress(data, isUserInput))
|
||||
dispatch(AddressActionUi.CheckAddress(sourceType))
|
||||
}
|
||||
|
||||
private fun verifyAddressPayId(
|
||||
private fun verifyAddress(
|
||||
sourceType: AddressEntered.SourceType?,
|
||||
appState: AppState?,
|
||||
dispatch: (Action) -> Unit,
|
||||
) {
|
||||
val sendState = appState?.sendState ?: return
|
||||
val wallet = sendState.walletManager?.wallet ?: return
|
||||
val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return
|
||||
val isUserInput = sendState.addressPayIdState.viewFieldValue.isFromUserInput
|
||||
val address = sendState.addressState.normalFieldValue ?: return
|
||||
val isUserInput = sendState.addressState.viewFieldValue.isFromUserInput
|
||||
|
||||
if (isPayIdEnabled() && PayIdManager.isPayId(addressPayId)) {
|
||||
verifyPayId(addressPayId, wallet, isUserInput, dispatch)
|
||||
} else {
|
||||
verifyAddress(
|
||||
address = addressPayId,
|
||||
wallet = wallet,
|
||||
isUserInput = isUserInput,
|
||||
dispatch = dispatch,
|
||||
sourceType = sourceType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyPayId(payId: String, wallet: Wallet, isUserInput: Boolean, dispatch: DispatchFunction) {
|
||||
val blockchain = wallet.blockchain
|
||||
if (!blockchain.isPayIdSupported()) {
|
||||
dispatch(SetPayIdError(Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN))
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
val result = PayIdManager().verifyPayId(payId, blockchain)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val addressDetails = result.data.getAddressDetails()
|
||||
if (addressDetails == null) {
|
||||
dispatch(SetPayIdError(Error.PAY_ID_NOT_REGISTERED))
|
||||
return@withContext
|
||||
}
|
||||
|
||||
val address = addressDetails.address
|
||||
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, address)
|
||||
if (failReason == null) {
|
||||
dispatch(SetPayIdWalletAddress(payId, address, isUserInput))
|
||||
dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, addressDetails.tag))
|
||||
dispatch(FeeAction.RequestFee)
|
||||
} else {
|
||||
dispatch(SetAddressError(failReason))
|
||||
dispatch(TransactionExtrasAction.Release)
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
dispatch(SetPayIdError(Error.PAY_ID_REQUEST_FAILED))
|
||||
dispatch(TransactionExtrasAction.Release)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
verifyAddress(
|
||||
address = address,
|
||||
wallet = wallet,
|
||||
isUserInput = isUserInput,
|
||||
dispatch = dispatch,
|
||||
sourceType = sourceType,
|
||||
)
|
||||
}
|
||||
|
||||
private fun verifyAddress(
|
||||
|
|
@ -219,41 +162,33 @@ internal class AddressPayIdMiddleware {
|
|||
}
|
||||
|
||||
private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) {
|
||||
val addressPayId = input ?: return
|
||||
val address = input ?: return
|
||||
val wallet = appState?.sendState?.walletManager?.wallet ?: return
|
||||
|
||||
val internalDispatcher: (Action) -> Unit = {
|
||||
when (it) {
|
||||
is SetWalletAddress, is SetPayIdWalletAddress -> {
|
||||
dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(true))
|
||||
is SetWalletAddress -> {
|
||||
dispatch(AddressVerifyAction.ChangePasteBtnEnableState(true))
|
||||
}
|
||||
is SetAddressError, is SetPayIdError -> {
|
||||
dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(false))
|
||||
is SetAddressError -> {
|
||||
dispatch(AddressVerifyAction.ChangePasteBtnEnableState(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PayIdManager.isPayId(addressPayId) && isPayIdEnabled()) {
|
||||
verifyPayId(addressPayId, wallet, false, internalDispatcher)
|
||||
} else {
|
||||
verifyAddress(
|
||||
address = addressPayId,
|
||||
wallet = wallet,
|
||||
sourceType = null,
|
||||
isUserInput = false,
|
||||
dispatch = internalDispatcher,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isPayIdEnabled(): Boolean {
|
||||
return store.state.globalState.configManager?.config?.isSendingToPayIdEnabled ?: false
|
||||
verifyAddress(
|
||||
address = address,
|
||||
wallet = wallet,
|
||||
sourceType = null,
|
||||
isUserInput = false,
|
||||
dispatch = internalDispatcher,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun String.splitToMap(firstDelimiter: String, secondDelimiter: String): Map<String, String> {
|
||||
return this.split(firstDelimiter)
|
||||
return this
|
||||
.split(firstDelimiter)
|
||||
.map { it.split(secondDelimiter) }
|
||||
.map { it.first() to it.last().toString() }
|
||||
.toMap()
|
||||
.associate { it.first() to it.last() }
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ class RequestFeeMiddleware {
|
|||
}
|
||||
val typedAmount = sendState.amountState.amountToExtract ?: return
|
||||
|
||||
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress!!
|
||||
val destinationAddress = sendState.addressState.destinationWalletAddress!!
|
||||
val destinationAmount = Amount(typedAmount, sendState.amountState.amountToSendCrypto)
|
||||
val txSender = if (scanResponse.isDemoCard()) {
|
||||
DemoTransactionSender(walletManager)
|
||||
|
|
|
|||
|
|
@ -55,18 +55,17 @@ class SendMiddleware {
|
|||
{ nextDispatch ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is AddressPayIdActionUi -> AddressPayIdMiddleware().handle(action, appState(), dispatch)
|
||||
is AddressActionUi -> AddressMiddleware().handle(action, appState(), dispatch)
|
||||
is AmountActionUi -> AmountMiddleware().handle(action, appState(), dispatch)
|
||||
is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch)
|
||||
is SendActionUi.SendAmountToRecipient ->
|
||||
verifyAndSendTransaction(action, appState(), dispatch)
|
||||
is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch)
|
||||
is SendAction.Warnings.Update -> updateWarnings(dispatch)
|
||||
is SendActionUi.CheckIfTransactionDataWasProvided -> {
|
||||
val transactionData = appState()?.sendState?.externalTransactionData
|
||||
if (transactionData != null) {
|
||||
store.dispatchOnMain(
|
||||
AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
|
||||
AddressVerifyAction.AddressVerification.SetWalletAddress(
|
||||
address = transactionData.destinationAddress,
|
||||
isUserInput = false,
|
||||
),
|
||||
|
|
@ -96,7 +95,7 @@ private fun verifyAndSendTransaction(
|
|||
val sendState = appState?.sendState ?: return
|
||||
val walletManager = sendState.walletManager ?: return
|
||||
val card = appState.globalState.scanResponse?.card ?: return
|
||||
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return
|
||||
val destinationAddress = sendState.addressState.destinationWalletAddress ?: return
|
||||
val typedAmount = sendState.amountState.amountToExtract ?: return
|
||||
val feeAmount = sendState.feeState.currentFee ?: return
|
||||
|
||||
|
|
@ -391,12 +390,6 @@ fun createValidateTransactionError(
|
|||
return TapError.ValidateTransactionErrors(tapErrors) { it.joinToString("\r\n") }
|
||||
}
|
||||
|
||||
private fun setIfSendingToPayIdEnabled(appState: AppState?, dispatch: (Action) -> Unit) {
|
||||
val isSendingToPayIdEnabled =
|
||||
appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false
|
||||
dispatch(AddressPayIdActionUi.ChangePayIdState(isSendingToPayIdEnabled))
|
||||
}
|
||||
|
||||
private fun updateWarnings(dispatch: (Action) -> Unit) {
|
||||
val warningsManager = store.state.globalState.warningManager ?: return
|
||||
val blockchain = store.state.sendState.walletManager?.wallet?.blockchain ?: return
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
package com.tangem.tap.features.send.redux.reducers
|
||||
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.states.AddressPayIdState
|
||||
import com.tangem.tap.features.send.redux.states.InputViewValue
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddressPayIdReducer : SendInternalReducer {
|
||||
override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) {
|
||||
is AddressPayIdActionUi -> handleUiAction(action, sendState, sendState.addressPayIdState)
|
||||
is AddressPayIdVerifyAction -> handleAction(action, sendState, sendState.addressPayIdState)
|
||||
else -> sendState
|
||||
}
|
||||
|
||||
private fun handleUiAction(
|
||||
action: AddressPayIdActionUi,
|
||||
sendState: SendState,
|
||||
state: AddressPayIdState,
|
||||
): SendState {
|
||||
val result = when (action) {
|
||||
is AddressPayIdActionUi.HandleUserInput -> state
|
||||
is AddressPayIdActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler)
|
||||
is AddressPayIdActionUi.TruncateOrRestore -> {
|
||||
val value = if (action.truncate) state.truncatedFieldValue ?: "" else state.normalFieldValue ?: ""
|
||||
state.copy(viewFieldValue = state.viewFieldValue.copy(value = value))
|
||||
}
|
||||
is AddressPayIdActionUi.PasteAddressPayId -> return sendState
|
||||
is AddressPayIdActionUi.CheckClipboard -> return sendState
|
||||
is AddressPayIdActionUi.CheckAddressPayId -> return sendState
|
||||
is AddressPayIdActionUi.ChangePayIdState -> state.copy(sendingToPayIdEnabled = action.sendingToPayIdEnabled)
|
||||
}
|
||||
return updateLastState(sendState.copy(addressPayIdState = result), result)
|
||||
}
|
||||
|
||||
private fun handleAction(
|
||||
action: AddressPayIdVerifyAction,
|
||||
sendState: SendState,
|
||||
state: AddressPayIdState,
|
||||
): SendState {
|
||||
val result = when (action) {
|
||||
is PayIdVerification.SetPayIdWalletAddress -> {
|
||||
state.copy(
|
||||
viewFieldValue = InputViewValue(action.payId, action.isUserInput),
|
||||
normalFieldValue = action.payId,
|
||||
truncatedFieldValue = state.truncate(action.payId),
|
||||
destinationWalletAddress = action.payIdWalletAddress,
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
is AddressVerification.SetWalletAddress -> {
|
||||
state.copy(
|
||||
viewFieldValue = InputViewValue(action.address, action.isUserInput),
|
||||
normalFieldValue = action.address,
|
||||
truncatedFieldValue = state.truncate(action.address),
|
||||
destinationWalletAddress = action.address,
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled)
|
||||
is AddressVerification.SetAddressError -> state.copy(error = action.error, destinationWalletAddress = null)
|
||||
is PayIdVerification.SetPayIdError -> state.copy(error = action.error, destinationWalletAddress = null)
|
||||
}
|
||||
return updateLastState(sendState.copy(addressPayIdState = result), result)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.tap.features.send.redux.reducers
|
||||
|
||||
import com.tangem.tap.features.send.redux.AddressActionUi
|
||||
import com.tangem.tap.features.send.redux.AddressVerifyAction
|
||||
import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.states.AddressState
|
||||
import com.tangem.tap.features.send.redux.states.InputViewValue
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddressReducer : SendInternalReducer {
|
||||
override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) {
|
||||
is AddressActionUi -> handleUiAction(action, sendState, sendState.addressState)
|
||||
is AddressVerifyAction -> handleAction(action, sendState, sendState.addressState)
|
||||
else -> sendState
|
||||
}
|
||||
|
||||
private fun handleUiAction(action: AddressActionUi, sendState: SendState, state: AddressState): SendState {
|
||||
val result = when (action) {
|
||||
is AddressActionUi.HandleUserInput -> state
|
||||
is AddressActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler)
|
||||
is AddressActionUi.TruncateOrRestore -> {
|
||||
val value = if (action.truncate) state.truncatedFieldValue ?: "" else state.normalFieldValue ?: ""
|
||||
state.copy(viewFieldValue = state.viewFieldValue.copy(value = value))
|
||||
}
|
||||
is AddressActionUi.PasteAddress -> return sendState
|
||||
is AddressActionUi.CheckClipboard -> return sendState
|
||||
is AddressActionUi.CheckAddress -> return sendState
|
||||
}
|
||||
return updateLastState(sendState.copy(addressState = result), result)
|
||||
}
|
||||
|
||||
private fun handleAction(action: AddressVerifyAction, sendState: SendState, state: AddressState): SendState {
|
||||
val result = when (action) {
|
||||
is AddressVerification.SetWalletAddress -> {
|
||||
state.copy(
|
||||
viewFieldValue = InputViewValue(action.address, action.isUserInput),
|
||||
normalFieldValue = action.address,
|
||||
truncatedFieldValue = state.truncate(action.address),
|
||||
destinationWalletAddress = action.address,
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
is AddressVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled)
|
||||
is AddressVerification.SetAddressError -> state.copy(error = action.error, destinationWalletAddress = null)
|
||||
}
|
||||
return updateLastState(sendState.copy(addressState = result), result)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ object SendScreenReducer {
|
|||
|
||||
val reducer: SendInternalReducer = when (action) {
|
||||
is PrepareSendScreen -> PrepareSendScreenStatesReducer()
|
||||
is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer()
|
||||
is AddressActionUi, is AddressVerifyAction -> AddressReducer()
|
||||
is TransactionExtrasAction -> TransactionExtrasReducer()
|
||||
is AmountActionUi, is AmountAction -> AmountReducer()
|
||||
is FeeActionUi, is FeeAction -> FeeReducer()
|
||||
|
|
@ -71,7 +71,7 @@ private class SendReducer : SendInternalReducer {
|
|||
amountState = state.amountState.copy(
|
||||
inputIsEnabled = false,
|
||||
),
|
||||
addressPayIdState = state.addressPayIdState.copy(
|
||||
addressState = state.addressState.copy(
|
||||
inputIsEnabled = false,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,17 +2,16 @@ package com.tangem.tap.features.send.redux.states
|
|||
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarMemo
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
|
||||
import com.tangem.tap.features.send.redux.AddressVerifyAction
|
||||
import java.math.BigInteger
|
||||
|
||||
data class AddressPayIdState(
|
||||
data class AddressState(
|
||||
val viewFieldValue: InputViewValue = InputViewValue(""),
|
||||
val normalFieldValue: String? = null,
|
||||
val truncatedFieldValue: String? = null,
|
||||
val destinationWalletAddress: String? = null,
|
||||
val error: AddressPayIdVerifyAction.Error? = null,
|
||||
val error: AddressVerifyAction.Error? = null,
|
||||
val truncateHandler: ((String) -> String)? = null,
|
||||
val sendingToPayIdEnabled: Boolean = false,
|
||||
val pasteIsEnabled: Boolean = false,
|
||||
val inputIsEnabled: Boolean = true,
|
||||
) : SendScreenState {
|
||||
|
|
@ -22,8 +21,6 @@ data class AddressPayIdState(
|
|||
fun truncate(value: String): String = truncateHandler?.invoke(value) ?: value
|
||||
|
||||
fun isReady(): Boolean = error == null && destinationWalletAddress?.isNotEmpty() ?: false
|
||||
|
||||
fun isPayIdState(): Boolean = destinationWalletAddress != null && destinationWalletAddress != normalFieldValue
|
||||
}
|
||||
|
||||
data class TransactionExtrasState(
|
||||
|
|
@ -98,11 +95,7 @@ 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(
|
||||
|
|
@ -34,7 +34,7 @@ data class SendState(
|
|||
val coinConverter: CurrencyConverter? = null,
|
||||
val tokenConverter: CurrencyConverter? = null,
|
||||
val lastChangedStates: LinkedHashSet<StateId> = linkedSetOf(),
|
||||
val addressPayIdState: AddressPayIdState = AddressPayIdState(),
|
||||
val addressState: AddressState = AddressState(),
|
||||
val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(),
|
||||
val amountState: AmountState = AmountState(),
|
||||
val feeState: FeeState = FeeState(),
|
||||
|
|
@ -52,7 +52,7 @@ data class SendState(
|
|||
MainCurrencyType.CRYPTO -> amountState.amountToExtract?.decimals ?: 0
|
||||
}
|
||||
|
||||
fun convertFiatToCoin(value: BigDecimal): BigDecimal {
|
||||
private fun convertFiatToCoin(value: BigDecimal): BigDecimal {
|
||||
return if (!this.coinIsConvertible()) value else coinConverter!!.toCrypto(value)
|
||||
}
|
||||
|
||||
|
|
@ -60,14 +60,14 @@ data class SendState(
|
|||
return if (!this.tokenIsConvertible()) value else tokenConverter!!.toCrypto(value)
|
||||
}
|
||||
|
||||
fun convertCoinToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
|
||||
private fun convertCoinToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
|
||||
if (!this.coinIsConvertible()) return value
|
||||
|
||||
val converter = coinConverter!!
|
||||
return if (!scaleWithPrecision) converter.toFiat(value) else converter.toFiatWithPrecision(value)
|
||||
}
|
||||
|
||||
fun convertTokenToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
|
||||
private fun convertTokenToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
|
||||
if (!this.tokenIsConvertible()) return value
|
||||
|
||||
val converter = tokenConverter!!
|
||||
|
|
@ -107,13 +107,13 @@ data class SendState(
|
|||
}
|
||||
|
||||
companion object {
|
||||
fun addressPayIdIsReady(): Boolean = store.state.sendState.addressPayIdState.isReady()
|
||||
private fun addressIsReady(): Boolean = store.state.sendState.addressState.isReady()
|
||||
|
||||
fun amountIsReady(): Boolean = store.state.sendState.amountState.isReady()
|
||||
private fun amountIsReady(): Boolean = store.state.sendState.amountState.isReady()
|
||||
|
||||
fun isReadyToRequestFee(): Boolean = addressPayIdIsReady() && amountIsReady()
|
||||
fun isReadyToRequestFee(): Boolean = addressIsReady() && amountIsReady()
|
||||
|
||||
fun isReadyToSend(): Boolean = addressPayIdIsReady() && amountIsReady() &&
|
||||
fun isReadyToSend(): Boolean = addressIsReady() && amountIsReady() &&
|
||||
store.state.sendState.feeState.isReady()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import com.tangem.tap.common.toggleWidget.ViewStateWidget
|
|||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.*
|
||||
import com.tangem.tap.features.send.redux.AddressActionUi.*
|
||||
import com.tangem.tap.features.send.redux.AmountActionUi.*
|
||||
import com.tangem.tap.features.send.redux.FeeActionUi.*
|
||||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
|
|
@ -80,7 +80,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
etAmountToSend = view.findViewById(R.id.etAmountToSend)
|
||||
|
||||
initSendButtonStates()
|
||||
setupAddressOrPayIdLayout()
|
||||
setupAddressLayout()
|
||||
setupTransactionExtrasLayout()
|
||||
setupAmountLayout()
|
||||
setupFeeLayout()
|
||||
|
|
@ -99,14 +99,14 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
sendBtn = IndeterminateProgressButtonWidget(btnSend, progress)
|
||||
}
|
||||
|
||||
private fun setupAddressOrPayIdLayout() = with(binding.lSendAddressPayid) {
|
||||
store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") })
|
||||
private fun setupAddressLayout() = with(binding.lSendAddress) {
|
||||
store.dispatch(SetTruncateHandler { etAddress.truncateMiddleWith(it, "...") })
|
||||
store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString()))
|
||||
|
||||
etAddressOrPayId.apply {
|
||||
etAddress.apply {
|
||||
setOnSystemPasteButtonClickListener {
|
||||
store.dispatch(
|
||||
PasteAddressPayId(
|
||||
PasteAddress(
|
||||
data = requireContext().getFromClipboard()?.toString() ?: "",
|
||||
sourceType = Token.Send.AddressEntered.SourceType.PastePopup,
|
||||
),
|
||||
|
|
@ -119,9 +119,9 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
|
||||
inputtedTextAsFlow()
|
||||
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
|
||||
.filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it }
|
||||
.filter { store.state.sendState.addressState.viewFieldValue.value != it }
|
||||
.onEach {
|
||||
store.dispatch(AddressPayIdActionUi.HandleUserInput(it))
|
||||
store.dispatch(AddressActionUi.HandleUserInput(it))
|
||||
}
|
||||
.launchIn(mainScope)
|
||||
}
|
||||
|
|
@ -129,12 +129,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
imvPaste.setOnClickListener {
|
||||
Analytics.send(Token.Send.ButtonPaste())
|
||||
store.dispatch(
|
||||
PasteAddressPayId(
|
||||
PasteAddress(
|
||||
data = requireContext().getFromClipboard()?.toString() ?: "",
|
||||
sourceType = Token.Send.AddressEntered.SourceType.PasteButton,
|
||||
),
|
||||
)
|
||||
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
|
||||
store.dispatch(TruncateOrRestore(!etAddress.isFocused))
|
||||
}
|
||||
imvQrCode.setOnClickListener {
|
||||
Analytics.send(Token.Send.ButtonQRCode())
|
||||
|
|
@ -145,7 +145,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupTransactionExtrasLayout() = with(binding.lSendAddressPayid) {
|
||||
private fun setupTransactionExtrasLayout() = with(binding.lSendAddress) {
|
||||
// TODO: [REDACTED_TASK_KEY]
|
||||
etXlmMemo.inputtedTextAsFlow()
|
||||
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
|
||||
|
|
@ -202,15 +202,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
|
||||
// If do not use the delay, then etAmount error field is not displayed when
|
||||
// inserting an incorrect amount by shareUri
|
||||
binding.lSendAddressPayid.imvQrCode.postDelayed(
|
||||
binding.lSendAddress.imvQrCode.postDelayed(
|
||||
{
|
||||
store.dispatch(
|
||||
PasteAddressPayId(
|
||||
PasteAddress(
|
||||
data = scannedCode,
|
||||
sourceType = Token.Send.AddressEntered.SourceType.QRCode,
|
||||
),
|
||||
)
|
||||
store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused))
|
||||
store.dispatch(TruncateOrRestore(!binding.lSendAddress.etAddress.isFocused))
|
||||
},
|
||||
200,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,30 +7,15 @@ 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.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.enableError
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.extensions.update
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.getMessageString
|
||||
import com.tangem.tap.common.text.DecimalDigitsInputFilter
|
||||
import com.tangem.tap.domain.MultiMessageError
|
||||
import com.tangem.tap.domain.assembleErrors
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
|
||||
import com.tangem.tap.features.send.redux.AddressVerifyAction.Error
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.send.redux.states.AddressPayIdState
|
||||
import com.tangem.tap.features.send.redux.states.AmountState
|
||||
import com.tangem.tap.features.send.redux.states.FeeState
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptLayoutType
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptState
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import com.tangem.tap.features.send.redux.states.StateId
|
||||
import com.tangem.tap.features.send.redux.states.TransactionExtraError
|
||||
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
|
||||
import com.tangem.tap.features.send.redux.states.*
|
||||
import com.tangem.tap.features.send.ui.FeeUiHelper
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.send.ui.dialogs.KaspaWarningDialog
|
||||
|
|
@ -61,7 +46,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
lastChangedStates.forEach {
|
||||
when (it) {
|
||||
StateId.SEND_SCREEN -> handleSendScreen(fg, state)
|
||||
StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState)
|
||||
StateId.ADDRESS_PAY_ID -> handleAddressState(fg, state.addressState)
|
||||
StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(fg, state.transactionExtrasState)
|
||||
StateId.AMOUNT -> handleAmountState(fg, state.amountState)
|
||||
StateId.FEE -> handleFeeState(fg, state.feeState)
|
||||
|
|
@ -72,7 +57,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun handleTransactionExtrasState(fg: SendFragment, infoState: TransactionExtrasState) =
|
||||
with(fg.binding.lSendAddressPayid) {
|
||||
with(fg.binding.lSendAddress) {
|
||||
fun showView(view: View, info: Any?) {
|
||||
view.show(info != null)
|
||||
}
|
||||
|
|
@ -192,13 +177,10 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleAddressPayIdState(fg: SendFragment, state: AddressPayIdState) =
|
||||
with(fg.binding.lSendAddressPayid) {
|
||||
private fun handleAddressState(fg: SendFragment, state: AddressState) {
|
||||
with(fg.binding.lSendAddress) {
|
||||
fun parseError(context: Context, error: Error?): String? {
|
||||
val resId = when (error) {
|
||||
Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_error_payid_unsupported_by_blockchain
|
||||
Error.PAY_ID_NOT_REGISTERED -> R.string.send_error_payid_not_registered
|
||||
Error.PAY_ID_REQUEST_FAILED -> R.string.send_error_payid_request_failed
|
||||
Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_validation_invalid_address
|
||||
Error.ADDRESS_SAME_AS_WALLET -> R.string.send_error_address_same_as_wallet
|
||||
else -> null
|
||||
|
|
@ -208,8 +190,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
|
||||
imvPaste.isEnabled = state.pasteIsEnabled
|
||||
|
||||
val et = etAddressOrPayId
|
||||
val til = tilAddressOrPayId
|
||||
val et = etAddress
|
||||
val til = tilAddress
|
||||
val parsedError = parseError(til.context, state.error)
|
||||
|
||||
til.isEnabled = state.inputIsEnabled
|
||||
|
|
@ -218,19 +200,15 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
flPaste.show(state.inputIsEnabled)
|
||||
flQrCode.show(state.inputIsEnabled)
|
||||
|
||||
val hintResId = if (state.sendingToPayIdEnabled) {
|
||||
R.string.send_destination_hint_address_payid
|
||||
} else {
|
||||
R.string.send_destination_hint_address
|
||||
}
|
||||
til.hint = til.getString(hintResId)
|
||||
til.hint = til.getString(R.string.send_destination_hint_address)
|
||||
til.error = parsedError
|
||||
til.isErrorEnabled = parsedError != null
|
||||
til.helperText = state.destinationWalletAddress
|
||||
til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
|
||||
til.isHelperTextEnabled = parsedError == null
|
||||
|
||||
if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAmountState(fg: SendFragment, state: AmountState) = with(fg.binding.lSendAmount) {
|
||||
if (state.error != null) {
|
||||
|
|
|
|||
|
|
@ -17,16 +17,8 @@ import com.tangem.tap.features.wallet.ui.analytics.WalletAnalyticsEventsMapper
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -71,6 +63,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = signInType,
|
||||
walletsCount = store.state.globalState.userWalletsListManager?.walletsCount.toString(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ internal fun WalletDataModel.getAvailableActions(
|
|||
|
||||
internal fun WalletDataModel.shouldShowMultipleAddress(): Boolean {
|
||||
val listOfAddresses = walletAddresses?.list.orEmpty()
|
||||
return listOfAddresses.size > 1 && currency.blockchain != Blockchain.BitcoinCash
|
||||
return listOfAddresses.size > 1
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.assembleWarnings(
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
package com.tangem.tap.network.payid
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface PayIdVerifyApi {
|
||||
@GET("{user}")
|
||||
suspend fun verifyAddress(
|
||||
@Path("user") user: String,
|
||||
@Header("Accept") acceptNetworkHeader: String,
|
||||
@Header("PayID-Version") payIdVersion: String = "1.0",
|
||||
): VerifyPayIdResponse
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class VerifyPayIdResponse(
|
||||
val addresses: List<PayIdAddress> = mutableListOf(),
|
||||
val payId: String? = null,
|
||||
) {
|
||||
fun getAddressDetails(): PayIdAddressDetails? = if (addresses.isNotEmpty()) addresses[0].addressDetails else null
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PayIdAddress(
|
||||
var paymentNetwork: String,
|
||||
var environment: String,
|
||||
var addressDetailsType: String,
|
||||
var addressDetails: PayIdAddressDetails,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PayIdAddressDetails(
|
||||
var address: String,
|
||||
var tag: String? = null,
|
||||
)
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.network.payid
|
||||
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PayIdVerifyService(
|
||||
private val baseUrl: String,
|
||||
) {
|
||||
|
||||
private val api = createRetrofitInstance(
|
||||
baseUrl = baseUrl,
|
||||
logEnabled = false,
|
||||
).create(PayIdVerifyApi::class.java)
|
||||
|
||||
suspend fun verifyAddress(user: String, network: String): Result<VerifyPayIdResponse> {
|
||||
return performRequest { api.verifyAddress(user, createNetworkHeader(network)) }
|
||||
}
|
||||
|
||||
private fun createNetworkHeader(network: String): String = "application/$network-mainnet+json"
|
||||
}
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/l_send_address_payid"
|
||||
layout="@layout/layout_send_address_payid"
|
||||
android:id="@+id/l_send_address"
|
||||
layout="@layout/layout_send_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
|
|
|||
|
|
@ -150,63 +150,6 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/tv_explore"
|
||||
tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." />
|
||||
|
||||
<View
|
||||
android:id="@+id/v_payid_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0.5dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@color/lightGray5"
|
||||
android:layout_marginTop="25dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/btn_copy" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_payid_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:src="@drawable/ic_payid"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/v_payid_divider"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_create_payid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="9dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/wallet_address_button_create_payid"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/v_payid_divider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_payid_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textAlignment="textEnd"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="13sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_payid_icon"
|
||||
app:layout_constraintTop_toBottomOf="@id/v_payid_divider"
|
||||
tools:text="romafdffdfdfn$payid.tangem.com" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_payid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="v_payid_divider, iv_payid_icon, tv_payid_address, tv_create_payid" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
|
|||
|
|
@ -18,10 +18,9 @@
|
|||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilAddressOrPayId"
|
||||
android:id="@+id/tilAddress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/send_destination_hint_address_payid"
|
||||
app:boxBackgroundColor="@color/backgroundLightGray"
|
||||
app:errorIconDrawable="@null"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
@ -29,7 +28,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.tangem.tap.features.send.ui.EditTextCustomPaste
|
||||
android:id="@+id/etAddressOrPayId"
|
||||
android:id="@+id/etAddress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
|
|
@ -71,7 +70,7 @@
|
|||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/shape_ellipse"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/tilAddressOrPayId">
|
||||
app:layout_constraintTop_toTopOf="@+id/tilAddress">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imvQrCode"
|
||||
|
|
@ -104,41 +103,6 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<!-- <com.google.android.material.chip.ChipGroup-->
|
||||
<!-- android:id="@+id/groupMemo"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:layout_marginTop="8dp"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:selectionRequired="true"-->
|
||||
<!-- app:singleLine="true"-->
|
||||
<!-- app:singleSelection="true">-->
|
||||
|
||||
<!-- <com.google.android.material.chip.Chip-->
|
||||
<!-- android:id="@+id/chipMemoText"-->
|
||||
<!-- style="@style/TapChip"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="Text" />-->
|
||||
|
||||
<!-- <com.google.android.material.chip.Chip-->
|
||||
<!-- android:id="@+id/chipMemoId"-->
|
||||
<!-- style="@style/TapChip"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="ID" />-->
|
||||
|
||||
<!-- <com.google.android.material.chip.Chip-->
|
||||
<!-- android:id="@+id/chipMemoHash"-->
|
||||
<!-- style="@style/TapChip"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="Hash" />-->
|
||||
|
||||
<!-- </com.google.android.material.chip.ChipGroup>-->
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilXlmMemo"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
android:id="@+id/amountContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tilAddressOrPayId">
|
||||
app:layout_constraintTop_toBottomOf="@+id/tilAddress">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flAmountToSend"
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
package com.tangem;
|
||||
|
||||
public class Test2 {
|
||||
}
|
||||
|
|
@ -1,305 +0,0 @@
|
|||
package com.tangem.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.text.Editable
|
||||
import android.text.Html
|
||||
import android.text.TextWatcher
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.os.bundleOf
|
||||
import com.tangem.Constant
|
||||
import com.tangem.data.Blockchain
|
||||
import com.tangem.tangem_card.data.TangemCard
|
||||
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
|
||||
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID
|
||||
import com.tangem.tangem_sdk.data.loadFromBundle
|
||||
import com.tangem.ui.activity.MainActivity
|
||||
import com.tangem.ui.fragment.BaseFragment
|
||||
import com.tangem.ui.fragment.pin.PinRequestFragment
|
||||
import com.tangem.ui.navigation.NavigationResultListener
|
||||
import com.tangem.util.UtilHelper
|
||||
import com.tangem.wallet.CoinEngine
|
||||
import com.tangem.wallet.CoinEngineFactory
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.TangemContext
|
||||
import kotlinx.android.synthetic.tangemAccess.fragment_confirm_transaction.*
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class ConfirmTransactionFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback {
|
||||
|
||||
override val layoutId = R.layout.fragment_confirm_transaction
|
||||
|
||||
private lateinit var sp: SharedPreferences
|
||||
private lateinit var ctx: TangemContext
|
||||
private lateinit var amount: CoinEngine.Amount
|
||||
|
||||
private var isIncludeFee: Boolean = true
|
||||
private var requestPIN2Count = 0
|
||||
private var nodeCheck = true
|
||||
private var dtVerified: Date? = null
|
||||
|
||||
private var blockchainCallbacks: CoinEngine.BlockchainRequestsCallbacks? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
sp = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
ctx = TangemContext.loadFromBundle(requireContext(), arguments)
|
||||
|
||||
val callback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
navigateUp()
|
||||
}
|
||||
}
|
||||
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val engine = CoinEngineFactory.create(ctx)
|
||||
|
||||
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(engine!!.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(engine!!.balanceHTML)
|
||||
tvBalance.text = html
|
||||
|
||||
isIncludeFee = arguments?.getBoolean(Constant.EXTRA_FEE_INCLUDED, true) ?: true
|
||||
|
||||
if (isIncludeFee)
|
||||
tvIncFee.setText(R.string.confirm_transaction_including_fee)
|
||||
else
|
||||
tvIncFee.setText(R.string.confirm_transaction_not_including_fee)
|
||||
|
||||
amount = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_AMOUNT) ?: "0",
|
||||
arguments?.getString(Constant.EXTRA_AMOUNT_CURRENCY) ?: "")
|
||||
|
||||
if (engine.allowSelectFeeInclusion())
|
||||
tvIncFee.visibility = View.VISIBLE
|
||||
else
|
||||
tvIncFee.visibility = View.INVISIBLE
|
||||
|
||||
if (ctx.card.blockchainID == Blockchain.Token.id) {
|
||||
// for Blockchain.Token limit decimals
|
||||
etAmount.setText(amount.toValueString(ctx.card.tokensDecimal))
|
||||
} else {
|
||||
// for others
|
||||
etAmount.setText(amount.toValueString())
|
||||
}
|
||||
|
||||
tvCurrency.text = engine.balanceCurrency
|
||||
tvCurrency2.text = engine.feeCurrency
|
||||
tvCardID.text = ctx.card.cidDescription
|
||||
etWallet.setText(arguments?.getString(Constant.EXTRA_TARGET_ADDRESS))
|
||||
|
||||
btnSend.visibility = View.INVISIBLE
|
||||
|
||||
if (!engine.allowSelectFeeLevel()) {
|
||||
rgFee.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
etFee.isEnabled = sp.getBoolean(getString(R.string.pref_manual_editing_fee), false)
|
||||
|
||||
// set listeners
|
||||
rgFee.setOnCheckedChangeListener { _, checkedId -> doSetFee(checkedId) }
|
||||
etFee.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
try {
|
||||
val eqFee = engine.evaluateFeeEquivalent(etFee!!.text.toString())
|
||||
tvFeeEquivalent.text = eqFee
|
||||
|
||||
if (!ctx.coinData!!.amountEquivalentDescriptionAvailable) {
|
||||
tvFeeEquivalent.error = getString(R.string.confirm_transaction_error_service_unavailable)
|
||||
tvCurrency2.visibility = View.GONE
|
||||
tvFeeEquivalent.visibility = View.GONE
|
||||
} else
|
||||
tvFeeEquivalent.error = null
|
||||
|
||||
if (sp.getBoolean(getString(R.string.pref_manual_editing_fee), false))
|
||||
(activity as MainActivity).toastHelper
|
||||
.showSingleToast(context, getString(R.string.confirm_transaction_warning_risk_delaying))
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
tvFeeEquivalent.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
|
||||
}
|
||||
})
|
||||
btnSend.setOnClickListener {
|
||||
if (UtilHelper.isOnline(requireContext())) {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.add(Calendar.MINUTE, -1)
|
||||
|
||||
if (dtVerified == null || dtVerified!!.before(calendar.time)) {
|
||||
finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_data_is_outdated))
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val engineCoin = CoinEngineFactory.create(ctx)
|
||||
|
||||
if (engineCoin!!.isNeedCheckNode && !nodeCheck) {
|
||||
Toast.makeText(context, getString(R.string.confirm_transaction_error_cannot_reach_node), Toast.LENGTH_LONG).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val txFee = engineCoin.convertToAmount(etFee.text.toString(), tvCurrency2.text.toString())
|
||||
val txAmount = engineCoin.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString())
|
||||
|
||||
if (!engineCoin.hasBalanceInfo()) {
|
||||
finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_cannot_check_balance))
|
||||
return@setOnClickListener
|
||||
|
||||
} else if (!engineCoin.isBalanceNotZero) {
|
||||
finishWithError(Activity.RESULT_CANCELED, getString(R.string.general_wallet_empty))
|
||||
return@setOnClickListener
|
||||
|
||||
} else if (!engineCoin.isExtractPossible) {
|
||||
finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_incoming_transaction_unconfirmed))
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (!engineCoin.checkNewTransactionAmountAndFee(txAmount, txFee, isIncludeFee)) {
|
||||
finishWithError(Activity.RESULT_CANCELED, getString(R.string.prepare_transaction_error_not_enough_funds))
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
requestPIN2Count = 0
|
||||
val data = Bundle()
|
||||
data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString())
|
||||
ctx.saveToBundle(data)
|
||||
data.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
|
||||
navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_, R.id.action_confirmTransactionFragment_to_pinRequestFragment, data)
|
||||
} else
|
||||
Toast.makeText(context, getString(R.string.general_error_no_connection), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
progressBar.visibility = View.VISIBLE
|
||||
|
||||
if (!navigatedBack) requestFee()
|
||||
}
|
||||
|
||||
private fun requestFee() {
|
||||
val coinEngine = CoinEngineFactory.create(ctx)
|
||||
coinEngine!!.requestFee(
|
||||
object : CoinEngine.BlockchainRequestsCallbacks {
|
||||
override fun onComplete(success: Boolean) {
|
||||
if (success) {
|
||||
progressBar?.visibility = View.INVISIBLE
|
||||
dtVerified = Date()
|
||||
doSetFee(rgFee?.checkedRadioButtonId ?: R.id.rbNormalFee)
|
||||
} else {
|
||||
finishWithError(Activity.RESULT_CANCELED, ctx.error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProgress() {
|
||||
}
|
||||
|
||||
override fun allowAdvance(): Boolean {
|
||||
return UtilHelper.isOnline(requireContext())
|
||||
}
|
||||
},
|
||||
etWallet.text.toString(),
|
||||
amount)
|
||||
}
|
||||
|
||||
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
|
||||
Log.d("LIFECYCLE", "NavigationResult assessed ${this::class.java.simpleName}")
|
||||
if (requestCode == Constant.REQUEST_CODE_SIGN_TRANSACTION) {
|
||||
if (data != null) {
|
||||
if (data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) {
|
||||
val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID))
|
||||
updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD))
|
||||
ctx.card = updatedCard
|
||||
}
|
||||
}
|
||||
if (resultCode == Constant.RESULT_INVALID_PIN_ && requestPIN2Count < 2) {
|
||||
requestPIN2Count++
|
||||
val bundle = Bundle()
|
||||
bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString())
|
||||
ctx.saveToBundle(bundle)
|
||||
bundle.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
|
||||
navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_,
|
||||
R.id.action_confirmTransactionFragment_to_pinRequestFragment,
|
||||
bundle)
|
||||
return
|
||||
}
|
||||
navigateBackWithResult(resultCode, data)
|
||||
} else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2_) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val bundle = Bundle()
|
||||
ctx.saveToBundle(bundle)
|
||||
bundle.putString(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
|
||||
bundle.putString(Constant.EXTRA_AMOUNT, etAmount.text.toString())
|
||||
bundle.putString(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
|
||||
bundle.putString(Constant.EXTRA_FEE, etFee.text.toString())
|
||||
bundle.putString(Constant.EXTRA_FEE_CURRENCY, tvCurrency2.text.toString())
|
||||
bundle.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
|
||||
navigateForResult(Constant.REQUEST_CODE_SIGN_TRANSACTION,
|
||||
R.id.action_confirmTransactionFragment_to_signTransactionFragment,
|
||||
bundle)
|
||||
} else
|
||||
Toast.makeText(context, R.string.confirm_transaction_error_pin_2_is_required, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTagDiscovered(tag: Tag) {
|
||||
try {
|
||||
(activity as MainActivity).nfcManager.ignoreTag(tag)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun doSetFee(checkedRadioButtonId: Int) {
|
||||
var txtFee = ""
|
||||
when (checkedRadioButtonId) {
|
||||
R.id.rbMinimalFee ->
|
||||
if (ctx.coinData.minFee != null) {
|
||||
txtFee = ctx.coinData.minFee!!.toValueString()
|
||||
btnSend?.visibility = View.VISIBLE
|
||||
} else
|
||||
btnSend?.visibility = View.INVISIBLE
|
||||
|
||||
R.id.rbNormalFee ->
|
||||
if (ctx.coinData.normalFee != null) {
|
||||
txtFee = ctx.coinData.normalFee!!.toValueString()
|
||||
btnSend?.visibility = View.VISIBLE
|
||||
} else
|
||||
btnSend?.visibility = View.INVISIBLE
|
||||
|
||||
R.id.rbMaximumFee ->
|
||||
if (ctx.coinData.maxFee != null) {
|
||||
txtFee = ctx.coinData.maxFee!!.toValueString()
|
||||
btnSend?.visibility = View.VISIBLE
|
||||
} else
|
||||
btnSend?.visibility = View.INVISIBLE
|
||||
}
|
||||
etFee?.setText(txtFee.replace(',', '.'))
|
||||
}
|
||||
|
||||
private fun finishWithError(errorCode: Int, message: String) {
|
||||
navigateBackWithResult(
|
||||
errorCode,
|
||||
bundleOf(Constant.EXTRA_MESSAGE to message),
|
||||
R.id.loadedWalletFragment)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
package com.tangem.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import com.tangem.Constant
|
||||
import com.tangem.data.isPayIdSupported
|
||||
import com.tangem.ui.activity.MainActivity
|
||||
import com.tangem.ui.fragment.BaseFragment
|
||||
import com.tangem.ui.fragment.qr.CameraPermissionManager
|
||||
import com.tangem.ui.navigation.NavigationResultListener
|
||||
import com.tangem.util.UtilHelper
|
||||
import com.tangem.util.extensions.isStart2CoinCard
|
||||
import com.tangem.wallet.CoinEngineFactory
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.TangemContext
|
||||
import kotlinx.android.synthetic.tangemAccess.fragment_prepare_transaction.*
|
||||
import java.io.IOException
|
||||
|
||||
class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback {
|
||||
companion object {
|
||||
val TAG: String = PrepareTransactionFragment::class.java.simpleName
|
||||
}
|
||||
|
||||
override val layoutId = R.layout.fragment_prepare_transaction
|
||||
|
||||
private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) }
|
||||
private val cameraPermissionManager: CameraPermissionManager by lazy { CameraPermissionManager(this) }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
tvCardID.text = ctx.card?.cidDescription
|
||||
val engine = CoinEngineFactory.create(ctx)
|
||||
|
||||
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(engine!!.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(engine!!.balanceHTML)
|
||||
tvBalance.text = html
|
||||
|
||||
if (ctx.blockchain.isPayIdSupported() && !ctx.card.isStart2CoinCard()) {
|
||||
etWallet.hint = getString(R.string.prepare_transaction_hint_address_or_pay_id)
|
||||
}
|
||||
|
||||
if (!engine.allowSelectFeeInclusion()) {
|
||||
rgIncFee.visibility = View.INVISIBLE
|
||||
} else {
|
||||
rgIncFee.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (ctx.card!!.remainingSignatures < 2) {
|
||||
etAmount.isEnabled = false
|
||||
}
|
||||
|
||||
if (ctx.card.remainingSignatures == 1) {
|
||||
androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.prepare_transaction_warning_last_signature)
|
||||
.setMessage(R.string.prepare_transaction_warning_send_full_amount)
|
||||
.setPositiveButton(R.string.general_ok) { _, _ -> }
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
tvCurrency.text = engine.balance.currency
|
||||
etAmount.setText(engine.balance.toValueString())
|
||||
|
||||
// limit number of symbols after comma
|
||||
etAmount.filters = engine.amountInputFilters
|
||||
|
||||
// set listeners
|
||||
etAmount.setOnEditorActionListener { lv, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
val imm = lv.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(lv.windowToken, 0)
|
||||
lv.clearFocus()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
btnVerify.setOnClickListener {
|
||||
if (!UtilHelper.isOnline(requireContext())) {
|
||||
Toast.makeText(context, R.string.general_error_no_connection, Toast.LENGTH_LONG).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val engine1 = CoinEngineFactory.create(ctx)
|
||||
val strAmount: String = etAmount.text.toString().replace(",", ".")
|
||||
val amount = engine1!!.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString())
|
||||
|
||||
try {
|
||||
if (!engine.checkNewTransactionAmount(amount))
|
||||
etAmount.error = getString(R.string.prepare_transaction_error_not_enough_funds)
|
||||
else
|
||||
etAmount.error = null
|
||||
} catch (e: Exception) {
|
||||
etAmount.error = getString(R.string.prepare_transaction_error_unknown_amount_format)
|
||||
}
|
||||
|
||||
// check wallet address
|
||||
if (!engine1.validateAddress(etWallet.text.toString())) {
|
||||
etWallet.error = getString(R.string.prepare_transaction_error_incorrect_destination)
|
||||
return@setOnClickListener
|
||||
} else
|
||||
etWallet.error = null
|
||||
|
||||
if (etWallet.text.toString() == ctx.coinData!!.wallet) {
|
||||
etWallet.error = getString(R.string.prepare_transaction_error_same_address)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (!etAmount.error.isNullOrEmpty() || !etWallet.error.isNullOrEmpty()) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val data = Bundle()
|
||||
ctx.saveToBundle(data)
|
||||
data.putString(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
|
||||
data.putBoolean(Constant.EXTRA_FEE_INCLUDED, (rgIncFee!!.checkedRadioButtonId == R.id.rbFeeIn))
|
||||
data.putString(Constant.EXTRA_AMOUNT, strAmount)
|
||||
data.putString(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
|
||||
navigateForResult(
|
||||
Constant.REQUEST_CODE_SEND_TRANSACTION__,
|
||||
R.id.action_prepareTransactionFragment_to_confirmTransactionFragment,
|
||||
data)
|
||||
}
|
||||
|
||||
ivCamera.setOnClickListener {
|
||||
if (cameraPermissionManager.isPermissionGranted()) {
|
||||
navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareTransactionFragment_to_qrScanFragment)
|
||||
} else {
|
||||
cameraPermissionManager.requirePermission()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
cameraPermissionManager.handleRequestPermissionResult(requestCode, grantResults) {
|
||||
navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareTransactionFragment_to_qrScanFragment)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
|
||||
if (requestCode == Constant.REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.containsKey("QRCode")) {
|
||||
val code = data.getString("QRCode")
|
||||
val schemeSplit = code!!.split(":")
|
||||
when (schemeSplit.size) {
|
||||
2 -> {
|
||||
if (schemeSplit[0] == ctx.blockchain.uriScheme) {
|
||||
etWallet?.setText(schemeSplit[1])
|
||||
} else {
|
||||
etWallet?.setText(code)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
etWallet?.setText(code)
|
||||
}
|
||||
}
|
||||
} else if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION__) {
|
||||
navigateBackWithResult(resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTagDiscovered(tag: Tag) {
|
||||
try {
|
||||
(activity as MainActivity).nfcManager.ignoreTag(tag)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,312 +0,0 @@
|
|||
package com.tangem.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.media.MediaPlayer
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.App
|
||||
import com.tangem.Constant
|
||||
import com.tangem.tangem_card.reader.CardProtocol
|
||||
import com.tangem.tangem_card.tasks.SignTask
|
||||
import com.tangem.tangem_card.util.Util
|
||||
import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation
|
||||
import com.tangem.tangem_sdk.android.reader.NfcReader
|
||||
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
|
||||
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID
|
||||
import com.tangem.tangem_sdk.data.asBundle
|
||||
import com.tangem.ui.activity.MainActivity
|
||||
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
|
||||
import com.tangem.ui.dialog.WaitSecurityDelayDialog
|
||||
import com.tangem.ui.fragment.BaseFragment
|
||||
import com.tangem.ui.navigation.NavigationResultListener
|
||||
import com.tangem.util.Analytics
|
||||
import com.tangem.util.AnalyticsEvent
|
||||
import com.tangem.util.LOG
|
||||
import com.tangem.wallet.CoinEngine
|
||||
import com.tangem.wallet.CoinEngineFactory
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.TangemContext
|
||||
import kotlinx.android.synthetic.main.layout_progress_horizontal.*
|
||||
import kotlinx.android.synthetic.main.layout_touch_card.*
|
||||
import kotlinx.android.synthetic.tangemAccess.fragment_sign_transaction.*
|
||||
|
||||
|
||||
class SignTransactionFragment : BaseFragment(), NavigationResultListener,
|
||||
NfcAdapter.ReaderCallback, CardProtocol.Notifications {
|
||||
|
||||
companion object {
|
||||
val TAG: String = SignTransactionFragment::class.java.simpleName
|
||||
}
|
||||
|
||||
override val layoutId = R.layout.fragment_sign_transaction
|
||||
|
||||
private lateinit var ctx: TangemContext
|
||||
private lateinit var mpFinishSignSound: MediaPlayer
|
||||
|
||||
private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
|
||||
|
||||
private var signTransactionTask: SignTask? = null
|
||||
|
||||
private lateinit var amount: CoinEngine.Amount
|
||||
private lateinit var fee: CoinEngine.Amount
|
||||
private var isIncludeFee = true
|
||||
private var outAddressStr: String? = null
|
||||
private var lastReadSuccess = true
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ctx = TangemContext.loadFromBundle(context, arguments)
|
||||
|
||||
val callback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
navigateBackWithResult(Activity.RESULT_CANCELED)
|
||||
}
|
||||
}
|
||||
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound)
|
||||
|
||||
// init NFC Antenna
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna.init()
|
||||
|
||||
amount = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_AMOUNT), arguments?.getString(Constant.EXTRA_AMOUNT_CURRENCY))
|
||||
fee = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_FEE), arguments?.getString(Constant.EXTRA_FEE_CURRENCY))
|
||||
isIncludeFee = arguments?.getBoolean(Constant.EXTRA_FEE_INCLUDED, true) ?: true
|
||||
outAddressStr = arguments?.getString(Constant.EXTRA_TARGET_ADDRESS)
|
||||
|
||||
tvCardID.text = ctx.card!!.cidDescription
|
||||
progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
|
||||
progressBar.visibility = View.INVISIBLE
|
||||
|
||||
FirebaseAnalytics.getInstance(requireActivity())
|
||||
.logEvent(AnalyticsEvent.READY_TO_SIGN.event, Analytics.setCardData(ctx))
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
signTransactionTask?.cancel(true)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
signTransactionTask?.cancel(true)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
|
||||
if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) {
|
||||
navigateBackWithResult(resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTagDiscovered(tag: Tag) {
|
||||
try {
|
||||
// get IsoDep handle and run cardReader thread
|
||||
val isoDep = IsoDep.get(tag)
|
||||
val uid = tag.id
|
||||
val sUID = Util.byteArrayToHexString(uid)
|
||||
|
||||
if (sUID == ctx.card.uid) {
|
||||
if (lastReadSuccess)
|
||||
isoDep.timeout = ctx.card.pauseBeforePIN2 + 5000
|
||||
else
|
||||
isoDep.timeout = ctx.card.pauseBeforePIN2 + 65000
|
||||
|
||||
val coinEngine = CoinEngineFactory.create(ctx)
|
||||
coinEngine?.setOnNeedSendTransaction { tx ->
|
||||
if (tx != null) {
|
||||
val data = Bundle()
|
||||
ctx.saveToBundle(data)
|
||||
data.putByteArray(Constant.EXTRA_TX, tx)
|
||||
navigateForResult(
|
||||
Constant.REQUEST_CODE_SEND_TRANSACTION_,
|
||||
R.id.action_signTransactionFragment_to_sendTransactionFragment,
|
||||
data)
|
||||
}
|
||||
}
|
||||
val transactionToSign = coinEngine?.constructTransaction(amount, fee, isIncludeFee, outAddressStr)
|
||||
|
||||
signTransactionTask = SignTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep),
|
||||
App.localStorage, App.pinStorage, this, transactionToSign)
|
||||
signTransactionTask?.start()
|
||||
} else
|
||||
(activity as MainActivity).nfcManager.ignoreTag(isoDep.tag)
|
||||
|
||||
} catch (e: CardProtocol.TangemException_WrongAmount) {
|
||||
try {
|
||||
val data = Bundle()
|
||||
data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
|
||||
data.putString(EXTRA_TANGEM_CARD_UID, ctx.card.uid)
|
||||
data.putBundle(EXTRA_TANGEM_CARD, ctx.card.asBundle)
|
||||
navigateBackWithResult(Activity.RESULT_CANCELED, data)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} catch(e: IllegalArgumentException) {
|
||||
val data = Bundle()
|
||||
data.putString(Constant.EXTRA_MESSAGE, e.message)
|
||||
navigateBackWithResult(Activity.RESULT_CANCELED, data, R.id.loadedWalletFragment)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReadStart(cardProtocol: CardProtocol) {
|
||||
rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
|
||||
progressBar?.post {
|
||||
progressBar?.visibility = View.VISIBLE
|
||||
progressBar?.progress = 5
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReadProgress(protocol: CardProtocol, progress: Int) {
|
||||
progressBar?.post { progressBar?.progress = progress }
|
||||
}
|
||||
|
||||
override fun onReadFinish(cardProtocol: CardProtocol?) {
|
||||
signTransactionTask = null
|
||||
if (cardProtocol != null) {
|
||||
if (cardProtocol.error == null) {
|
||||
|
||||
FirebaseAnalytics.getInstance(requireActivity())
|
||||
.logEvent(AnalyticsEvent.SIGNED.event, Analytics.setCardData(ctx))
|
||||
|
||||
rlProgressBar?.post { rlProgressBar?.visibility = View.GONE }
|
||||
|
||||
progressBar?.post {
|
||||
progressBar?.progress = 100
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN)
|
||||
}
|
||||
|
||||
mpFinishSignSound.start()
|
||||
} else {
|
||||
lastReadSuccess = false
|
||||
FirebaseCrashlytics.getInstance().recordException(cardProtocol.error)
|
||||
if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) {
|
||||
progressBar?.post {
|
||||
progressBar?.progress = 100
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
|
||||
}
|
||||
progressBar?.postDelayed({
|
||||
try {
|
||||
progressBar?.progress = 0
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
|
||||
progressBar?.visibility = View.INVISIBLE
|
||||
val data = Bundle()
|
||||
data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_cannot_sign))
|
||||
data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
|
||||
data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
|
||||
navigateBackWithResult(Constant.RESULT_INVALID_PIN_, data)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 500)
|
||||
} else {
|
||||
if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) {
|
||||
try {
|
||||
val data = Bundle()
|
||||
data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
|
||||
data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
|
||||
data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
|
||||
navigateBackWithResult(Activity.RESULT_CANCELED, data)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
progressBar?.post {
|
||||
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
|
||||
if (!NoExtendedLengthSupportDialog.allReadyShowed) {
|
||||
NoExtendedLengthSupportDialog.message = getText(R.string.dialog_the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.dialog_the_nfc_adapter_length_apdu_advice).toString()
|
||||
NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG)
|
||||
}
|
||||
} else {
|
||||
(activity as? MainActivity)?.toastHelper?.showSingleToast(
|
||||
context, getString(R.string.general_notification_scan_again)
|
||||
)
|
||||
}
|
||||
progressBar?.progress = 100
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rlProgressBar?.postDelayed({
|
||||
try {
|
||||
rlProgressBar?.visibility = View.GONE
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 500)
|
||||
|
||||
progressBar?.postDelayed({
|
||||
try {
|
||||
progressBar?.progress = 0
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
|
||||
progressBar?.visibility = View.INVISIBLE
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
override fun onReadCancel() {
|
||||
signTransactionTask = null
|
||||
|
||||
progressBar?.postDelayed({
|
||||
try {
|
||||
progressBar?.progress = 0
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
|
||||
progressBar?.visibility = View.INVISIBLE
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// private val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew()
|
||||
|
||||
override fun onReadBeforeRequest(timeout: Int) {
|
||||
LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
|
||||
activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) }
|
||||
|
||||
// if (!waitSecurityDelayDialogNew.isAdded)
|
||||
// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG)
|
||||
|
||||
|
||||
// val readBeforeRequest = ReadBeforeRequest()
|
||||
// readBeforeRequest.timeout = timeout
|
||||
// EventBus.getDefault().post(readBeforeRequest)
|
||||
}
|
||||
|
||||
override fun onReadAfterRequest() {
|
||||
LOG.i(TAG, "onReadAfterRequest")
|
||||
activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) }
|
||||
|
||||
// val readAfterRequest = ReadAfterRequest()
|
||||
// EventBus.getDefault().post(readAfterRequest)
|
||||
}
|
||||
|
||||
override fun onReadWait(msec: Int) {
|
||||
LOG.i(TAG, "onReadWait msec $msec")
|
||||
activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) }
|
||||
|
||||
// val readWait = ReadWait()
|
||||
// readWait.msec = msec
|
||||
// EventBus.getDefault().post(readWait)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,343 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.tangem.ui.ConfirmTransactionFragment"
|
||||
tools:ignore="Autofill">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="@string/general_send_transaction"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTransaction"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/btn_light"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:paddingBottom="8dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_from_card"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCardID"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
tools:text="BB00 0000 1210 0233" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_balance"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBalance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
tools:text="4.51735 BTC" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_send_to_wallet"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etWallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/confirm_transaction_hint_target_address"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="2"
|
||||
android:padding="12dp"
|
||||
android:singleLine="false"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_1_small"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_amount"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvIncFee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/textView3"
|
||||
app:layout_constraintLeft_toRightOf="@+id/textView3"
|
||||
tools:text="(including fee)" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAmount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789.,"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/prepare_transaction_hint_enter_amount"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:textSize="@dimen/text_size_1_large"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvCurrency"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_min="150dp"
|
||||
android:enabled="false"
|
||||
android:textColor="@color/black"
|
||||
tools:text="1.34343434343443434343434"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCurrency"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_btc"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_1_large"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/etAmount"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llFee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@+id/llTransaction">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView4"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/confirm_transaction_fee"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<info.hoang8f.android.segmented.SegmentedGroup
|
||||
android:id="@+id/rgFee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:checkedButton="@+id/rbNormalFee"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:sc_border_width="2dp"
|
||||
app:sc_corner_radius="10dp"
|
||||
app:sc_tint_color="@color/colorPrimary">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbMinimalFee"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_fee_minimal"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbNormalFee"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_fee_normal"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbMaximumFee"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_fee_priority"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
</info.hoang8f.android.segmented.SegmentedGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etFee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/confirm_transaction_hint_fee_amount"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:textColor="@color/black"
|
||||
android:textColorLink="@android:color/holo_blue_dark"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_min="100dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCurrency2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_btc"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@+id/etFee"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFeeEquivalent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:textSize="@dimen/text_size_1_small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@+id/tvCurrency2"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSend"
|
||||
style="@style/AppTheme.RoundedCornerMaterialButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_send"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="@android:style/Widget.Holo.Light.ProgressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:elevation="1dp"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colorPrimary"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/llFee" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.tangem.ui.PrepareTransactionFragment"
|
||||
tools:ignore="contentDescription">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="@string/general_send_transaction"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llFrom"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/btn_light"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="24dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_from_card"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCardID"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
tools:text="BB00 0000 1210 0233" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_balance"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBalance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
tools:text="4.51735 Btc" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/card_state_loaded_with_zero"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="32dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/llFrom">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_send_to_wallet"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivCamera"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:src="@drawable/qr_scan"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etWallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/prepare_transaction_hint_enter_address"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="text|textMultiLine|textNoSuggestions"
|
||||
android:padding="12dp"
|
||||
android:singleLine="false"
|
||||
android:textColor="@color/colorPrimaryDark"
|
||||
android:textColorLink="@android:color/holo_blue_dark"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@+id/ivCamera"
|
||||
tools:layout_editor_absoluteY="2dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_amount"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAmount"
|
||||
tools:text="1.34343434343443434343434"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789.,"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/prepare_transaction_hint_enter_amount"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:textColor="@color/colorPrimaryDark"
|
||||
android:textSize="@dimen/text_size_1_large"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvCurrency"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_min="150dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCurrency"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_btc"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_1_large"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/etAmount"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<info.hoang8f.android.segmented.SegmentedGroup
|
||||
android:id="@+id/rgIncFee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:checkedButton="@+id/rbFeeIn"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:sc_border_width="2dp"
|
||||
app:sc_corner_radius="10dp"
|
||||
app:sc_tint_color="@color/colorPrimary"
|
||||
tools:layout_editor_absoluteX="10dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbFeeIn"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_including_fee"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbFeeOut"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_not_including_fee"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
</info.hoang8f.android.segmented.SegmentedGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnVerify"
|
||||
style="@style/AppTheme.RoundedCornerMaterialButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/prepare_transaction_btn_verify"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
layout="@layout/layout_touch_card"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="250dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.75">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/now_touch_the_card_with_id"
|
||||
android:textAlignment="center"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCardID"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
tools:text="CB02 0000 0002 5000" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/to_sign_the_transaction"
|
||||
android:textAlignment="center"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/layout_progress_horizontal" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="tangem_app_name" translatable="false">Tangem</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -17,7 +17,6 @@ interface ConfigManager {
|
|||
fun resetToDefault(name: String)
|
||||
|
||||
companion object {
|
||||
const val IS_SENDING_TO_PAY_ID_ENABLED = "isSendingToPayIdEnabled"
|
||||
const val IS_CREATING_TWIN_CARDS_ALLOWED = "isCreatingTwinCardsAllowed"
|
||||
const val IS_TOP_UP_ENABLED = "isTopUpEnabled"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.datasource.config
|
|||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.datasource.config.ConfigManager.Companion.IS_CREATING_TWIN_CARDS_ALLOWED
|
||||
import com.tangem.datasource.config.ConfigManager.Companion.IS_SENDING_TO_PAY_ID_ENABLED
|
||||
import com.tangem.datasource.config.ConfigManager.Companion.IS_TOP_UP_ENABLED
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.config.models.ConfigModel
|
||||
|
|
@ -29,7 +28,6 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
|
||||
override fun turnOff(name: String) {
|
||||
when (name) {
|
||||
IS_SENDING_TO_PAY_ID_ENABLED -> config = config.copy(isSendingToPayIdEnabled = false)
|
||||
IS_TOP_UP_ENABLED -> config = config.copy(isTopUpEnabled = false)
|
||||
IS_CREATING_TWIN_CARDS_ALLOWED -> config = config.copy(isCreatingTwinCardsAllowed = false)
|
||||
}
|
||||
|
|
@ -37,13 +35,13 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
|
||||
override fun resetToDefault(name: String) {
|
||||
when (name) {
|
||||
IS_SENDING_TO_PAY_ID_ENABLED ->
|
||||
config =
|
||||
config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
|
||||
IS_TOP_UP_ENABLED -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
|
||||
IS_CREATING_TWIN_CARDS_ALLOWED ->
|
||||
config =
|
||||
config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
|
||||
IS_TOP_UP_ENABLED -> {
|
||||
config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
|
||||
}
|
||||
IS_CREATING_TWIN_CARDS_ALLOWED -> {
|
||||
config = config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,12 +50,11 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
|
||||
config = config.copy(
|
||||
isTopUpEnabled = model.isTopUpEnabled,
|
||||
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
|
||||
)
|
||||
|
||||
defaultConfig = defaultConfig.copy(
|
||||
isTopUpEnabled = model.isTopUpEnabled,
|
||||
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ data class Config(
|
|||
val appsFlyerDevKey: String = "",
|
||||
val amplitudeApiKey: String = "",
|
||||
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
|
||||
val isSendingToPayIdEnabled: Boolean = true,
|
||||
val isTopUpEnabled: Boolean = false,
|
||||
@Deprecated("Not relevant since version 3.23")
|
||||
val isCreatingTwinCardsAllowed: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.squareup.moshi.Json
|
|||
|
||||
class FeatureModel(
|
||||
val isTopUpEnabled: Boolean,
|
||||
val isSendingToPayIdEnabled: Boolean,
|
||||
val isCreatingTwinCardsAllowed: Boolean,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,11 +35,7 @@
|
|||
<string name="main_processing_full_amount">Der Betrag enthält nicht einige Ihrer Mittel</string>
|
||||
<string name="send_amount_label">Betrag</string>
|
||||
<string name="send_destination_hint_address">Adresse</string>
|
||||
<string name="send_destination_hint_address_payid">Adresse oder PayString</string>
|
||||
<string name="send_error_address_same_as_wallet">Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein</string>
|
||||
<string name="send_error_payid_not_registered">PayString ist nicht registriert</string>
|
||||
<string name="send_error_payid_request_failed">PayString-Anfrage ist fehlgeschlagen</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString wird von der Blockchain nicht unterstützt</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">inkl. Gebühr</string>
|
||||
|
|
@ -57,7 +53,6 @@
|
|||
<string name="send_validation_invalid_address">Ungültige Adresse</string>
|
||||
<string name="shop_one_wallet">Tangem Wallet</string>
|
||||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="wallet_address_button_create_payid">PayString erstellen</string>
|
||||
<string name="wallet_balance_loading">Die Bilanz wird aufgeladen…</string>
|
||||
<string name="wallet_balance_tx_in_progress">Die Transaktion läuft…</string>
|
||||
<string name="wallet_balance_verified">Verifizierte Bilanz</string>
|
||||
|
|
|
|||
|
|
@ -35,11 +35,7 @@
|
|||
<string name="main_processing_full_amount">Le montant n\'inclut pas certains de vos fonds</string>
|
||||
<string name="send_amount_label">Somme</string>
|
||||
<string name="send_destination_hint_address">Adresse</string>
|
||||
<string name="send_destination_hint_address_payid">Adresse ou PayString</string>
|
||||
<string name="send_error_address_same_as_wallet">L\'adresse est la même que celle de votre portefeuille</string>
|
||||
<string name="send_error_payid_not_registered">PayString non enregistré</string>
|
||||
<string name="send_error_payid_request_failed">La demande de PayString a échoué</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString non pris en charge par la blockchain</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">Inclure les commissions</string>
|
||||
|
|
@ -57,7 +53,6 @@
|
|||
<string name="send_validation_invalid_address">Adresse incorrecte</string>
|
||||
<string name="shop_one_wallet">Tangem Wallet</string>
|
||||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="wallet_address_button_create_payid">Créer PayString</string>
|
||||
<string name="wallet_balance_loading">Solde est en cours de téléchargement…</string>
|
||||
<string name="wallet_balance_tx_in_progress">Transaction en cours…</string>
|
||||
<string name="wallet_balance_verified">Solde confirmé</string>
|
||||
|
|
|
|||
|
|
@ -35,11 +35,7 @@
|
|||
<string name="main_processing_full_amount">L\'importo non include alcuni dei tuoi fondi</string>
|
||||
<string name="send_amount_label">Importo</string>
|
||||
<string name="send_destination_hint_address">Indirizzo</string>
|
||||
<string name="send_destination_hint_address_payid">Indirizzo o PayString</string>
|
||||
<string name="send_error_address_same_as_wallet">L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio</string>
|
||||
<string name="send_error_payid_not_registered">PayString non registrato</string>
|
||||
<string name="send_error_payid_request_failed">Richiesta PayString fallita</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString non supportato dalla blockchain</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">Includi commissione</string>
|
||||
|
|
@ -57,7 +53,6 @@
|
|||
<string name="send_validation_invalid_address">Indirizzo non valido</string>
|
||||
<string name="shop_one_wallet">Tangem Wallet</string>
|
||||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="wallet_address_button_create_payid">Crea PayString</string>
|
||||
<string name="wallet_balance_loading">Il saldo sta per essere caricato…</string>
|
||||
<string name="wallet_balance_tx_in_progress">Transazione in corso…</string>
|
||||
<string name="wallet_balance_verified">Saldo verificato</string>
|
||||
|
|
|
|||
|
|
@ -318,11 +318,7 @@
|
|||
<string name="search_tokens_title">Поиск валют</string>
|
||||
<string name="send_amount_label">Сумма</string>
|
||||
<string name="send_destination_hint_address">Адрес</string>
|
||||
<string name="send_destination_hint_address_payid">Адрес или PayString</string>
|
||||
<string name="send_error_address_same_as_wallet">Адрес совпадает с адресом кошелька</string>
|
||||
<string name="send_error_payid_not_registered">PayString не зарегистрирован</string>
|
||||
<string name="send_error_payid_request_failed">Не удалось выполнить запрос PayString</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString не поддерживается блокчейном</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Недопустимый Tag. Он не будет добавлен в транзакцию.</string>
|
||||
<string name="send_extras_error_invalid_memo">Недопустимый Memo. Он не будет добавлен в транзакцию.</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
|
|
@ -451,7 +447,6 @@
|
|||
<string name="user_wallet_list_single_header">Одновалютные</string>
|
||||
<string name="user_wallet_list_title">Мои кошельки</string>
|
||||
<string name="user_wallet_list_unlock_all">Разблокировать все с %s</string>
|
||||
<string name="wallet_address_button_create_payid">Создать PayString</string>
|
||||
<string name="wallet_address_button_explore">История транзакций</string>
|
||||
<string name="wallet_balance_blockchain_unreachable">Сеть недоступна</string>
|
||||
<string name="wallet_balance_blockchain_unreachable_try_later">Блокчейн недоступен. Попробуй позже.</string>
|
||||
|
|
|
|||
|
|
@ -281,11 +281,7 @@
|
|||
<string name="search_tokens_title">搜尋代幣</string>
|
||||
<string name="send_amount_label">數量</string>
|
||||
<string name="send_destination_hint_address">地址</string>
|
||||
<string name="send_destination_hint_address_payid">地址或 PayString</string>
|
||||
<string name="send_error_address_same_as_wallet">地址與錢包地址相同</string>
|
||||
<string name="send_error_payid_not_registered">PayString 未註冊</string>
|
||||
<string name="send_error_payid_request_failed">PayString 請求失敗</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString 不被區塊鏈支持</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">標籤無效。它不會被添加到交易中</string>
|
||||
<string name="send_extras_error_invalid_memo">Memo無效。 它不會被添加到交易中</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
|
|
@ -395,7 +391,6 @@
|
|||
<string name="user_wallet_list_single_header">單一幣種</string>
|
||||
<string name="user_wallet_list_title">我的錢包</string>
|
||||
<string name="user_wallet_list_unlock_all">用 %s 解鎖全部</string>
|
||||
<string name="wallet_address_button_create_payid">創建支付字符串</string>
|
||||
<string name="wallet_address_button_explore">交易記錄</string>
|
||||
<string name="wallet_balance_blockchain_unreachable">網路無法使用</string>
|
||||
<string name="wallet_balance_blockchain_unreachable_try_later">區塊鍊無法使用。稍後再試</string>
|
||||
|
|
|
|||
|
|
@ -314,11 +314,7 @@
|
|||
<string name="search_tokens_title">Search tokens</string>
|
||||
<string name="send_amount_label">Amount</string>
|
||||
<string name="send_destination_hint_address">Address</string>
|
||||
<string name="send_destination_hint_address_payid">Address or PayString</string>
|
||||
<string name="send_error_address_same_as_wallet">Address is the same as wallet address</string>
|
||||
<string name="send_error_payid_not_registered">PayString not registered</string>
|
||||
<string name="send_error_payid_request_failed">PayString request failed</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString unsupported by blockchain</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
|
|
@ -443,7 +439,6 @@
|
|||
<string name="user_wallet_list_single_header">Single-currency</string>
|
||||
<string name="user_wallet_list_title">My Wallets</string>
|
||||
<string name="user_wallet_list_unlock_all">Unlock all with %s</string>
|
||||
<string name="wallet_address_button_create_payid">Create PayString</string>
|
||||
<string name="wallet_address_button_explore">Transaction history</string>
|
||||
<string name="wallet_balance_blockchain_unreachable">Network is unreachable</string>
|
||||
<string name="wallet_balance_blockchain_unreachable_try_later">Blockchain is unreachable. Try later</string>
|
||||
|
|
|
|||
|
|
@ -2,19 +2,17 @@ package com.tangem.core.ui.components
|
|||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
// region TextButton
|
||||
|
|
@ -26,7 +24,7 @@ fun TextButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier,
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
|
|
@ -49,7 +47,7 @@ fun TextButtonIconStart(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
icon = TangemButtonIconPosition.Start(iconResId),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
|
|
@ -63,7 +61,7 @@ fun WarningTextButton(text: String, onClick: () -> Unit, modifier: Modifier = Mo
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
|
|
@ -85,7 +83,7 @@ fun PrimaryButton(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -108,7 +106,7 @@ fun PrimaryButtonIconEnd(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.End(iconResId),
|
||||
icon = TangemButtonIconPosition.End(iconResId),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -131,7 +129,7 @@ fun PrimaryButtonIconStart(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
icon = TangemButtonIconPosition.Start(iconResId),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -152,7 +150,7 @@ fun SecondaryButton(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -175,7 +173,7 @@ fun SecondaryButtonIconEnd(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.End(iconResId),
|
||||
icon = TangemButtonIconPosition.End(iconResId),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -198,7 +196,7 @@ fun SecondaryButtonIconStart(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
icon = TangemButtonIconPosition.Start(iconResId),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -214,7 +212,7 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif
|
|||
modifier = modifier,
|
||||
text = text,
|
||||
textStyle = TangemTheme.typography.subtitle2,
|
||||
icon = TangemButtonIcon.End(iconResId = R.drawable.ic_chevron_24),
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.selectorButtonColors,
|
||||
showProgress = false,
|
||||
|
|
@ -224,362 +222,6 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif
|
|||
}
|
||||
// endregion Other
|
||||
|
||||
// region Action
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-305&t=3z98eFnTeyIx5TH5-4)
|
||||
* */
|
||||
@Composable
|
||||
fun RoundedActionButton(
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1208-1395&t=3z98eFnTeyIx5TH5-4)
|
||||
* */
|
||||
@Composable
|
||||
fun ActionButton(
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
size = TangemButtonSize.Action,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as [RoundedActionButton] but colored in primary background color
|
||||
* */
|
||||
@Composable
|
||||
fun BackgroundActionButton(
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.backgroundButtonColors,
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
)
|
||||
}
|
||||
// endregion Action
|
||||
|
||||
// region Defaults
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun TangemButton(
|
||||
text: String,
|
||||
icon: TangemButtonIcon,
|
||||
onClick: () -> Unit,
|
||||
colors: ButtonColors,
|
||||
showProgress: Boolean,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
elevation: ButtonElevation = TangemButtonsDefaults.elevation,
|
||||
textStyle: TextStyle = TangemTheme.typography.button,
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier.heightIn(min = size.toHeightDp()),
|
||||
onClick = { if (!showProgress) onClick() },
|
||||
enabled = enabled,
|
||||
elevation = elevation,
|
||||
shape = size.toShape(),
|
||||
colors = colors,
|
||||
contentPadding = size.toContentPadding(icon = icon),
|
||||
) {
|
||||
ButtonContent(
|
||||
text = text,
|
||||
textStyle = textStyle,
|
||||
buttonIcon = icon,
|
||||
colors = colors,
|
||||
showProgress = showProgress,
|
||||
enabled = enabled,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun ButtonContent(
|
||||
text: String,
|
||||
textStyle: TextStyle,
|
||||
buttonIcon: TangemButtonIcon,
|
||||
colors: ButtonColors,
|
||||
size: TangemButtonSize,
|
||||
enabled: Boolean,
|
||||
showProgress: Boolean,
|
||||
) {
|
||||
val icon = @Composable { iconResId: Int ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
painter = painterResource(id = iconResId),
|
||||
tint = colors.contentColor(enabled = enabled).value,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
if (showProgress) {
|
||||
Box(modifier = Modifier.wrapContentSize()) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(TangemTheme.dimens.size24),
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
strokeWidth = TangemTheme.dimens.size4,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()),
|
||||
) {
|
||||
if (buttonIcon is TangemButtonIcon.Start) {
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
maxLines = 1,
|
||||
)
|
||||
if (buttonIcon is TangemButtonIcon.End) {
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private sealed interface TangemButtonIcon {
|
||||
val iconResId: Int?
|
||||
|
||||
data class Start(override val iconResId: Int) : TangemButtonIcon
|
||||
|
||||
data class End(override val iconResId: Int) : TangemButtonIcon
|
||||
|
||||
object None : TangemButtonIcon {
|
||||
override val iconResId: Int? = null
|
||||
}
|
||||
}
|
||||
|
||||
private enum class TangemButtonSize {
|
||||
Default,
|
||||
Text,
|
||||
Selector,
|
||||
Action,
|
||||
RoundedAction,
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toHeightDp(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.size48
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.size40
|
||||
TangemButtonSize.Selector -> TangemTheme.dimens.size24
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> TangemTheme.dimens.size36
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toShape(): Shape = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toIconPadding(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Selector -> 0.dp
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> TangemTheme.dimens.spacing8
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toContentPadding(icon: TangemButtonIcon): PaddingValues {
|
||||
val horizontalPadding = this.toHorizontalContentPadding(icon = icon)
|
||||
|
||||
return when (this) {
|
||||
TangemButtonSize.Default -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing14,
|
||||
bottom = TangemTheme.dimens.spacing14,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Text -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Selector -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing0_5,
|
||||
bottom = TangemTheme.dimens.spacing0_5,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing8,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIcon): Pair<Dp, Dp> {
|
||||
return when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
|
||||
TangemButtonSize.Text -> when (icon) {
|
||||
is TangemButtonIcon.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
|
||||
is TangemButtonIcon.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
|
||||
is TangemButtonIcon.End -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing14
|
||||
}
|
||||
TangemButtonSize.Selector -> TangemTheme.dimens.spacing0_5 to TangemTheme.dimens.spacing0_5
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> when (icon) {
|
||||
is TangemButtonIcon.None -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing24
|
||||
is TangemButtonIcon.Start -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing24
|
||||
is TangemButtonIcon.End -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object TangemButtonsDefaults {
|
||||
val elevation: ButtonElevation
|
||||
@Composable get() = ButtonDefaults
|
||||
.elevation(
|
||||
defaultElevation = TangemTheme.dimens.elevation0,
|
||||
pressedElevation = TangemTheme.dimens.elevation0,
|
||||
)
|
||||
|
||||
val primaryButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.button.primary,
|
||||
contentColor = TangemTheme.colors.text.primary2,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val secondaryButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.button.secondary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val defaultTextButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.secondary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val warningTextButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.warning,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val selectorButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.tertiary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val backgroundButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private open class TangemButtonColors(
|
||||
private val backgroundColor: Color,
|
||||
private val contentColor: Color,
|
||||
private val disabledBackgroundColor: Color,
|
||||
private val disabledContentColor: Color,
|
||||
) : ButtonColors {
|
||||
@Composable
|
||||
override fun backgroundColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(newValue = if (enabled) backgroundColor else disabledBackgroundColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun contentColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(newValue = if (enabled) contentColor else disabledContentColor)
|
||||
}
|
||||
}
|
||||
// endregion Defaults
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun PrimaryButtonSample() {
|
||||
|
|
@ -730,34 +372,4 @@ private fun TextButtonPreview_DarkTheme() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionButtonSample() {
|
||||
Column(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
|
||||
ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
|
||||
BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
|
||||
RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
|
||||
ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
|
||||
BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun ActionButtonPreview_LightTheme() {
|
||||
TangemTheme {
|
||||
ActionButtonSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun ActionButtonPreview_DarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
ActionButtonSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Closable notification with custom icon
|
||||
* Child of parent component
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1045-807&t=6CVvYDJe0sB7wBKE-0">Figma component</a>
|
||||
*
|
||||
* Use to show banner with custom icon and possibility to close
|
||||
* i.e. Feedback notification
|
||||
*
|
||||
* @param title notification title
|
||||
* @param icon drawable res on icon
|
||||
* @param iconColor icon color
|
||||
* @param onClick callback on click
|
||||
* @param onCloseClick callback on close icon click
|
||||
*/
|
||||
@Composable
|
||||
fun ClosableNotification(
|
||||
title: String,
|
||||
@DrawableRes icon: Int,
|
||||
iconColor: Color,
|
||||
onClick: (() -> Unit),
|
||||
onCloseClick: (() -> Unit),
|
||||
) {
|
||||
NotificationCardTemplate(onClick) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.align(Alignment.CenterStart),
|
||||
painter = painterResource(id = icon),
|
||||
tint = iconColor,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing28)
|
||||
.align(Alignment.CenterStart),
|
||||
text = title,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.align(Alignment.CenterEnd)
|
||||
.clickable(onClick = onCloseClick),
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification component from Design system
|
||||
* There are few states for this component, but only one parent, see link below
|
||||
*
|
||||
* Use this for Notification with title, subtitle, clickable or not
|
||||
*
|
||||
* @param title notification title
|
||||
* @param subtitle notification subtitle
|
||||
* @param onClick click on notification, if its null then no chevron icon
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1045-807&t=6CVvYDJe0sB7wBKE-0">Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun WarningNotification(title: String, subtitle: String?, onClick: (() -> Unit)?) {
|
||||
NotificationCardTemplate(onClick) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.align(Alignment.CenterStart),
|
||||
painter = painterResource(id = R.drawable.img_attention_20),
|
||||
contentDescription = null,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing28)
|
||||
.align(Alignment.CenterStart),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
if (!subtitle.isNullOrEmpty()) {
|
||||
SpacerH2()
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (onClick != null) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.align(Alignment.CenterEnd),
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun NotificationCardTemplate(onClick: (() -> Unit)? = null, content: @Composable BoxScope.() -> Unit) {
|
||||
Surface(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius18),
|
||||
onClick = onClick ?: {},
|
||||
enabled = onClick != null,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
)
|
||||
.wrapContentSize(),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
||||
@Composable
|
||||
private fun WarningNotificationPreview() {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
WarningNotification(
|
||||
title = "Your wallet hasn’t been backed up",
|
||||
subtitle = "Lorem ipsum dolor sit amet, consectetur " +
|
||||
"adipiscing elit, sed do eiusmod tempor incididunt ut labore et...",
|
||||
onClick = {},
|
||||
)
|
||||
SpacerH32()
|
||||
WarningNotification(
|
||||
title = "Your wallet hasn’t been backed up",
|
||||
subtitle = null,
|
||||
onClick = {},
|
||||
)
|
||||
SpacerH32()
|
||||
WarningNotification(
|
||||
title = "Your wallet hasn’t been backed up",
|
||||
subtitle = "Lorem ipsum dolor sit amet, consectetur " +
|
||||
"adipiscing elit, sed do eiusmod tempor incididunt ut labore et...",
|
||||
onClick = null,
|
||||
)
|
||||
SpacerH32()
|
||||
WarningNotification(
|
||||
title = "Your wallet hasn’t been backed up",
|
||||
subtitle = null,
|
||||
onClick = null,
|
||||
)
|
||||
SpacerH32()
|
||||
ClosableNotification(
|
||||
title = "Like tangem app?",
|
||||
icon = R.drawable.ic_star_24,
|
||||
iconColor = TangemTheme.colors.icon.attention,
|
||||
onClick = {},
|
||||
onCloseClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_WarningNotification_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
WarningNotificationPreview()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_WarningNotification_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
WarningNotificationPreview()
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.core.ui.components.buttons.actions
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
/**
|
||||
* Action button config
|
||||
*
|
||||
* @property text text
|
||||
* @property iconResId icon resource id
|
||||
* @property onClick lambda be invoked when action component is clicked
|
||||
* @property enabled enabled
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class ActionConfig(
|
||||
val text: String,
|
||||
@DrawableRes val iconResId: Int,
|
||||
val onClick: () -> Unit,
|
||||
val enabled: Boolean = true,
|
||||
)
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package com.tangem.core.ui.components.buttons.actions
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-305&t=3z98eFnTeyIx5TH5-4)
|
||||
*/
|
||||
@Composable
|
||||
fun RoundedActionButton(config: ActionConfig, modifier: Modifier = Modifier) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = config.text,
|
||||
icon = TangemButtonIconPosition.Start(config.iconResId),
|
||||
onClick = config.onClick,
|
||||
enabled = config.enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1208-1395&t=3z98eFnTeyIx5TH5-4)
|
||||
*/
|
||||
@Composable
|
||||
fun ActionButton(config: ActionConfig, modifier: Modifier = Modifier) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = config.text,
|
||||
icon = TangemButtonIconPosition.Start(config.iconResId),
|
||||
onClick = config.onClick,
|
||||
enabled = config.enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
size = TangemButtonSize.Action,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as [RoundedActionButton] but colored in primary background color
|
||||
*/
|
||||
@Composable
|
||||
fun BackgroundActionButton(config: ActionConfig, modifier: Modifier = Modifier) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = config.text,
|
||||
icon = TangemButtonIconPosition.Start(config.iconResId),
|
||||
onClick = config.onClick,
|
||||
enabled = config.enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.backgroundButtonColors,
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ActionButton_Light(@PreviewParameter(ActionStateProvider::class) state: ActionConfig) {
|
||||
TangemTheme(isDark = false) {
|
||||
Column {
|
||||
RoundedActionButton(state)
|
||||
ActionButton(state)
|
||||
BackgroundActionButton(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ActionButton_Dark(@PreviewParameter(ActionStateProvider::class) state: ActionConfig) {
|
||||
TangemTheme {
|
||||
Column {
|
||||
RoundedActionButton(state)
|
||||
ActionButton(state)
|
||||
BackgroundActionButton(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionStateProvider : CollectionPreviewParameterProvider<ActionConfig>(
|
||||
collection = listOf(
|
||||
ActionConfig(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = {}),
|
||||
ActionConfig(text = "Receive", iconResId = R.drawable.ic_arrow_down_24, enabled = false, onClick = {}),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun TangemButton(
|
||||
text: String,
|
||||
icon: TangemButtonIconPosition,
|
||||
onClick: () -> Unit,
|
||||
colors: ButtonColors,
|
||||
showProgress: Boolean,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
elevation: ButtonElevation = TangemButtonsDefaults.elevation,
|
||||
textStyle: TextStyle = TangemTheme.typography.button,
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier.heightIn(min = size.toHeightDp()),
|
||||
onClick = { if (!showProgress) onClick() },
|
||||
enabled = enabled,
|
||||
elevation = elevation,
|
||||
shape = size.toShape(),
|
||||
colors = colors,
|
||||
contentPadding = size.toContentPadding(icon = icon),
|
||||
) {
|
||||
ButtonContent(
|
||||
text = text,
|
||||
textStyle = textStyle,
|
||||
buttonIcon = icon,
|
||||
colors = colors,
|
||||
showProgress = showProgress,
|
||||
enabled = enabled,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun ButtonContent(
|
||||
text: String,
|
||||
textStyle: TextStyle,
|
||||
buttonIcon: TangemButtonIconPosition,
|
||||
colors: ButtonColors,
|
||||
size: TangemButtonSize,
|
||||
enabled: Boolean,
|
||||
showProgress: Boolean,
|
||||
) {
|
||||
val icon = @Composable { iconResId: Int ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
painter = painterResource(id = iconResId),
|
||||
tint = colors.contentColor(enabled = enabled).value,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
if (showProgress) {
|
||||
Box(modifier = Modifier.wrapContentSize()) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(TangemTheme.dimens.size24),
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
strokeWidth = TangemTheme.dimens.size4,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()),
|
||||
) {
|
||||
if (buttonIcon is TangemButtonIconPosition.Start) {
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
maxLines = 1,
|
||||
)
|
||||
if (buttonIcon is TangemButtonIconPosition.End) {
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
internal class TangemButtonColors(
|
||||
private val backgroundColor: Color,
|
||||
private val contentColor: Color,
|
||||
private val disabledBackgroundColor: Color,
|
||||
private val disabledContentColor: Color,
|
||||
) : ButtonColors {
|
||||
|
||||
@Composable
|
||||
override fun backgroundColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(newValue = if (enabled) backgroundColor else disabledBackgroundColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun contentColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(newValue = if (enabled) contentColor else disabledContentColor)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
internal sealed interface TangemButtonIconPosition {
|
||||
val iconResId: Int?
|
||||
|
||||
data class Start(@DrawableRes override val iconResId: Int) : TangemButtonIconPosition
|
||||
|
||||
data class End(@DrawableRes override val iconResId: Int) : TangemButtonIconPosition
|
||||
|
||||
object None : TangemButtonIconPosition {
|
||||
@DrawableRes
|
||||
override val iconResId: Int? = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
internal enum class TangemButtonSize {
|
||||
Default,
|
||||
Text,
|
||||
Selector,
|
||||
Action,
|
||||
RoundedAction,
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toHeightDp(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.size48
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.size40
|
||||
TangemButtonSize.Selector -> TangemTheme.dimens.size24
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> TangemTheme.dimens.size36
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toShape(): Shape = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toIconPadding(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Selector -> 0.dp
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> TangemTheme.dimens.spacing8
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition): PaddingValues {
|
||||
val horizontalPadding = this.toHorizontalContentPadding(icon = icon)
|
||||
|
||||
return when (this) {
|
||||
TangemButtonSize.Default -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing14,
|
||||
bottom = TangemTheme.dimens.spacing14,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Text -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Selector -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing0_5,
|
||||
bottom = TangemTheme.dimens.spacing0_5,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing8,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIconPosition): Pair<Dp, Dp> {
|
||||
return when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
|
||||
TangemButtonSize.Text -> when (icon) {
|
||||
is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
|
||||
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
|
||||
is TangemButtonIconPosition.End -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing14
|
||||
}
|
||||
TangemButtonSize.Selector -> TangemTheme.dimens.spacing0_5 to TangemTheme.dimens.spacing0_5
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> when (icon) {
|
||||
is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing24
|
||||
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing24
|
||||
is TangemButtonIconPosition.End -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.ButtonElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
internal object TangemButtonsDefaults {
|
||||
|
||||
val elevation: ButtonElevation
|
||||
@Composable get() = ButtonDefaults.elevation(
|
||||
defaultElevation = TangemTheme.dimens.elevation0,
|
||||
pressedElevation = TangemTheme.dimens.elevation0,
|
||||
)
|
||||
|
||||
val primaryButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.button.primary,
|
||||
contentColor = TangemTheme.colors.text.primary2,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val secondaryButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.button.secondary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val defaultTextButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.secondary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val warningTextButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.warning,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val selectorButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.tertiary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val backgroundButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
package com.tangem.core.ui.components.notifications
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH2
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Notification component from Design system.
|
||||
* Use this for Notification with title, subtitle, clickable or not.
|
||||
*
|
||||
* @param state component state
|
||||
* @param modifier modifier
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1045-807&t=6CVvYDJe0sB7wBKE-0"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun Notification(state: NotificationState, modifier: Modifier = Modifier) {
|
||||
Surface(
|
||||
onClick = if (state is NotificationState.Action) {
|
||||
state.onClick
|
||||
} else {
|
||||
{}
|
||||
},
|
||||
modifier = modifier,
|
||||
enabled = when (state) {
|
||||
is NotificationState.Simple -> false
|
||||
is NotificationState.Action -> true
|
||||
},
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius18),
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12, vertical = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
NotificationIcon(
|
||||
iconResId = state.iconResId,
|
||||
iconTint = state.tint,
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size20)
|
||||
.align(alignment = Alignment.CenterStart),
|
||||
)
|
||||
|
||||
NotificationInfoBlock(
|
||||
title = state.title,
|
||||
subtitle = state.subtitle,
|
||||
modifier = Modifier.align(alignment = Alignment.CenterStart),
|
||||
)
|
||||
|
||||
if (state is NotificationState.Action) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size20)
|
||||
.align(alignment = Alignment.CenterEnd),
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationIcon(@DrawableRes iconResId: Int, iconTint: Color?, modifier: Modifier = Modifier) {
|
||||
if (iconTint != null) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
modifier = modifier,
|
||||
tint = iconTint,
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
painter = painterResource(id = iconResId),
|
||||
contentDescription = null,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationInfoBlock(title: String, subtitle: String?, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing30)) {
|
||||
Text(
|
||||
text = title,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
|
||||
if (!subtitle.isNullOrEmpty()) {
|
||||
SpacerH2()
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_WarningNotification_Light(
|
||||
@PreviewParameter(NotificationStateProvider::class)
|
||||
state: NotificationState,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
Notification(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_WarningNotification_Dark(
|
||||
@PreviewParameter(NotificationStateProvider::class)
|
||||
state: NotificationState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
Notification(state)
|
||||
}
|
||||
}
|
||||
|
||||
private class NotificationStateProvider : CollectionPreviewParameterProvider<NotificationState>(
|
||||
collection = listOf(
|
||||
NotificationState.Simple(
|
||||
title = "Your wallet hasn’t been backed up",
|
||||
subtitle = "Lorem ipsum dolor sit amet, consectetur " +
|
||||
"adipiscing elit, sed do eiusmod tempor incididunt ut labore et...",
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
NotificationState.Simple(
|
||||
title = "Your wallet hasn’t been backed up",
|
||||
subtitle = null,
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
tint = TangemColorPalette.Amaranth,
|
||||
),
|
||||
NotificationState.Action(
|
||||
title = "Your wallet hasn’t been backed up",
|
||||
subtitle = "Lorem ipsum dolor sit amet, consectetur " +
|
||||
"adipiscing elit, sed do eiusmod tempor incididunt ut labore et...",
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
onClick = {},
|
||||
),
|
||||
NotificationState.Action(
|
||||
title = "Your wallet hasn’t been backed up",
|
||||
subtitle = null,
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
tint = TangemColorPalette.Amaranth,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.core.ui.components.notifications
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
* Notification component state
|
||||
*
|
||||
* @property title title
|
||||
* @property subtitle subtitle
|
||||
* @property iconResId icon resource id
|
||||
* @property tint icon tint
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class NotificationState(
|
||||
open val title: String,
|
||||
open val subtitle: String? = null,
|
||||
@DrawableRes open val iconResId: Int,
|
||||
open val tint: Color? = null,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Simple notification state. Non clickable.
|
||||
*
|
||||
* @property title title
|
||||
* @property subtitle subtitle
|
||||
* @property iconResId icon resource id
|
||||
* @property tint icon tint
|
||||
*/
|
||||
data class Simple(
|
||||
override val title: String,
|
||||
override val subtitle: String? = null,
|
||||
@DrawableRes override val iconResId: Int,
|
||||
override val tint: Color? = null,
|
||||
) : NotificationState(title, subtitle, iconResId, tint)
|
||||
|
||||
/**
|
||||
* Clickable notification state
|
||||
*
|
||||
* @property title title
|
||||
* @property subtitle subtitle
|
||||
* @property iconResId icon resource id
|
||||
* @property tint icon tint
|
||||
* @param onClick lambda be invoked when notification component is clicked
|
||||
*/
|
||||
data class Action(
|
||||
override val title: String,
|
||||
override val subtitle: String? = null,
|
||||
@DrawableRes override val iconResId: Int,
|
||||
override val tint: Color? = null,
|
||||
val onClick: () -> Unit,
|
||||
) : NotificationState(title, subtitle, iconResId, tint)
|
||||
}
|
||||
|
|
@ -76,6 +76,7 @@ data class TangemDimens internal constructor(
|
|||
val size200: Dp = 200.dp,
|
||||
// endregion Size
|
||||
// region Spacing
|
||||
val spacing0: Dp = 0.dp,
|
||||
val spacing0_5: Dp = 0.5.dp,
|
||||
val spacing2: Dp = 2.dp,
|
||||
val spacing4: Dp = 4.dp,
|
||||
|
|
@ -91,6 +92,7 @@ data class TangemDimens internal constructor(
|
|||
val spacing24: Dp = 24.dp,
|
||||
val spacing26: Dp = 26.dp,
|
||||
val spacing28: Dp = 28.dp,
|
||||
val spacing30: Dp = 30.dp,
|
||||
val spacing32: Dp = 32.dp,
|
||||
val spacing34: Dp = 34.dp,
|
||||
val spacing36: Dp = 34.dp,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
|||
"bitcoin-cash/test" -> Blockchain.BitcoinCashTestnet
|
||||
"cardano" -> Blockchain.CardanoShelley
|
||||
"dogecoin" -> Blockchain.Dogecoin
|
||||
"ducatus" -> Blockchain.Ducatus
|
||||
"litecoin" -> Blockchain.Litecoin
|
||||
"rootstock" -> Blockchain.RSK
|
||||
"stellar" -> Blockchain.Stellar
|
||||
|
|
@ -83,7 +82,6 @@ fun Blockchain.toNetworkId(): String {
|
|||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.CardanoShelley -> "cardano"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Ethereum -> "ethereum"
|
||||
Blockchain.EthereumTestnet -> "ethereum/test"
|
||||
Blockchain.EthereumClassic -> "ethereum-classic"
|
||||
|
|
@ -144,7 +142,6 @@ fun Blockchain.toCoinId(): String {
|
|||
Blockchain.Fantom, Blockchain.FantomTestnet -> "fantom"
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> "tron"
|
||||
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> "polkadot"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Litecoin -> "litecoin"
|
||||
Blockchain.RSK -> "rootstock"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
|
|
@ -172,7 +169,4 @@ fun Blockchain.isSupportedInApp(): Boolean {
|
|||
return !excludedBlockchains.contains(this)
|
||||
}
|
||||
|
||||
private val excludedBlockchains = listOf(
|
||||
Blockchain.Ducatus,
|
||||
Blockchain.Unknown,
|
||||
)
|
||||
private val excludedBlockchains = listOf(Blockchain.Unknown)
|
||||
|
|
@ -22,6 +22,7 @@ dependencies {
|
|||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.reorderable)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@ import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenO
|
|||
import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensListState
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.*
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.util.UUID
|
||||
|
|
@ -108,21 +105,22 @@ internal object WalletPreviewData {
|
|||
private const val tokensSize = 3
|
||||
val draggableItems = List(networksSize) { it }
|
||||
.flatMap { index ->
|
||||
val n = index + 1
|
||||
val lastNetworkIndex = networksSize - 1
|
||||
val networkNumber = index + 1
|
||||
|
||||
val group = DraggableItem.GroupHeader(
|
||||
id = "group_$n",
|
||||
networkName = "$n",
|
||||
id = "group_$networkNumber",
|
||||
networkName = "$networkNumber",
|
||||
)
|
||||
|
||||
val tokens: MutableList<DraggableItem.Token> = mutableListOf()
|
||||
repeat(times = tokensSize) { i ->
|
||||
val nt = i + 1
|
||||
val tokenNumber = i + 1
|
||||
tokens.add(
|
||||
DraggableItem.Token(
|
||||
tokenItemState = tokenItemDragState.copy(
|
||||
id = "${group.id}_token_$nt",
|
||||
name = "Token $nt",
|
||||
id = "${group.id}_token_$tokenNumber",
|
||||
name = "Token $tokenNumber",
|
||||
networkIconResId = R.drawable.img_eth_22.takeIf { i != 0 },
|
||||
),
|
||||
groupId = group.id,
|
||||
|
|
@ -130,9 +128,14 @@ internal object WalletPreviewData {
|
|||
)
|
||||
}
|
||||
|
||||
val divider = DraggableItem.GroupPlaceholder(id = "divider_$networkNumber")
|
||||
|
||||
buildList {
|
||||
add(group)
|
||||
addAll(tokens)
|
||||
if (index != lastNetworkIndex) {
|
||||
add(divider)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toPersistentList()
|
||||
|
|
@ -149,6 +152,12 @@ internal object WalletPreviewData {
|
|||
onSortByBalanceClick = {},
|
||||
onGroupByNetworkClick = {},
|
||||
),
|
||||
dragConfig = OrganizeTokensStateHolder.DragConfig(
|
||||
onItemDragged = { _, _ -> },
|
||||
onDragStart = {},
|
||||
canDragItemOver = { _, _ -> false },
|
||||
onItemDragEnd = {},
|
||||
),
|
||||
actions = OrganizeTokensStateHolder.ActionsConfig(
|
||||
onApplyClick = {},
|
||||
onCancelClick = {},
|
||||
|
|
@ -161,6 +170,14 @@ internal object WalletPreviewData {
|
|||
),
|
||||
)
|
||||
|
||||
val manageButtons = persistentListOf(
|
||||
WalletManageButton.Buy(onClick = {}),
|
||||
WalletManageButton.Send(onClick = {}),
|
||||
WalletManageButton.Receive(onClick = {}),
|
||||
WalletManageButton.Exchange(onClick = {}),
|
||||
WalletManageButton.CopyAddress(onClick = {}),
|
||||
)
|
||||
|
||||
val multicurrencyWalletScreenState = WalletStateHolder.MultiCurrencyContent(
|
||||
onBackClick = {},
|
||||
topBarConfig = walletTopBarConfig,
|
||||
|
|
@ -220,6 +237,12 @@ internal object WalletPreviewData {
|
|||
),
|
||||
),
|
||||
),
|
||||
notifications = persistentListOf(
|
||||
WalletNotification.UnreachableNetworks,
|
||||
WalletNotification.LikeTangemApp(onClick = {}),
|
||||
WalletNotification.NeedToBackup(onClick = {}),
|
||||
WalletNotification.ScanCard(onClick = {}),
|
||||
),
|
||||
onOrganizeTokensClick = {},
|
||||
)
|
||||
|
||||
|
|
@ -252,5 +275,12 @@ internal object WalletPreviewData {
|
|||
),
|
||||
),
|
||||
),
|
||||
notifications = persistentListOf(
|
||||
WalletNotification.UnreachableNetworks,
|
||||
WalletNotification.LikeTangemApp(onClick = {}),
|
||||
WalletNotification.NeedToBackup(onClick = {}),
|
||||
WalletNotification.ScanCard(onClick = {}),
|
||||
),
|
||||
buttons = manageButtons,
|
||||
)
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
|
||||
@Composable
|
||||
internal fun NetworkGroupItem(networkName: String, modifier: Modifier = Modifier) {
|
||||
|
|
@ -24,16 +26,33 @@ internal fun NetworkGroupItem(networkName: String, modifier: Modifier = Modifier
|
|||
}
|
||||
|
||||
@Composable
|
||||
internal fun DraggableNetworkGroupItem(networkName: String, modifier: Modifier = Modifier) {
|
||||
internal fun DraggableNetworkGroupItem(
|
||||
networkName: String,
|
||||
modifier: Modifier = Modifier,
|
||||
reorderableTokenListState: ReorderableLazyListState? = null,
|
||||
) {
|
||||
InternalNetworkGroupItem(
|
||||
modifier = modifier,
|
||||
networkName = networkName,
|
||||
endIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_group_drop_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size32)
|
||||
.let {
|
||||
if (reorderableTokenListState != null) {
|
||||
it.detectReorder(reorderableTokenListState)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_group_drop_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import com.tangem.feature.wallet.impl.R
|
|||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
|
||||
private const val DOTS = "•••"
|
||||
val TOKEN_ITEM_HEIGHT: Dp
|
||||
|
|
@ -46,7 +48,7 @@ internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) {
|
|||
when (state) {
|
||||
is TokenItemState.Content -> ContentTokenItem(state, modifier)
|
||||
is TokenItemState.Loading -> LoadingTokenItem(modifier)
|
||||
is TokenItemState.Draggable -> DraggableTokenItem(state, modifier)
|
||||
is TokenItemState.Draggable -> DraggableTokenItem(state, modifier, reorderableTokenListState = null)
|
||||
is TokenItemState.Unreachable -> UnreachableTokenItem(state, modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +73,11 @@ private fun ContentTokenItem(content: TokenItemState.Content, modifier: Modifier
|
|||
}
|
||||
|
||||
@Composable
|
||||
internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modifier = Modifier) {
|
||||
internal fun DraggableTokenItem(
|
||||
state: TokenItemState.Draggable,
|
||||
modifier: Modifier = Modifier,
|
||||
reorderableTokenListState: ReorderableLazyListState? = null,
|
||||
) {
|
||||
InternalTokenItem(
|
||||
modifier = modifier,
|
||||
name = state.name,
|
||||
|
|
@ -81,13 +87,25 @@ internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modif
|
|||
amount = state.fiatAmount,
|
||||
hasPending = false,
|
||||
options = { ref ->
|
||||
Icon(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.constrainAsOptionsItem(scope = this, ref),
|
||||
painter = painterResource(id = R.drawable.ic_drag_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
.size(TangemTheme.dimens.size32)
|
||||
.constrainAsOptionsItem(scope = this, ref)
|
||||
.let {
|
||||
if (reorderableTokenListState != null) {
|
||||
it.detectReorder(reorderableTokenListState)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_drag_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@ import androidx.compose.material.AppBarDefaults
|
|||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
|
|
@ -24,14 +21,19 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.BackgroundActionButton
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionConfig
|
||||
import com.tangem.core.ui.components.buttons.actions.BackgroundActionButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkGroupItem
|
||||
import com.tangem.feature.wallet.presentation.common.component.DraggableTokenItem
|
||||
import org.burnoutcrew.reorderable.ReorderableItem
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
|
||||
@Composable
|
||||
internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder, modifier: Modifier = Modifier) {
|
||||
|
|
@ -47,6 +49,7 @@ internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder, modifier: Mo
|
|||
modifier = Modifier.padding(paddingValues),
|
||||
listState = tokensListState,
|
||||
state = state.itemsState,
|
||||
dragConfig = state.dragConfig,
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
|
|
@ -57,28 +60,56 @@ internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder, modifier: Mo
|
|||
)
|
||||
}
|
||||
|
||||
// TODO: Fix list animations
|
||||
@Composable
|
||||
private fun TokenList(listState: LazyListState, state: OrganizeTokensListState, modifier: Modifier = Modifier) {
|
||||
private fun TokenList(
|
||||
listState: LazyListState,
|
||||
state: OrganizeTokensListState,
|
||||
dragConfig: OrganizeTokensStateHolder.DragConfig,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
val reorderableListState = rememberReorderableLazyListState(
|
||||
onMove = dragConfig.onItemDragged,
|
||||
listState = listState,
|
||||
canDragOver = dragConfig.canDragItemOver,
|
||||
onDragEnd = { _, _ -> dragConfig.onItemDragEnd() },
|
||||
)
|
||||
val items = state.items
|
||||
val lastItemIndex = items.lastIndex
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.reorderable(reorderableListState)
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
state = listState,
|
||||
state = reorderableListState.listState,
|
||||
contentPadding = PaddingValues(
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing92,
|
||||
),
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = state.items,
|
||||
items = items,
|
||||
key = { _, item -> item.id },
|
||||
) { index, item ->
|
||||
|
||||
val onDragStart = remember(item) {
|
||||
{ dragConfig.onDragStart(item) }
|
||||
}
|
||||
|
||||
DraggableItem(
|
||||
item = item,
|
||||
index = index,
|
||||
lastItemIndex = state.items.lastIndex,
|
||||
lastItemIndex = lastItemIndex,
|
||||
reorderableState = reorderableListState,
|
||||
onDragStart = onDragStart,
|
||||
)
|
||||
|
||||
if (item is DraggableItem.GroupPlaceholder) {
|
||||
// This item should be displayed in the list but remain invisible
|
||||
Box(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,19 +118,39 @@ private fun TokenList(listState: LazyListState, state: OrganizeTokensListState,
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun DraggableItem(index: Int, item: DraggableItem, lastItemIndex: Int) {
|
||||
val itemModifier = Modifier
|
||||
.clipFirstAndLastItems(index, lastItemIndex)
|
||||
private fun LazyItemScope.DraggableItem(
|
||||
index: Int,
|
||||
item: DraggableItem,
|
||||
lastItemIndex: Int,
|
||||
reorderableState: ReorderableLazyListState,
|
||||
onDragStart: () -> Unit,
|
||||
) {
|
||||
ReorderableItem(
|
||||
reorderableState = reorderableState,
|
||||
index = index,
|
||||
key = item.id,
|
||||
) { isDragging ->
|
||||
|
||||
when (item) {
|
||||
is DraggableItem.GroupHeader -> DraggableNetworkGroupItem(
|
||||
modifier = itemModifier,
|
||||
networkName = item.networkName,
|
||||
)
|
||||
is DraggableItem.Token -> DraggableTokenItem(
|
||||
modifier = itemModifier,
|
||||
state = item.tokenItemState,
|
||||
)
|
||||
if (isDragging) {
|
||||
onDragStart()
|
||||
}
|
||||
|
||||
val itemModifier = Modifier
|
||||
.clipFirstLastAndDraggingItems(index, lastItemIndex, isDragging)
|
||||
|
||||
when (item) {
|
||||
is DraggableItem.GroupHeader -> DraggableNetworkGroupItem(
|
||||
modifier = itemModifier,
|
||||
networkName = item.networkName,
|
||||
reorderableTokenListState = reorderableState,
|
||||
)
|
||||
is DraggableItem.Token -> DraggableTokenItem(
|
||||
modifier = itemModifier,
|
||||
state = item.tokenItemState,
|
||||
reorderableTokenListState = reorderableState,
|
||||
)
|
||||
is DraggableItem.GroupPlaceholder -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,16 +215,20 @@ private fun TopBar(
|
|||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
BackgroundActionButton(
|
||||
config = ActionConfig(
|
||||
text = stringResource(id = R.string.organize_tokens_sort_by_balance),
|
||||
iconResId = R.drawable.ic_sort_24,
|
||||
onClick = config.onSortByBalanceClick,
|
||||
),
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.organize_tokens_sort_by_balance),
|
||||
iconResId = R.drawable.ic_sort_24,
|
||||
onClick = config.onSortByBalanceClick,
|
||||
)
|
||||
BackgroundActionButton(
|
||||
config = ActionConfig(
|
||||
text = stringResource(id = R.string.organize_tokens_group),
|
||||
iconResId = R.drawable.ic_group_24,
|
||||
onClick = config.onGroupByNetworkClick,
|
||||
),
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.organize_tokens_group),
|
||||
iconResId = R.drawable.ic_group_24,
|
||||
onClick = config.onGroupByNetworkClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -201,29 +256,36 @@ private fun Actions(config: OrganizeTokensStateHolder.ActionsConfig, modifier: M
|
|||
}
|
||||
}
|
||||
|
||||
private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed {
|
||||
when (index) {
|
||||
0 -> {
|
||||
this
|
||||
.clip(
|
||||
private fun Modifier.clipFirstLastAndDraggingItems(index: Int, lastItemIndex: Int, isDragging: Boolean): Modifier =
|
||||
composed {
|
||||
when {
|
||||
isDragging -> {
|
||||
val elevation by animateDpAsState(
|
||||
targetValue = TangemTheme.dimens.elevation12,
|
||||
label = "dragging_item_shadow_elevation",
|
||||
)
|
||||
|
||||
this.shadow(elevation, shape = TangemTheme.shapes.roundedCornersXMedium)
|
||||
}
|
||||
index == 0 -> {
|
||||
this.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens.radius16,
|
||||
topEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
lastItemIndex -> {
|
||||
this
|
||||
.clip(
|
||||
}
|
||||
index == lastItemIndex -> {
|
||||
this.clip(
|
||||
RoundedCornerShape(
|
||||
bottomStart = TangemTheme.dimens.radius16,
|
||||
bottomEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ package com.tangem.feature.wallet.presentation.organizetokens
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
|
||||
internal data class OrganizeTokensStateHolder(
|
||||
val header: HeaderConfig,
|
||||
val itemsState: OrganizeTokensListState,
|
||||
val dragConfig: DragConfig,
|
||||
val actions: ActionsConfig,
|
||||
) {
|
||||
|
||||
|
|
@ -20,44 +22,79 @@ internal data class OrganizeTokensStateHolder(
|
|||
val onApplyClick: () -> Unit,
|
||||
val onCancelClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class DragConfig(
|
||||
val onItemDragged: (from: ItemPosition, to: ItemPosition) -> Unit,
|
||||
val canDragItemOver: (dragOver: ItemPosition, dragging: ItemPosition) -> Boolean,
|
||||
val onItemDragEnd: () -> Unit,
|
||||
val onDragStart: (item: DraggableItem) -> Unit,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal sealed class OrganizeTokensListState {
|
||||
abstract val items: ImmutableList<DraggableItem>
|
||||
internal sealed interface OrganizeTokensListState {
|
||||
val items: PersistentList<DraggableItem>
|
||||
|
||||
data class GroupedByNetwork(
|
||||
override val items: ImmutableList<DraggableItem>,
|
||||
) : OrganizeTokensListState()
|
||||
override val items: PersistentList<DraggableItem>,
|
||||
) : OrganizeTokensListState
|
||||
|
||||
data class Ungrouped(
|
||||
override val items: ImmutableList<DraggableItem.Token>,
|
||||
) : OrganizeTokensListState()
|
||||
override val items: PersistentList<DraggableItem.Token>,
|
||||
) : OrganizeTokensListState
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun updateItems(update: (List<DraggableItem>) -> List<DraggableItem>): OrganizeTokensListState {
|
||||
fun updateItems(update: (PersistentList<DraggableItem>) -> List<DraggableItem>): OrganizeTokensListState {
|
||||
val updatedItems = update(this.items).toPersistentList()
|
||||
|
||||
return when (this) {
|
||||
is GroupedByNetwork -> this.copy(items = updatedItems)
|
||||
is Ungrouped -> this.copy(items = updatedItems as ImmutableList<DraggableItem.Token>)
|
||||
is Ungrouped -> this.copy(items = updatedItems as PersistentList<DraggableItem.Token>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for the DND list items
|
||||
*
|
||||
* @property id ID of the item
|
||||
* */
|
||||
@Immutable
|
||||
internal sealed interface DraggableItem {
|
||||
val id: String
|
||||
|
||||
/**
|
||||
* Item for network group header.
|
||||
*
|
||||
* @property id ID of the network group
|
||||
* @property networkName network group name
|
||||
* */
|
||||
data class GroupHeader(
|
||||
override val id: String,
|
||||
val networkName: String,
|
||||
) : DraggableItem
|
||||
|
||||
/**
|
||||
* Item for token.
|
||||
*
|
||||
* @property tokenItemState state of the token item
|
||||
* @property groupId ID of the network group which contains this token
|
||||
* @property id ID of the token
|
||||
* */
|
||||
data class Token(
|
||||
val tokenItemState: TokenItemState.Draggable,
|
||||
val groupId: String,
|
||||
) : DraggableItem {
|
||||
override val id: String = tokenItemState.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper item used to detect possible positions where a network group can be placed.
|
||||
* Used only on [OrganizeTokensListState.GroupedByNetwork] and placed between network groups.
|
||||
*
|
||||
* @property id ID of the placeholder
|
||||
* */
|
||||
data class GroupPlaceholder(
|
||||
override val id: String,
|
||||
) : DraggableItem
|
||||
}
|
||||
|
|
@ -5,10 +5,16 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder.DragConfig
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder.HeaderConfig
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.checkCanMoveHeaderOver
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.checkCanMoveTokenOver
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.findItemsToMove
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.moveItem
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
|
@ -16,6 +22,10 @@ import kotlin.properties.Delegates
|
|||
@HiltViewModel
|
||||
internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
// TODO: Move to domain
|
||||
@Volatile
|
||||
private var groupIdToTokens: Map<String, List<DraggableItem.Token>>? = null
|
||||
|
||||
var router: InnerWalletRouter by Delegates.notNull()
|
||||
|
||||
var uiState: OrganizeTokensStateHolder by mutableStateOf(getInitialState())
|
||||
|
|
@ -25,6 +35,12 @@ internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() {
|
|||
itemsState = OrganizeTokensListState.Ungrouped(
|
||||
items = WalletPreviewData.draggableTokens,
|
||||
),
|
||||
dragConfig = DragConfig(
|
||||
onItemDragged = this::moveItem,
|
||||
canDragItemOver = this::checkCanMoveItemOver,
|
||||
onItemDragEnd = this::expandGroups,
|
||||
onDragStart = this::collapseGroup,
|
||||
),
|
||||
header = HeaderConfig(
|
||||
onSortByBalanceClick = { /* no-op */ },
|
||||
onGroupByNetworkClick = this::toggleTokensByNetworkGrouping,
|
||||
|
|
@ -43,4 +59,69 @@ internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() {
|
|||
|
||||
uiState = uiState.copy(itemsState = newListState)
|
||||
}
|
||||
|
||||
private fun checkCanMoveItemOver(moveOverItemPosition: ItemPosition, movedItemPosition: ItemPosition): Boolean {
|
||||
val items = (uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork)
|
||||
?.items
|
||||
?: return true // If ungrouped then item can be moved anywhere
|
||||
|
||||
val (moveOverItem, movedItem) = items.findItemsToMove(moveOverItemPosition.key, movedItemPosition.key)
|
||||
|
||||
if (moveOverItem == null || movedItem == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return when (movedItem) {
|
||||
is DraggableItem.GroupHeader -> checkCanMoveHeaderOver(moveOverItemPosition, moveOverItem, items.lastIndex)
|
||||
is DraggableItem.Token -> checkCanMoveTokenOver(movedItem, moveOverItem)
|
||||
is DraggableItem.GroupPlaceholder -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun collapseGroup(item: DraggableItem) {
|
||||
if (!groupIdToTokens.isNullOrEmpty() || item is DraggableItem.Token) return
|
||||
|
||||
val itemsState = uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork ?: return
|
||||
groupIdToTokens = itemsState.items
|
||||
.asSequence()
|
||||
.filterIsInstance<DraggableItem.Token>()
|
||||
.groupBy(DraggableItem.Token::groupId)
|
||||
|
||||
uiState = uiState.copy(
|
||||
itemsState = itemsState.updateItems { items ->
|
||||
items.filterNot { it is DraggableItem.Token && it.groupId == item.id }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun expandGroups() {
|
||||
if (groupIdToTokens.isNullOrEmpty()) return
|
||||
|
||||
val itemsState = uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork ?: return
|
||||
val currentGroups = itemsState.items.filterIsInstance<DraggableItem.GroupHeader>()
|
||||
val newItems = currentGroups
|
||||
.flatMapIndexed { index, group ->
|
||||
mutableListOf<DraggableItem>(group)
|
||||
.also { it.addAll(groupIdToTokens?.get(group.id).orEmpty()) }
|
||||
.also {
|
||||
if (index != currentGroups.lastIndex) {
|
||||
it.add(DraggableItem.GroupPlaceholder(id = "group_divider_$index"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiState = uiState.copy(
|
||||
itemsState = itemsState.updateItems { newItems },
|
||||
)
|
||||
|
||||
groupIdToTokens = null
|
||||
}
|
||||
|
||||
private fun moveItem(from: ItemPosition, to: ItemPosition) {
|
||||
uiState = uiState.copy(
|
||||
itemsState = uiState.itemsState.updateItems {
|
||||
it.moveItem(from.index, to.index)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.feature.wallet.presentation.organizetokens.utils
|
||||
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
|
||||
internal fun List<DraggableItem>.findItemsToMove(
|
||||
moveOverItemKey: Any?,
|
||||
movedItemKey: Any?,
|
||||
): Pair<DraggableItem?, DraggableItem?> {
|
||||
var moveOverItem: DraggableItem? = null
|
||||
var movedItem: DraggableItem? = null
|
||||
|
||||
for (item in this) {
|
||||
if (item.id == moveOverItemKey) {
|
||||
moveOverItem = item
|
||||
}
|
||||
if (item.id == movedItemKey) {
|
||||
movedItem = item
|
||||
}
|
||||
if (moveOverItem != null && movedItem != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(moveOverItem, movedItem)
|
||||
}
|
||||
|
||||
internal fun checkCanMoveHeaderOver(
|
||||
moveOverItemPosition: ItemPosition,
|
||||
moveOverItem: DraggableItem,
|
||||
lastItemIndex: Int,
|
||||
): Boolean {
|
||||
return when {
|
||||
moveOverItemPosition.index == 0 -> true
|
||||
moveOverItemPosition.index == lastItemIndex -> true
|
||||
moveOverItem is DraggableItem.GroupPlaceholder -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun checkCanMoveTokenOver(item: DraggableItem.Token, moveOverItem: DraggableItem): Boolean {
|
||||
return when (moveOverItem) {
|
||||
is DraggableItem.GroupHeader -> false // Token item can not be moved to group item
|
||||
is DraggableItem.Token -> item.groupId == moveOverItem.groupId // Token item can not be moved over its group
|
||||
is DraggableItem.GroupPlaceholder -> false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun PersistentList<DraggableItem>.moveItem(fromIndex: Int, toIndex: Int): PersistentList<DraggableItem> {
|
||||
val fromItem = this[fromIndex]
|
||||
return this
|
||||
.removeAt(fromIndex)
|
||||
.add(toIndex, fromItem)
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state
|
||||
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionConfig
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
||||
/**
|
||||
* Wallet manage button state
|
||||
*
|
||||
* @param config action config
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class WalletManageButton(val config: ActionConfig) {
|
||||
|
||||
/**
|
||||
* Buy
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class Buy(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Buy",
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Send
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class Send(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Send",
|
||||
iconResId = R.drawable.ic_arrow_up_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Receive
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class Receive(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Receive",
|
||||
iconResId = R.drawable.ic_arrow_down_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Exchange
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class Exchange(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Exchange",
|
||||
iconResId = R.drawable.ic_exchange_vertical_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Copy address
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class CopyAddress(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Copy address",
|
||||
iconResId = R.drawable.ic_copy_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state
|
||||
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* Wallet manage buttons
|
||||
*
|
||||
* @param buttons manage buttons
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun WalletManageButtons(buttons: ImmutableList<WalletManageButton>) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.horizontalScroll(state = rememberScrollState())
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
buttons.forEach { button ->
|
||||
key(button.config.text) {
|
||||
ActionButton(config = button.config)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_WalletManageButtons_Light(
|
||||
@PreviewParameter(WalletManageButtonProvider::class) buttons: ImmutableList<WalletManageButton>,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
WalletManageButtons(buttons = buttons)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_WalletManageButtons_Dark(
|
||||
@PreviewParameter(WalletManageButtonProvider::class) buttons: ImmutableList<WalletManageButton>,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
WalletManageButtons(buttons = buttons)
|
||||
}
|
||||
}
|
||||
|
||||
private class WalletManageButtonProvider : CollectionPreviewParameterProvider<ImmutableList<WalletManageButton>>(
|
||||
collection = listOf(WalletPreviewData.manageButtons),
|
||||
)
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state
|
||||
|
||||
import com.tangem.core.ui.components.notifications.NotificationState
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
||||
/**
|
||||
* Wallet notification component state
|
||||
*
|
||||
* @property state state
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class WalletNotification(open val state: NotificationState) {
|
||||
|
||||
/**
|
||||
* "Backup the card" notification
|
||||
*
|
||||
* @property onClick lambda be invoked when notification is clicked
|
||||
*/
|
||||
data class NeedToBackup(val onClick: () -> Unit) : WalletNotification(
|
||||
state = NotificationState.Action(
|
||||
title = "Backup your card",
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
onClick = onClick,
|
||||
tint = TangemColorPalette.Amaranth,
|
||||
),
|
||||
)
|
||||
|
||||
/** "Unreachable networks" notification */
|
||||
object UnreachableNetworks : WalletNotification(
|
||||
state = NotificationState.Simple(
|
||||
title = "Some networks are unreachable",
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
tint = null,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* "Like Tangem App" notification
|
||||
*
|
||||
* @property onClick lambda be invoked when notification is clicked
|
||||
*/
|
||||
data class LikeTangemApp(val onClick: () -> Unit) : WalletNotification(
|
||||
state = NotificationState.Action(
|
||||
title = "Like Tangem App?",
|
||||
iconResId = R.drawable.ic_star_24,
|
||||
onClick = onClick,
|
||||
tint = TangemColorPalette.Tangerine,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* "Scan the card" notification
|
||||
*
|
||||
* @property onClick lambda be invoked when notification is clicked
|
||||
*/
|
||||
data class ScanCard(val onClick: () -> Unit) : WalletNotification(
|
||||
state = NotificationState.Action(
|
||||
title = "Scan your card to continue",
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
* @property selectedWallet selected wallet
|
||||
* @property wallets list of wallets states
|
||||
* @property contentItems content items
|
||||
* @property notifications notifications
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -19,16 +20,19 @@ internal sealed class WalletStateHolder(
|
|||
open val selectedWallet: WalletCardState,
|
||||
open val wallets: ImmutableList<WalletCardState>,
|
||||
open val contentItems: ImmutableList<WalletContentItemState>,
|
||||
open val notifications: ImmutableList<WalletNotification>,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Multi currency wallet content state
|
||||
*
|
||||
* @property onBackClick lambda be invoked when back button is clicked
|
||||
* @property topBarConfig top bar config
|
||||
* @property selectedWallet selected wallet
|
||||
* @property wallets list of wallets states
|
||||
* @property contentItems content items
|
||||
* @property onBackClick lambda be invoked when back button is clicked
|
||||
* @property topBarConfig top bar config
|
||||
* @property selectedWallet selected wallet
|
||||
* @property wallets list of wallets states
|
||||
* @property contentItems content items
|
||||
* @property notifications notifications
|
||||
* @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked
|
||||
*/
|
||||
data class MultiCurrencyContent(
|
||||
override val onBackClick: () -> Unit,
|
||||
|
|
@ -36,8 +40,9 @@ internal sealed class WalletStateHolder(
|
|||
override val selectedWallet: WalletCardState,
|
||||
override val wallets: ImmutableList<WalletCardState>,
|
||||
override val contentItems: ImmutableList<WalletContentItemState.MultiCurrencyItem>,
|
||||
override val notifications: ImmutableList<WalletNotification>,
|
||||
val onOrganizeTokensClick: () -> Unit,
|
||||
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems)
|
||||
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems, notifications)
|
||||
|
||||
/**
|
||||
* Single currency wallet content state
|
||||
|
|
@ -47,6 +52,8 @@ internal sealed class WalletStateHolder(
|
|||
* @property selectedWallet selected wallet
|
||||
* @property wallets list of wallets states
|
||||
* @property contentItems content items
|
||||
* @property notifications notifications
|
||||
* @property buttons manage buttons
|
||||
*/
|
||||
data class SingleCurrencyContent(
|
||||
override val onBackClick: () -> Unit,
|
||||
|
|
@ -54,5 +61,7 @@ internal sealed class WalletStateHolder(
|
|||
override val selectedWallet: WalletCardState,
|
||||
override val wallets: ImmutableList<WalletCardState>,
|
||||
override val contentItems: ImmutableList<WalletContentItemState.SingleCurrencyItem>,
|
||||
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems)
|
||||
override val notifications: ImmutableList<WalletNotification>,
|
||||
val buttons: ImmutableList<WalletManageButton>,
|
||||
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems, notifications)
|
||||
}
|
||||
|
|
@ -1,28 +1,26 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.RoundedActionButton
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionConfig
|
||||
import com.tangem.core.ui.components.buttons.actions.RoundedActionButton
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.transactions.Transaction
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
|
@ -30,9 +28,11 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
|||
import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem
|
||||
import com.tangem.feature.wallet.presentation.common.component.TokenItem
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButtons
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletCardsList
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletTopBar
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.decorations.walletContentItemDecoration
|
||||
|
||||
/**
|
||||
* Wallet screen
|
||||
|
|
@ -49,15 +49,29 @@ internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifie
|
|||
topBar = { WalletTopBar(config = state.topBarConfig) },
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
) { scaffoldPaddings ->
|
||||
val lastContentItemIndex = remember(state.contentItems) { state.contentItems.lastIndex }
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.padding(scaffoldPaddings)
|
||||
.padding(paddingValues = scaffoldPaddings)
|
||||
.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
item { WalletCardsList(wallets = state.wallets) }
|
||||
item {
|
||||
WalletCardsList(
|
||||
wallets = state.wallets,
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
)
|
||||
}
|
||||
|
||||
if (state is WalletStateHolder.SingleCurrencyContent) {
|
||||
item { WalletManageButtons(buttons = state.buttons) }
|
||||
}
|
||||
|
||||
items(items = state.notifications, itemContent = { Notification(state = it.state) })
|
||||
|
||||
itemsIndexed(
|
||||
items = state.contentItems,
|
||||
|
|
@ -70,36 +84,19 @@ internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifie
|
|||
is WalletContentItemState.SingleCurrencyItem.Transaction -> index
|
||||
}
|
||||
},
|
||||
) { index, item ->
|
||||
val itemModifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.clipFirstAndLastItems(index, lastContentItemIndex)
|
||||
|
||||
when (item) {
|
||||
is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> {
|
||||
NetworkGroupItem(networkName = item.networkName, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.MultiCurrencyItem.Token -> {
|
||||
TokenItem(state = item.state, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.Title -> {
|
||||
SingleCurrencyTitle(config = item, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle -> {
|
||||
TransactionGroupTitle(config = item, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.Transaction -> {
|
||||
Transaction(state = item.state, modifier = itemModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
itemContent = { index, item ->
|
||||
ContentItem(currentIndex = index, lastIndex = state.contentItems.lastIndex, item = item)
|
||||
},
|
||||
)
|
||||
|
||||
if (state is WalletStateHolder.MultiCurrencyContent) {
|
||||
item {
|
||||
RoundedActionButton(
|
||||
text = stringResource(id = R.string.organize_tokens_title),
|
||||
iconResId = R.drawable.ic_filter_24,
|
||||
onClick = state.onOrganizeTokensClick,
|
||||
config = ActionConfig(
|
||||
text = stringResource(id = R.string.organize_tokens_title),
|
||||
iconResId = R.drawable.ic_filter_24,
|
||||
onClick = state.onOrganizeTokensClick,
|
||||
),
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing14),
|
||||
)
|
||||
}
|
||||
|
|
@ -108,6 +105,29 @@ internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifie
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentItem(currentIndex: Int, lastIndex: Int, item: WalletContentItemState) {
|
||||
val itemModifier = Modifier.walletContentItemDecoration(currentIndex, lastIndex)
|
||||
|
||||
when (item) {
|
||||
is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> {
|
||||
NetworkGroupItem(networkName = item.networkName, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.MultiCurrencyItem.Token -> {
|
||||
TokenItem(state = item.state, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.Title -> {
|
||||
SingleCurrencyTitle(config = item, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle -> {
|
||||
TransactionGroupTitle(config = item, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.Transaction -> {
|
||||
Transaction(state = item.state, modifier = itemModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SingleCurrencyTitle(
|
||||
config: WalletContentItemState.SingleCurrencyItem.Title,
|
||||
|
|
@ -166,31 +186,6 @@ private fun TransactionGroupTitle(
|
|||
)
|
||||
}
|
||||
|
||||
private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed {
|
||||
when (index) {
|
||||
0 -> {
|
||||
this
|
||||
.padding(top = TangemTheme.dimens.spacing14)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens.radius16,
|
||||
topEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
lastItemIndex -> {
|
||||
this
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
bottomStart = TangemTheme.dimens.radius16,
|
||||
bottomEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -23,21 +23,21 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
/**
|
||||
* Wallets list
|
||||
*
|
||||
* @param wallets list of wallet state
|
||||
* @param wallets list of wallet state
|
||||
* @param modifier modifier
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun WalletCardsList(wallets: ImmutableList<WalletCardState>) {
|
||||
internal fun WalletCardsList(wallets: ImmutableList<WalletCardState>, modifier: Modifier = Modifier) {
|
||||
val horizontalCardPadding = TangemTheme.dimens.spacing16
|
||||
val itemWidth = LocalConfiguration.current.screenWidthDp.dp - horizontalCardPadding * 2
|
||||
|
||||
val lazyListState = rememberLazyListState()
|
||||
LazyRow(
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
modifier = modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
state = lazyListState,
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.decorations
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.clip
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal fun Modifier.walletContentItemDecoration(currentIndex: Int, lastItemIndex: Int): Modifier = composed {
|
||||
when (currentIndex) {
|
||||
0 -> {
|
||||
this
|
||||
.padding(top = TangemTheme.dimens.spacing14)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens.radius16,
|
||||
topEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
lastItemIndex -> {
|
||||
this
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
bottomStart = TangemTheme.dimens.radius16,
|
||||
bottomEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ compose-constraint = "1.0.1"
|
|||
compose-navigation = "2.5.3"
|
||||
compose-accompanist = "0.30.1"
|
||||
compose-paging = "1.0.0-alpha18"
|
||||
compose-reorderable = "0.9.6"
|
||||
# endregion Compose
|
||||
|
||||
# region Other libraries
|
||||
|
|
@ -77,7 +78,7 @@ walletConnectWeb3 = "1.8.0"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-247"
|
||||
tangemBlockchainSdk = "develop-250"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-266"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds
|
||||
|
|
@ -147,6 +148,7 @@ compose-accompanist-appCompatTheme = { module = "com.google.accompanist:accompan
|
|||
compose-accompanist-systemUiController = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "compose-accompanist" }
|
||||
compose-accompanist-webView = { module = "com.google.accompanist:accompanist-webview", version.ref = "compose-accompanist" }
|
||||
compose-paging = { module = "androidx.paging:paging-compose", version.ref = "compose-paging" }
|
||||
compose-reorderable = { module = "org.burnoutcrew.composereorderable:reorderable", version.ref = "compose-reorderable" }
|
||||
# endregion Compose
|
||||
|
||||
# region Firebase
|
||||
|
|
|
|||
|
|
@ -1,836 +0,0 @@
|
|||
build:
|
||||
maxIssues: 0
|
||||
|
||||
config:
|
||||
validation: true
|
||||
warningsAsErrors: false
|
||||
|
||||
processors:
|
||||
active: true
|
||||
|
||||
console-reports:
|
||||
active: true
|
||||
exclude:
|
||||
- 'ProjectStatisticsReport'
|
||||
- 'ComplexityReport'
|
||||
- 'NotificationReport'
|
||||
- 'FindingsReport'
|
||||
- 'FileBasedFindingsReport'
|
||||
# - 'LiteFindingsReport'
|
||||
|
||||
output-reports:
|
||||
active: true
|
||||
exclude:
|
||||
- 'HtmlOutputReport'
|
||||
# - 'TxtOutputReport'
|
||||
- 'XmlOutputReport'
|
||||
- 'SarifOutputReport'
|
||||
- 'MdOutputReport'
|
||||
|
||||
comments:
|
||||
active: false
|
||||
AbsentOrWrongFileLicense:
|
||||
active: false
|
||||
licenseTemplateFile: 'license.template'
|
||||
licenseTemplateIsRegex: false
|
||||
CommentOverPrivateFunction:
|
||||
active: false
|
||||
CommentOverPrivateProperty:
|
||||
active: false
|
||||
DeprecatedBlockTag:
|
||||
active: false
|
||||
EndOfSentenceFormat:
|
||||
active: false
|
||||
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
|
||||
KDocReferencesNonPublicProperty:
|
||||
active: false
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
OutdatedDocumentation:
|
||||
active: false
|
||||
matchTypeParameters: true
|
||||
matchDeclarationsOrder: true
|
||||
allowParamOnConstructorProperties: false
|
||||
UndocumentedPublicClass:
|
||||
active: false
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
searchInNestedClass: true
|
||||
searchInInnerClass: true
|
||||
searchInInnerObject: true
|
||||
searchInInnerInterface: true
|
||||
UndocumentedPublicFunction:
|
||||
active: false
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
UndocumentedPublicProperty:
|
||||
active: false
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
|
||||
complexity:
|
||||
active: true
|
||||
CyclomaticComplexMethod:
|
||||
active: true
|
||||
ComplexCondition:
|
||||
active: true
|
||||
threshold: 4
|
||||
ComplexInterface:
|
||||
active: false
|
||||
threshold: 10
|
||||
includeStaticDeclarations: false
|
||||
includePrivateDeclarations: false
|
||||
LabeledExpression:
|
||||
active: false
|
||||
ignoredLabels: [ ]
|
||||
LargeClass:
|
||||
active: true
|
||||
threshold: 300
|
||||
LongMethod:
|
||||
active: true
|
||||
threshold: 70
|
||||
LongParameterList:
|
||||
active: true
|
||||
functionThreshold: 6
|
||||
constructorThreshold: 7
|
||||
ignoreDefaultParameters: true
|
||||
ignoreDataClasses: true
|
||||
ignoreAnnotated: [ 'Provides' ]
|
||||
MethodOverloading:
|
||||
active: true
|
||||
threshold: 6
|
||||
NamedArguments:
|
||||
active: true
|
||||
threshold: 3
|
||||
ignoreArgumentsMatchingNames: false
|
||||
NestedBlockDepth:
|
||||
active: true
|
||||
threshold: 5
|
||||
NestedScopeFunctions:
|
||||
active: false
|
||||
threshold: 1
|
||||
functions:
|
||||
- 'kotlin.apply'
|
||||
- 'kotlin.run'
|
||||
- 'kotlin.with'
|
||||
- 'kotlin.let'
|
||||
- 'kotlin.also'
|
||||
ReplaceSafeCallChainWithRun:
|
||||
active: true
|
||||
StringLiteralDuplication:
|
||||
active: false
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
threshold: 3
|
||||
ignoreAnnotation: true
|
||||
excludeStringsWithLessThan5Characters: true
|
||||
ignoreStringsRegex: '$^'
|
||||
TooManyFunctions:
|
||||
active: true
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
thresholdInFiles: 20
|
||||
thresholdInClasses: 20
|
||||
thresholdInInterfaces: 20
|
||||
thresholdInObjects: 20
|
||||
thresholdInEnums: 20
|
||||
ignoreDeprecated: false
|
||||
ignorePrivate: true
|
||||
ignoreOverridden: false
|
||||
|
||||
coroutines:
|
||||
active: true
|
||||
GlobalCoroutineUsage:
|
||||
active: true
|
||||
InjectDispatcher:
|
||||
active: false #TODO
|
||||
dispatcherNames:
|
||||
- 'IO'
|
||||
- 'Default'
|
||||
- 'Unconfined'
|
||||
RedundantSuspendModifier:
|
||||
active: true
|
||||
SleepInsteadOfDelay:
|
||||
active: true
|
||||
SuspendFunWithCoroutineScopeReceiver:
|
||||
active: false
|
||||
SuspendFunWithFlowReturnType:
|
||||
active: true
|
||||
|
||||
empty-blocks:
|
||||
active: true
|
||||
EmptyCatchBlock:
|
||||
active: true
|
||||
allowedExceptionNameRegex: '_|(ignore|expected).*'
|
||||
EmptyClassBlock:
|
||||
active: true
|
||||
EmptyDefaultConstructor:
|
||||
active: true
|
||||
EmptyDoWhileBlock:
|
||||
active: true
|
||||
EmptyElseBlock:
|
||||
active: true
|
||||
EmptyFinallyBlock:
|
||||
active: true
|
||||
EmptyForBlock:
|
||||
active: true
|
||||
EmptyFunctionBlock:
|
||||
active: true
|
||||
ignoreOverridden: true
|
||||
EmptyIfBlock:
|
||||
active: true
|
||||
EmptyInitBlock:
|
||||
active: true
|
||||
EmptyKtFile:
|
||||
active: true
|
||||
EmptySecondaryConstructor:
|
||||
active: true
|
||||
EmptyTryBlock:
|
||||
active: true
|
||||
EmptyWhenBlock:
|
||||
active: true
|
||||
EmptyWhileBlock:
|
||||
active: true
|
||||
|
||||
exceptions:
|
||||
active: true
|
||||
ExceptionRaisedInUnexpectedLocation:
|
||||
active: true
|
||||
methodNames:
|
||||
- 'equals'
|
||||
- 'finalize'
|
||||
- 'hashCode'
|
||||
- 'toString'
|
||||
InstanceOfCheckForException:
|
||||
active: false
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
NotImplementedDeclaration:
|
||||
active: false
|
||||
ObjectExtendsThrowable:
|
||||
active: true
|
||||
PrintStackTrace:
|
||||
active: true
|
||||
RethrowCaughtException:
|
||||
active: true
|
||||
ReturnFromFinally:
|
||||
active: true
|
||||
ignoreLabeled: false
|
||||
SwallowedException:
|
||||
active: false
|
||||
ignoredExceptionTypes:
|
||||
- 'InterruptedException'
|
||||
- 'MalformedURLException'
|
||||
- 'NumberFormatException'
|
||||
- 'ParseException'
|
||||
allowedExceptionNameRegex: '_|(ignore|expected).*'
|
||||
ThrowingExceptionFromFinally:
|
||||
active: true
|
||||
ThrowingExceptionInMain:
|
||||
active: false
|
||||
ThrowingExceptionsWithoutMessageOrCause:
|
||||
active: true
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
exceptions:
|
||||
- 'ArrayIndexOutOfBoundsException'
|
||||
- 'Exception'
|
||||
- 'IllegalArgumentException'
|
||||
- 'IllegalMonitorStateException'
|
||||
- 'IllegalStateException'
|
||||
- 'IndexOutOfBoundsException'
|
||||
- 'NullPointerException'
|
||||
- 'RuntimeException'
|
||||
- 'Throwable'
|
||||
ThrowingNewInstanceOfSameException:
|
||||
active: true
|
||||
TooGenericExceptionCaught:
|
||||
active: false
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
exceptionNames:
|
||||
- 'ArrayIndexOutOfBoundsException'
|
||||
- 'Error'
|
||||
- 'Exception'
|
||||
- 'IllegalMonitorStateException'
|
||||
- 'IndexOutOfBoundsException'
|
||||
- 'NullPointerException'
|
||||
- 'RuntimeException'
|
||||
- 'Throwable'
|
||||
allowedExceptionNameRegex: '_|(ignore|expected).*'
|
||||
TooGenericExceptionThrown:
|
||||
active: true
|
||||
exceptionNames:
|
||||
- 'Error'
|
||||
- 'Exception'
|
||||
- 'RuntimeException'
|
||||
- 'Throwable'
|
||||
|
||||
naming:
|
||||
active: true
|
||||
BooleanPropertyNaming:
|
||||
active: true
|
||||
allowedPattern: '^(is|has|are)'
|
||||
ignoreOverridden: true
|
||||
ClassNaming:
|
||||
active: true
|
||||
classPattern: '[A-Z][a-zA-Z0-9]*'
|
||||
ConstructorParameterNaming:
|
||||
active: true
|
||||
parameterPattern: '[a-z][A-Za-z0-9]*'
|
||||
privateParameterPattern: '[a-z][A-Za-z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
EnumNaming:
|
||||
active: true
|
||||
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
|
||||
ForbiddenClassName:
|
||||
active: false
|
||||
forbiddenName: [ ]
|
||||
FunctionMaxLength:
|
||||
active: false
|
||||
maximumFunctionNameLength: 30
|
||||
FunctionMinLength:
|
||||
active: false
|
||||
minimumFunctionNameLength: 3
|
||||
FunctionNaming:
|
||||
active: true
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
functionPattern: '[a-z][a-zA-Z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
ignoreAnnotated: [ 'Composable' ]
|
||||
FunctionParameterNaming:
|
||||
active: true
|
||||
parameterPattern: '[a-z][A-Za-z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
InvalidPackageDeclaration:
|
||||
active: true
|
||||
rootPackage: ''
|
||||
requireRootInDeclaration: false
|
||||
LambdaParameterNaming:
|
||||
active: false
|
||||
parameterPattern: '[a-z][A-Za-z0-9]*|_'
|
||||
MatchingDeclarationName: # Same as FileName
|
||||
active: false
|
||||
mustBeFirst: true
|
||||
MemberNameEqualsClassName:
|
||||
active: false
|
||||
ignoreOverridden: true
|
||||
NoNameShadowing:
|
||||
active: true
|
||||
NonBooleanPropertyPrefixedWithIs:
|
||||
active: true
|
||||
ObjectPropertyNaming:
|
||||
active: true
|
||||
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
|
||||
PackageNaming:
|
||||
active: true
|
||||
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
|
||||
TopLevelPropertyNaming:
|
||||
active: true
|
||||
constantPattern: '[A-Z][_A-Z0-9]*'
|
||||
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
||||
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
|
||||
VariableMaxLength:
|
||||
active: false
|
||||
maximumVariableNameLength: 50
|
||||
VariableMinLength:
|
||||
active: false
|
||||
minimumVariableNameLength: 3
|
||||
VariableNaming:
|
||||
active: true
|
||||
variablePattern: '[a-z][A-Za-z0-9]*'
|
||||
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
|
||||
excludeClassPattern: '$^'
|
||||
ignoreOverridden: true
|
||||
|
||||
performance:
|
||||
active: true
|
||||
ArrayPrimitive:
|
||||
active: true
|
||||
CouldBeSequence:
|
||||
active: false
|
||||
threshold: 3
|
||||
ForEachOnRange:
|
||||
active: true
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
SpreadOperator:
|
||||
active: false
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
UnnecessaryTemporaryInstantiation:
|
||||
active: true
|
||||
|
||||
potential-bugs:
|
||||
active: true
|
||||
AvoidReferentialEquality:
|
||||
active: true
|
||||
forbiddenTypePatterns:
|
||||
- 'kotlin.String'
|
||||
CastToNullableType:
|
||||
active: true
|
||||
Deprecation:
|
||||
active: false
|
||||
DontDowncastCollectionTypes:
|
||||
active: true
|
||||
DoubleMutabilityForCollection:
|
||||
active: true
|
||||
mutableTypes:
|
||||
- 'kotlin.collections.MutableList'
|
||||
- 'kotlin.collections.MutableMap'
|
||||
- 'kotlin.collections.MutableSet'
|
||||
- 'java.util.ArrayList'
|
||||
- 'java.util.LinkedHashSet'
|
||||
- 'java.util.HashSet'
|
||||
- 'java.util.LinkedHashMap'
|
||||
- 'java.util.HashMap'
|
||||
ElseCaseInsteadOfExhaustiveWhen:
|
||||
active: false
|
||||
EqualsAlwaysReturnsTrueOrFalse:
|
||||
active: true
|
||||
EqualsWithHashCodeExist:
|
||||
active: true
|
||||
ExitOutsideMain:
|
||||
active: false
|
||||
ExplicitGarbageCollectionCall:
|
||||
active: true
|
||||
HasPlatformType:
|
||||
active: true
|
||||
IgnoredReturnValue:
|
||||
active: true
|
||||
returnValueAnnotations:
|
||||
- '*.CheckResult'
|
||||
- '*.CheckReturnValue'
|
||||
ignoreReturnValueAnnotations:
|
||||
- '*.CanIgnoreReturnValue'
|
||||
ignoreFunctionCall: [ ]
|
||||
ImplicitDefaultLocale:
|
||||
active: true
|
||||
ImplicitUnitReturnType:
|
||||
active: false
|
||||
allowExplicitReturnType: true
|
||||
InvalidRange:
|
||||
active: true
|
||||
IteratorHasNextCallsNextMethod:
|
||||
active: true
|
||||
IteratorNotThrowingNoSuchElementException:
|
||||
active: true
|
||||
LateinitUsage:
|
||||
active: false
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
ignoreAnnotated: [ 'Inject' ]
|
||||
ignoreOnClassesPattern: ''
|
||||
MapGetWithNotNullAssertionOperator:
|
||||
active: true
|
||||
MissingPackageDeclaration:
|
||||
active: true
|
||||
excludes: [ '**/*.kts' ]
|
||||
NullCheckOnMutableProperty:
|
||||
active: true
|
||||
NullableToStringCall:
|
||||
active: true
|
||||
UnconditionalJumpStatementInLoop:
|
||||
active: true
|
||||
UnnecessaryNotNullOperator:
|
||||
active: true
|
||||
UnnecessarySafeCall:
|
||||
active: true
|
||||
UnreachableCatchBlock:
|
||||
active: true
|
||||
UnreachableCode:
|
||||
active: true
|
||||
UnsafeCallOnNullableType:
|
||||
active: true
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
|
||||
UnsafeCast:
|
||||
active: true
|
||||
UnusedUnaryOperator:
|
||||
active: true
|
||||
UselessPostfixExpression:
|
||||
active: true
|
||||
WrongEqualsTypeParameter:
|
||||
active: true
|
||||
|
||||
style:
|
||||
active: true
|
||||
CanBeNonNullable:
|
||||
active: false
|
||||
CascadingCallWrapping:
|
||||
active: false
|
||||
includeElvis: true
|
||||
ClassOrdering:
|
||||
active: true
|
||||
CollapsibleIfStatements:
|
||||
active: true
|
||||
DataClassContainsFunctions:
|
||||
active: false
|
||||
conversionFunctionPrefix:
|
||||
- 'to'
|
||||
DataClassShouldBeImmutable:
|
||||
active: false
|
||||
DestructuringDeclarationWithTooManyEntries:
|
||||
active: true
|
||||
maxDestructuringEntries: 3
|
||||
EqualsNullCall:
|
||||
active: false
|
||||
EqualsOnSignatureLine:
|
||||
active: true
|
||||
ExplicitCollectionElementAccessMethod:
|
||||
active: true
|
||||
ExplicitItLambdaParameter:
|
||||
active: true
|
||||
ExpressionBodySyntax:
|
||||
active: false
|
||||
includeLineWrapping: true
|
||||
ForbiddenComment:
|
||||
active: false
|
||||
values:
|
||||
- 'FIXME:'
|
||||
- 'STOPSHIP:'
|
||||
- 'TODO:'
|
||||
allowedPatterns: ''
|
||||
customMessage: ''
|
||||
ForbiddenImport:
|
||||
active: false
|
||||
imports: [ ]
|
||||
forbiddenPatterns: ''
|
||||
ForbiddenMethodCall:
|
||||
active: false
|
||||
methods:
|
||||
- reason: 'print does not allow you to configure the output stream. Use a logger instead.'
|
||||
value: 'kotlin.io.print'
|
||||
- reason: 'println does not allow you to configure the output stream. Use a logger instead.'
|
||||
value: 'kotlin.io.println'
|
||||
ForbiddenSuppress:
|
||||
active: false
|
||||
rules: [ ]
|
||||
ForbiddenVoid:
|
||||
active: false
|
||||
ignoreOverridden: false
|
||||
ignoreUsageInGenerics: false
|
||||
FunctionOnlyReturningConstant:
|
||||
active: true
|
||||
ignoreOverridableFunction: true
|
||||
ignoreActualFunction: true
|
||||
LoopWithTooManyJumpStatements:
|
||||
active: true
|
||||
maxJumpCount: 2
|
||||
MagicNumber:
|
||||
active: true
|
||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/*.kts' ]
|
||||
ignoreNumbers:
|
||||
- '-1'
|
||||
- '0'
|
||||
- '1'
|
||||
- '2'
|
||||
ignoreHashCodeFunction: true
|
||||
ignorePropertyDeclaration: true
|
||||
ignoreLocalVariableDeclaration: false
|
||||
ignoreConstantDeclaration: true
|
||||
ignoreCompanionObjectPropertyDeclaration: true
|
||||
ignoreAnnotation: false
|
||||
ignoreNamedArgument: true
|
||||
ignoreEnums: false
|
||||
ignoreRanges: false
|
||||
ignoreExtensionFunctions: true
|
||||
ignoreAnnotated: [ 'Preview' ]
|
||||
MandatoryBracesIfStatements:
|
||||
active: true
|
||||
MandatoryBracesLoops:
|
||||
active: true
|
||||
MaxChainedCallsOnSameLine:
|
||||
active: true
|
||||
maxChainedCalls: 5
|
||||
MaxLineLength:
|
||||
active: false # Same as MaximumLineLength
|
||||
maxLineLength: 120
|
||||
excludePackageStatements: true
|
||||
excludeImportStatements: true
|
||||
excludeCommentStatements: false
|
||||
MayBeConst:
|
||||
active: true
|
||||
ModifierOrder:
|
||||
active: false # Same as ModifierOrdering
|
||||
MultilineLambdaItParameter:
|
||||
active: true
|
||||
NestedClassesVisibility:
|
||||
active: true
|
||||
NewLineAtEndOfFile:
|
||||
active: false # Same as FinalNewline
|
||||
NoTabs:
|
||||
active: true
|
||||
NullableBooleanCheck:
|
||||
active: true
|
||||
ObjectLiteralToLambda:
|
||||
active: true
|
||||
OptionalAbstractKeyword:
|
||||
active: true
|
||||
OptionalUnit:
|
||||
active: true
|
||||
OptionalWhenBraces:
|
||||
active: false
|
||||
PreferToOverPairSyntax:
|
||||
active: false
|
||||
ProtectedMemberInFinalClass:
|
||||
active: true
|
||||
RedundantExplicitType:
|
||||
active: false
|
||||
RedundantHigherOrderMapUsage:
|
||||
active: true
|
||||
RedundantVisibilityModifierRule:
|
||||
active: true
|
||||
ReturnCount:
|
||||
active: false
|
||||
max: 2
|
||||
excludedFunctions:
|
||||
- 'equals'
|
||||
excludeLabeled: false
|
||||
excludeReturnFromLambda: true
|
||||
excludeGuardClauses: false
|
||||
SafeCast:
|
||||
active: true
|
||||
SerialVersionUIDInSerializableClass:
|
||||
active: true
|
||||
SpacingBetweenPackageAndImports:
|
||||
active: true
|
||||
ThrowsCount:
|
||||
active: false
|
||||
max: 2
|
||||
excludeGuardClauses: false
|
||||
TrailingWhitespace:
|
||||
active: false
|
||||
UnderscoresInNumericLiterals:
|
||||
active: false
|
||||
acceptableLength: 4
|
||||
allowNonStandardGrouping: false
|
||||
UnnecessaryAbstractClass:
|
||||
active: true
|
||||
UnnecessaryAnnotationUseSiteTarget:
|
||||
active: false
|
||||
UnnecessaryApply:
|
||||
active: true
|
||||
UnnecessaryBackticks:
|
||||
active: true
|
||||
UnnecessaryFilter:
|
||||
active: true
|
||||
UnnecessaryInheritance:
|
||||
active: true
|
||||
UnnecessaryInnerClass:
|
||||
active: false
|
||||
UnnecessaryLet:
|
||||
active: true
|
||||
UnnecessaryParentheses:
|
||||
active: true
|
||||
UntilInsteadOfRangeTo:
|
||||
active: true
|
||||
UnusedImports:
|
||||
active: true
|
||||
UnusedPrivateClass:
|
||||
active: true
|
||||
ignoreAnnotated: [ 'UnusedRequiredComponent' ]
|
||||
UnusedPrivateMember:
|
||||
active: true
|
||||
allowedNames: '(_|ignored|expected|serialVersionUID)'
|
||||
ignoreAnnotated: [ 'Preview', 'UnusedRequiredComponent' ]
|
||||
UseAnyOrNoneInsteadOfFind:
|
||||
active: true
|
||||
UseArrayLiteralsInAnnotations:
|
||||
active: true
|
||||
UseCheckNotNull:
|
||||
active: true
|
||||
UseCheckOrError:
|
||||
active: true
|
||||
UseDataClass:
|
||||
active: false
|
||||
allowVars: false
|
||||
UseEmptyCounterpart:
|
||||
active: true
|
||||
UseIfEmptyOrIfBlank:
|
||||
active: true
|
||||
UseIfInsteadOfWhen:
|
||||
active: false
|
||||
UseIsNullOrEmpty:
|
||||
active: true
|
||||
UseOrEmpty:
|
||||
active: true
|
||||
UseRequire:
|
||||
active: true
|
||||
UseRequireNotNull:
|
||||
active: true
|
||||
UselessCallOnNotNull:
|
||||
active: true
|
||||
UtilityClassWithPublicConstructor:
|
||||
active: true
|
||||
VarCouldBeVal:
|
||||
active: true
|
||||
ignoreLateinitVar: false
|
||||
WildcardImport:
|
||||
active: false
|
||||
excludeImports:
|
||||
- 'java.util.*'
|
||||
|
||||
formatting:
|
||||
active: true
|
||||
android: true
|
||||
AnnotationOnSeparateLine:
|
||||
active: false
|
||||
AnnotationSpacing:
|
||||
active: true
|
||||
ArgumentListWrapping:
|
||||
active: true
|
||||
indentSize: 4
|
||||
maxLineLength: 120
|
||||
BlockCommentInitialStarAlignment:
|
||||
active: true
|
||||
ChainWrapping:
|
||||
active: true
|
||||
CommentSpacing:
|
||||
active: true
|
||||
CommentWrapping:
|
||||
active: false
|
||||
indentSize: 4
|
||||
DiscouragedCommentLocation:
|
||||
active: true
|
||||
EnumEntryNameCase:
|
||||
active: true
|
||||
Filename:
|
||||
active: true # This rules overlaps with naming>MatchingDeclarationName from the standard rules
|
||||
mustBeFirst: true
|
||||
FinalNewline: # This rules overlaps with style>NewLineAtEndOfFile from the standard rules
|
||||
active: true
|
||||
insertFinalNewLine: true
|
||||
FunKeywordSpacing:
|
||||
active: true
|
||||
FunctionReturnTypeSpacing:
|
||||
active: true
|
||||
FunctionSignature:
|
||||
active: true
|
||||
forceMultilineWhenParameterCountGreaterOrEqualThan: 2147483647
|
||||
functionBodyExpressionWrapping: 'default'
|
||||
maxLineLength: 120
|
||||
indentSize: 4
|
||||
FunctionStartOfBodySpacing:
|
||||
active: true
|
||||
FunctionTypeReferenceSpacing:
|
||||
active: true
|
||||
ImportOrdering:
|
||||
active: false
|
||||
layout: 'java.**,javax.**,kotlin.**,kotlinx.**,android.**,androidx.**,*,^'
|
||||
Indentation:
|
||||
active: true
|
||||
indentSize: 4
|
||||
KdocWrapping:
|
||||
active: true
|
||||
indentSize: 4
|
||||
MaximumLineLength:
|
||||
excludes: [ "**/assets/**" ]
|
||||
active: true # This rules overlaps with style>MaxLineLength from the standard rules
|
||||
maxLineLength: 120
|
||||
excludePackageStatements: true
|
||||
excludeImportStatements: true
|
||||
excludeCommentStatements: false
|
||||
ModifierListSpacing:
|
||||
active: true
|
||||
ModifierOrdering:
|
||||
active: true # This rules overlaps with style>ModifierOrder from the standard rules
|
||||
MultiLineIfElse:
|
||||
active: true
|
||||
NoBlankLineBeforeRbrace:
|
||||
active: true
|
||||
NoBlankLinesInChainedMethodCalls:
|
||||
active: true
|
||||
NoConsecutiveBlankLines:
|
||||
active: true
|
||||
NoEmptyClassBody:
|
||||
active: true
|
||||
NoEmptyFirstLineInMethodBlock:
|
||||
active: true
|
||||
NoLineBreakAfterElse:
|
||||
active: true
|
||||
NoLineBreakBeforeAssignment:
|
||||
active: true
|
||||
NoMultipleSpaces:
|
||||
active: true
|
||||
NoSemicolons:
|
||||
active: true
|
||||
NoTrailingSpaces:
|
||||
active: true
|
||||
NoUnitReturn:
|
||||
active: true
|
||||
NoUnusedImports:
|
||||
active: true
|
||||
NoWildcardImports:
|
||||
active: false
|
||||
packagesToUseImportOnDemandProperty: 'java.util.*,kotlinx.android.synthetic.**'
|
||||
NullableTypeSpacing:
|
||||
active: true
|
||||
PackageName:
|
||||
active: true
|
||||
ParameterListSpacing:
|
||||
active: true
|
||||
ParameterListWrapping:
|
||||
active: true
|
||||
maxLineLength: 120
|
||||
SpacingAroundAngleBrackets:
|
||||
active: true
|
||||
SpacingAroundColon:
|
||||
active: true
|
||||
SpacingAroundComma:
|
||||
active: true
|
||||
SpacingAroundCurly:
|
||||
active: true
|
||||
SpacingAroundDot:
|
||||
active: true
|
||||
SpacingAroundDoubleColon:
|
||||
active: true
|
||||
SpacingAroundKeyword:
|
||||
active: true
|
||||
SpacingAroundOperators:
|
||||
active: true
|
||||
SpacingAroundParens:
|
||||
active: true
|
||||
SpacingAroundRangeOperator:
|
||||
active: true
|
||||
SpacingAroundUnaryOperator:
|
||||
active: true
|
||||
SpacingBetweenDeclarationsWithAnnotations:
|
||||
active: true
|
||||
SpacingBetweenDeclarationsWithComments:
|
||||
active: false
|
||||
SpacingBetweenFunctionNameAndOpeningParenthesis:
|
||||
active: true
|
||||
StringTemplate:
|
||||
active: true
|
||||
TrailingCommaOnCallSite:
|
||||
active: true
|
||||
useTrailingCommaOnCallSite: true
|
||||
TrailingCommaOnDeclarationSite:
|
||||
active: true
|
||||
useTrailingCommaOnDeclarationSite: true
|
||||
TypeArgumentListSpacing:
|
||||
active: true
|
||||
TypeParameterListSpacing:
|
||||
active: true
|
||||
UnnecessaryParenthesesBeforeTrailingLambda:
|
||||
active: true
|
||||
Wrapping:
|
||||
active: true
|
||||
indentSize: 4
|
||||
|
||||
compose:
|
||||
ReusedModifierInstance:
|
||||
active: true
|
||||
UnnecessaryEventHandlerParameter:
|
||||
active: true
|
||||
ComposableEventParameterNaming:
|
||||
active: true
|
||||
ComposableParametersOrdering:
|
||||
active: true
|
||||
ModifierDefaultValue:
|
||||
active: true
|
||||
MissingModifierDefaultValue:
|
||||
active: true
|
||||
ModifierHeightWithText:
|
||||
active: true
|
||||
ModifierParameterPosition:
|
||||
active: true
|
||||
PublicComposablePreview:
|
||||
active: true
|
||||
TopLevelComposableFunctions:
|
||||
active: true
|
||||
ComposeFunctionName:
|
||||
active: true
|
||||
|
|
@ -22,7 +22,7 @@ private fun DetektExtension.configure(project: Project) {
|
|||
ignoreFailures = false
|
||||
autoCorrect = true
|
||||
buildUponDefaultConfig = true
|
||||
config.setFrom(project.rootProject.files("plugins/configuration/detekt.yml"))
|
||||
config.setFrom(project.rootProject.files("tangem-android-tools/detekt-config.yml"))
|
||||
}
|
||||
|
||||
private fun Project.configureDetektPlugins() {
|
||||
|
|
|
|||
1
tangem-android-tools
Submodule
1
tangem-android-tools
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 03186c35c13d8693d9a4c7dcb6e760fa58984e67
|
||||
Loading…
Add table
Add a link
Reference in a new issue