Updated on 2026-08-14
This commit is contained in:
commit
9e83415c6e
15 changed files with 135 additions and 42 deletions
|
|
@ -64,9 +64,9 @@ dependencies {
|
|||
implementation 'com.google.android.material:material:1.2.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
|
||||
|
||||
implementation 'com.tangem:blockchain:1.49.0'
|
||||
implementation 'com.tangem:core:1.57.0'
|
||||
implementation 'com.tangem:sdk:1.57.0'
|
||||
implementation 'com.tangem:blockchain:1.56.0'
|
||||
implementation 'com.tangem:core:1.61.0'
|
||||
implementation 'com.tangem:sdk:1.61.0'
|
||||
|
||||
//lifecycle
|
||||
implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"
|
||||
|
|
|
|||
|
|
@ -14,4 +14,5 @@ sealed class GlobalAction : Action {
|
|||
object RestoreAppCurrency : GlobalAction() {
|
||||
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
|
||||
}
|
||||
data class UpdateWalletSignedHashes(val walletSignedHashes: Int) : GlobalAction()
|
||||
}
|
||||
|
|
@ -7,22 +7,32 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
|
||||
if (action !is GlobalAction) return state.globalState
|
||||
|
||||
var newState = state.globalState
|
||||
val globalState = state.globalState
|
||||
|
||||
when (action) {
|
||||
return when (action) {
|
||||
is GlobalAction.SaveScanNoteResponse ->
|
||||
newState = newState.copy(scanNoteResponse = action.scanNoteResponse)
|
||||
globalState.copy(scanNoteResponse = action.scanNoteResponse)
|
||||
is GlobalAction.SetFiatRate -> {
|
||||
val rates = newState.conversionRates.rates.toMutableMap()
|
||||
val rates = globalState.conversionRates.rates.toMutableMap()
|
||||
rates[action.fiatRates.first] = action.fiatRates.second
|
||||
newState = newState.copy(conversionRates = ConversionRates(rates))
|
||||
globalState.copy(conversionRates = ConversionRates(rates))
|
||||
}
|
||||
is GlobalAction.ChangeAppCurrency -> {
|
||||
newState = newState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
|
||||
globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
|
||||
}
|
||||
is GlobalAction.RestoreAppCurrency.Success -> {
|
||||
newState = newState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
|
||||
globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
|
||||
}
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
val card = globalState.scanNoteResponse?.card?.copy(
|
||||
walletSignedHashes = action.walletSignedHashes
|
||||
)
|
||||
if (card != null) {
|
||||
globalState.copy(scanNoteResponse = globalState.scanNoteResponse.copy(card = card))
|
||||
} else {
|
||||
globalState
|
||||
}
|
||||
}
|
||||
else -> globalState
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
|
@ -34,11 +34,19 @@ class TangemSdkManager(val activity: ComponentActivity) {
|
|||
}
|
||||
|
||||
suspend fun setPasscode(cardId: String?): CompletionResult<SetPinResponse> {
|
||||
return runTaskAsyncReturnOnMain(SetPinCommand.setPin2(null), cardId)
|
||||
return runTaskAsyncReturnOnMain(SetPinCommand(
|
||||
pinType = PinType.Pin2,
|
||||
newPin1 = tangemSdk.config.defaultPin1.calculateSha256(),
|
||||
newPin2 = null
|
||||
), cardId)
|
||||
}
|
||||
|
||||
suspend fun setAccessCode(cardId: String?): CompletionResult<SetPinResponse> {
|
||||
return runTaskAsyncReturnOnMain(SetPinCommand.setPin1(null), cardId)
|
||||
return runTaskAsyncReturnOnMain(SetPinCommand(
|
||||
pinType = PinType.Pin1,
|
||||
newPin1 = null,
|
||||
newPin2 = tangemSdk.config.defaultPin2.calculateSha256()
|
||||
), cardId)
|
||||
}
|
||||
|
||||
suspend fun setLongTap(cardId: String?): CompletionResult<SetPinResponse> {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
}
|
||||
|
||||
private fun getErrorIfExcludedCard(card: Card): TangemError? {
|
||||
if (card.cardData?.productMask?.contains(Product.Note) != true) {
|
||||
if (card.cardData?.productMask != null &&
|
||||
card.cardData?.productMask?.contains(Product.Note) != true) {
|
||||
return TapSdkError.CardForDifferentApp
|
||||
}
|
||||
if (excludedBatches.contains(card.cardData?.batchId)) {
|
||||
|
|
|
|||
|
|
@ -111,11 +111,16 @@ private fun handleSecurityAction(
|
|||
): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.ManageSecurity.OpenSecurity -> {
|
||||
val prohibitDefaultPin = state.card?.settingsMask?.contains(Settings.ProhibitDefaultPIN1) == true
|
||||
val allowSetPin1 = state.card?.settingsMask?.contains(Settings.AllowSetPIN1) != false
|
||||
val allowSetPin2 = state.card?.settingsMask?.contains(Settings.AllowSetPIN2) != false
|
||||
val isDefaultPin1 = state.card?.isPin1Default != false
|
||||
val isDefaultPin2 = state.card?.isPin2Default != false
|
||||
if (state.card?.isPin2Default == null) {
|
||||
return state.copy(securityScreenState = state.securityScreenState?.copy(
|
||||
allowedOptions = EnumSet.noneOf(SecurityOption::class.java)
|
||||
))
|
||||
}
|
||||
val prohibitDefaultPin = state.card.settingsMask?.contains(Settings.ProhibitDefaultPIN1) == true
|
||||
val allowSetPin1 = state.card.settingsMask?.contains(Settings.AllowSetPIN1) != false
|
||||
val allowSetPin2 = state.card.settingsMask?.contains(Settings.AllowSetPIN2) != false
|
||||
val isDefaultPin1 = state.card.isPin1Default != false
|
||||
val isDefaultPin2 = state.card.isPin2Default != false
|
||||
|
||||
val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java)
|
||||
if ((isDefaultPin1 && isDefaultPin2) || !prohibitDefaultPin) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.wallet.R
|
|||
import kotlinx.android.synthetic.main.fragment_details_confirm.toolbar
|
||||
import kotlinx.android.synthetic.main.fragment_details_security.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import java.util.*
|
||||
|
||||
class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
|
||||
StoreSubscriber<DetailsState> {
|
||||
|
|
@ -73,11 +74,53 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
|
|||
override fun newState(state: DetailsState) {
|
||||
if (activity == null) return
|
||||
selectSecurityOption(state.securityScreenState?.selectedOption)
|
||||
for (option in SecurityOption.values()) {
|
||||
enableOptions(
|
||||
option,
|
||||
state.securityScreenState?.allowedOptions
|
||||
?: EnumSet.noneOf(SecurityOption::class.java)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectSecurityOption(securityOption: SecurityOption?) {
|
||||
radiobutton_long_tap.isChecked = securityOption == SecurityOption.LongTap
|
||||
radiobutton_passcode.isChecked = securityOption == SecurityOption.PassCode
|
||||
radiobutton_access_code.isChecked = securityOption == SecurityOption.AccessCode
|
||||
radiobutton_long_tap.isChecked =
|
||||
securityOption == SecurityOption.LongTap && radiobutton_long_tap.isEnabled
|
||||
radiobutton_passcode.isChecked =
|
||||
securityOption == SecurityOption.PassCode && radiobutton_passcode.isEnabled
|
||||
radiobutton_access_code.isChecked =
|
||||
securityOption == SecurityOption.AccessCode && radiobutton_access_code.isEnabled
|
||||
}
|
||||
|
||||
private fun enableOptions(option: SecurityOption, allowedOptions: EnumSet<SecurityOption>) {
|
||||
when (option) {
|
||||
SecurityOption.LongTap -> enableLongTap(allowedOptions.contains(option))
|
||||
SecurityOption.PassCode -> enablePasscode(allowedOptions.contains(option))
|
||||
SecurityOption.AccessCode -> enableAccessCode(allowedOptions.contains(option))
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableLongTap(enable: Boolean) {
|
||||
val alpha = if (enable) 1f else 0.5f
|
||||
tv_long_tap_description.alpha = alpha
|
||||
tv_long_tap_title.alpha = alpha
|
||||
radiobutton_long_tap.alpha = alpha
|
||||
radiobutton_long_tap.isEnabled = enable
|
||||
}
|
||||
|
||||
private fun enablePasscode(enable: Boolean) {
|
||||
val alpha = if (enable) 1f else 0.5f
|
||||
tv_passcode_description.alpha = alpha
|
||||
tv_passcode_title.alpha = alpha
|
||||
radiobutton_passcode.alpha = alpha
|
||||
radiobutton_passcode.isEnabled = enable
|
||||
}
|
||||
|
||||
private fun enableAccessCode(enable: Boolean) {
|
||||
val alpha = if (enable) 1f else 0.5f
|
||||
tv_access_code_description.alpha = alpha
|
||||
tv_access_code_title.alpha = alpha
|
||||
radiobutton_access_code.alpha = alpha
|
||||
radiobutton_access_code.isEnabled = enable
|
||||
}
|
||||
}
|
||||
|
|
@ -128,7 +128,7 @@ internal class AddressPayIdMiddleware {
|
|||
|
||||
//TODO: move to the blockchainSDK
|
||||
private fun extractAddressFromShareUri(shareUri: String): String {
|
||||
val sharePrefix = listOf("bitcoin:", "ethereum:", "ripple:", "litecoin:")
|
||||
val sharePrefix = listOf("bitcoin:", "ethereum:", "xrpl:", "litecoin:", "bnb:")
|
||||
val prefixes = sharePrefix.filter { shareUri.contains(it) }
|
||||
return if (prefixes.isEmpty()) shareUri else shareUri.replace(prefixes[0], "")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.tangem.tap.features.send.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.Signer
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi
|
||||
|
|
@ -62,12 +63,13 @@ private fun verifyAndSendTransaction(appState: AppState?, dispatch: (Action) ->
|
|||
val result = (walletManager as TransactionSender).send(txData, Signer(tangemSdk))
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is SimpleResult.Success -> {
|
||||
is Result.Success -> {
|
||||
dispatch(SendAction.SendSuccess)
|
||||
dispatch(GlobalAction.UpdateWalletSignedHashes(result.data.walletSignedHashes))
|
||||
dispatch(WalletAction.UpdateWallet)
|
||||
dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
is SimpleResult.Failure -> {
|
||||
is Result.Failure -> {
|
||||
when (result.error) {
|
||||
is CreateAccountUnderfunded -> {
|
||||
val error = result.error as CreateAccountUnderfunded
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import com.tangem.blockchain.extensions.SimpleResult
|
|||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.CardType
|
||||
import com.tangem.common.extensions.getType
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -195,9 +197,9 @@ private fun checkIfWarningNeeded(
|
|||
card: Card, signatureCountValidator: SignatureCountValidator? = null
|
||||
): WarningType? {
|
||||
|
||||
// if (card.getType() != CardType.Release) {
|
||||
// return WarningType.DevCard
|
||||
// }
|
||||
if (card.getType() != CardType.Release) {
|
||||
return WarningType.DevCard
|
||||
}
|
||||
|
||||
return if (signatureCountValidator == null) {
|
||||
if (card.walletSignedHashes ?: 0 > 0) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.redux
|
|||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.commands.common.network.TangemService
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.common.extensions.toFiatString
|
||||
|
|
@ -140,13 +141,15 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
))
|
||||
}
|
||||
is WalletAction.LoadArtwork -> {
|
||||
// TODO: form URL in Card SDK
|
||||
var artworkAddress = "https://verify.tangem.com/card/artwork"
|
||||
artworkAddress += "?artworkId=${action.artworkId}"
|
||||
artworkAddress += "&CID=${store.state.globalState.scanNoteResponse?.card?.cardId}"
|
||||
artworkAddress += "&publicKey=${store.state.globalState.scanNoteResponse?.card?.cardPublicKey?.toHexString()}"
|
||||
val artwork = if (action.artworkId != null) {
|
||||
Artwork(artworkId = artworkAddress)
|
||||
val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
val cardPublicKey = store.state.globalState.scanNoteResponse?.card?.cardPublicKey?.toHexString()
|
||||
val artworkUrl = if (cardId != null && cardPublicKey != null && action.artworkId != null) {
|
||||
TangemService.getUrlForArtwork(cardId, cardPublicKey, action.artworkId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val artwork = if (artworkUrl != null) {
|
||||
Artwork(artworkId = artworkUrl)
|
||||
} else {
|
||||
Artwork(artworkResId = R.drawable.card_default)
|
||||
}
|
||||
|
|
@ -221,10 +224,15 @@ private fun onWalletLoaded(wallet: Wallet, walletState: WalletState): WalletStat
|
|||
.toPendingTransactions(wallet.address)
|
||||
|
||||
val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val balanceStatus = if (pendingTransactions.isNotEmpty()) {
|
||||
BalanceStatus.TransactionInProgress
|
||||
} else {
|
||||
BalanceStatus.VerifiedOnline
|
||||
}
|
||||
return walletState.copy(
|
||||
state = ProgressState.Done, wallet = wallet,
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.VerifiedOnline, wallet.blockchain.fullName,
|
||||
balanceStatus, wallet.blockchain.fullName,
|
||||
currencySymbol = wallet.blockchain.currency,
|
||||
formattedAmount,
|
||||
token = tokenData,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import kotlinx.android.synthetic.main.layout_token.view.*
|
|||
|
||||
enum class BalanceStatus {
|
||||
VerifiedOnline,
|
||||
TransactionInProgress,
|
||||
Unreachable,
|
||||
Loading,
|
||||
NoAccount,
|
||||
|
|
@ -56,14 +57,22 @@ class BalanceWidget(
|
|||
|
||||
showStatus(R.id.tv_status_loading)
|
||||
}
|
||||
BalanceStatus.VerifiedOnline -> {
|
||||
BalanceStatus.VerifiedOnline, BalanceStatus.TransactionInProgress -> {
|
||||
fragment.l_balance.show()
|
||||
fragment.l_balance_error.hide()
|
||||
fragment.tv_currency.text = data.currency
|
||||
fragment.tv_amount.text = data.amount
|
||||
fragment.tv_fiat_amount.show()
|
||||
fragment.tv_fiat_amount.text = data.fiatAmount
|
||||
showStatus(R.id.tv_status_verified)
|
||||
val statusView = if (data.status == BalanceStatus.VerifiedOnline) {
|
||||
R.id.tv_status_verified
|
||||
} else {
|
||||
fragment.tv_status_error.text =
|
||||
fragment.getText(R.string.wallet_transaction_in_progress)
|
||||
R.id.group_error
|
||||
}
|
||||
showStatus(statusView)
|
||||
fragment.tv_status_error_message.hide()
|
||||
|
||||
if (data.token != null) {
|
||||
fragment.l_token.show()
|
||||
|
|
@ -83,6 +92,8 @@ class BalanceWidget(
|
|||
fragment.tv_currency.text = data.currency
|
||||
fragment.tv_amount.text = ""
|
||||
fragment.tv_status_error_message.text = data.errorMessage
|
||||
fragment.tv_status_error.text =
|
||||
fragment.getString(R.string.wallet_blockchain_is_unreachable)
|
||||
|
||||
showStatus(R.id.group_error)
|
||||
fragment.tv_status_error_message.show(!data.errorMessage.isNullOrBlank())
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@
|
|||
android:id="@+id/tv_security_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/details_manage_security"
|
||||
android:textColor="@color/darkGray6"
|
||||
|
|
@ -208,7 +208,7 @@
|
|||
android:id="@+id/tv_erase_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:drawablePadding="15dp"
|
||||
android:textColor="@color/darkGray6"
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@
|
|||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:text="@string/details_save_changes"
|
||||
android:text="@string/details_continue"
|
||||
app:icon="@drawable/ic_save"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
<string name="wallet_create_payid">Create PayID</string>
|
||||
<string name="wallet_verified_balance">Verified Balance</string>
|
||||
<string name="wallet_blockchain_is_unreachable">Blockchain is unreachable</string>
|
||||
<string name="wallet_transaction_in_progress">Transaction in progress</string>
|
||||
<string name="wallet_unknown_blockchain">Your Tangem card was made to work with a different application. Please see the name and instructions on your card, and install the correct app.</string>
|
||||
<string name="wallet_unknown_blockchain_title">Uh oh!</string>
|
||||
<string name="wallet_balance_is_loading">Balance is loading…</string>
|
||||
|
|
@ -106,6 +107,7 @@
|
|||
<string name="details_manage_security">Manage security</string>
|
||||
<string name="details_erase_wallet">Erase wallet</string>
|
||||
<string name="details_save_changes">Save changes</string>
|
||||
<string name="details_continue">Continue</string>
|
||||
<string name="details_manage_security_long_tap">Long Tap</string>
|
||||
<string name="details_manage_security_passcode">Passcode</string>
|
||||
<string name="details_manage_security_access_code">Access Code</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue