Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-28 07:35:13 +00:00
commit a0cf4c6206
83 changed files with 238 additions and 145 deletions

View file

@ -65,6 +65,8 @@ dependencies {
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'
//lifecycle
implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -34,11 +34,11 @@ class TangemSdkManager(val activity: ComponentActivity) {
}
suspend fun setPasscode(cardId: String?): CompletionResult<SetPinResponse> {
return runTaskAsyncReturnOnMain(SetPinCommand.setPin1(null), cardId)
return runTaskAsyncReturnOnMain(SetPinCommand.setPin2(null), cardId)
}
suspend fun setAccessCode(cardId: String?): CompletionResult<SetPinResponse> {
return runTaskAsyncReturnOnMain(SetPinCommand.setPin2(null), cardId)
return runTaskAsyncReturnOnMain(SetPinCommand.setPin1(null), cardId)
}
suspend fun setLongTap(cardId: String?): CompletionResult<SetPinResponse> {

View file

@ -1,8 +1,8 @@
package com.tangem.tap.domain
import androidx.annotation.StringRes
import com.tangem.TangemError
import com.tangem.wallet.R
import java.math.BigDecimal
interface TapErrors
interface MultiMessageError : TapErrors {
@ -34,6 +34,14 @@ sealed class TapError(@StringRes val localizedMessage: Int) : Throwable(), TapEr
) : TapError(-1), MultiMessageError
}
sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemError {
final override val code: Int = 1
override var customMessage: String = code.toString()
object CardForDifferentApp : TapSdkError(R.string.error_card_for_different_app)
}
fun TapErrors.assembleErrorIds(): MutableList<Int> {
val idList = mutableListOf<Int>()
when (this) {

View file

@ -79,7 +79,9 @@ class TapWalletManager {
suspend fun onCardScanned(data: ScanNoteResponse) {
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.ResetState)
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
store.dispatch(WalletAction.CheckIfWarningNeeded)
val artworkId = data.verifyResponse?.artworkInfo?.id
if (data.walletManager != null) {
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
@ -118,9 +120,9 @@ class TapWalletManager {
val error = result.error
val blockchain = walletManager.wallet.blockchain
if (error != null && blockchain.isNoAccountError(error)) {
val amountToCreateAccount = blockchain.amountToCreateAccount()
val amountToCreateAccount = blockchain.amountToCreateAccount(walletManager.wallet.token)
if (amountToCreateAccount != null) {
store.dispatch(WalletAction.LoadWallet.NoAccount(amountToCreateAccount))
store.dispatch(WalletAction.LoadWallet.NoAccount(amountToCreateAccount.toString()))
return@withContext
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import org.stellar.sdk.requests.ErrorResponse
@ -14,10 +15,12 @@ fun Blockchain.isNoAccountError(exception: Throwable): Boolean {
}
}
fun Blockchain.amountToCreateAccount(): Int? {
fun Blockchain.amountToCreateAccount(token: Token? = null): Double? {
return when (this) {
Blockchain.Stellar -> 1
Blockchain.XRP -> 20
Blockchain.Stellar -> if (token?.symbol == NODL) 1.5 else 1.toDouble()
Blockchain.XRP -> 20.toDouble()
else -> null
}
}
}
private const val NODL = "NODL"

View file

@ -2,14 +2,18 @@ package com.tangem.tap.domain.tasks
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.TangemError
import com.tangem.TangemSdkError
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.commands.Card
import com.tangem.commands.CardStatus
import com.tangem.commands.CommandResponse
import com.tangem.commands.Product
import com.tangem.commands.verifycard.VerifyCardCommand
import com.tangem.commands.verifycard.VerifyCardResponse
import com.tangem.common.CompletionResult
import com.tangem.tap.domain.TapSdkError
import com.tangem.tasks.ScanTask
data class ScanNoteResponse(
@ -29,6 +33,12 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
is CompletionResult.Success -> {
val card = this.card ?: result.data
val error = getErrorIfExcludedCard(card)
if (error != null) {
callback(CompletionResult.Failure(error))
return@run
}
val walletManager = try {
WalletManagerFactory.makeWalletManager(card)
} catch (exception: Exception) {
@ -50,4 +60,21 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
}
}
}
private fun getErrorIfExcludedCard(card: Card): TangemError? {
if (card.cardData?.productMask?.contains(Product.Note) != true) {
return TapSdkError.CardForDifferentApp
}
if (excludedBatches.contains(card.cardData?.batchId)) {
return TapSdkError.CardForDifferentApp
}
if (card.status == CardStatus.Purged) return TangemSdkError.CardIsPurged()
if (card.status == CardStatus.NotPersonalized) return TangemSdkError.NotPersonalized()
return null
}
companion object {
private val excludedBatches = listOf("0027", "0030", "0031")
}
}

View file

@ -125,7 +125,7 @@ private fun handleSecurityAction(
allowedSecurityOptions.add(SecurityOption.AccessCode)
}
if (allowSetPin2 && (isDefaultPin1 || !prohibitDefaultPin)) {
allowedSecurityOptions.add(SecurityOption.AccessCode)
allowedSecurityOptions.add(SecurityOption.PassCode)
}
state.copy(securityScreenState = state.securityScreenState?.copy(

View file

@ -2,7 +2,6 @@ package com.tangem.tap.features.details.ui
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.features.details.redux.DetailsAction
@ -20,9 +19,9 @@ class CurrencySelectionDialog {
val currenciesToShow = currencies.map { it.toFormattedString() }.toTypedArray()
var currentSelection = currencies.indexOfFirst { it.symbol == currentAppCurrency }
dialog = MaterialAlertDialogBuilder(context)
dialog = AlertDialog.Builder(context)
.setTitle(context.getString(R.string.details_currency))
.setNeutralButton(context.getString(R.string.generic_cancel)) { _, _ ->
.setNegativeButton(context.getString(R.string.generic_cancel)) { _, _ ->
store.dispatch(DetailsAction.AppCurrencyAction.Cancel)
}
.setPositiveButton(context.getString(R.string.generic_done)) { _, _ ->

View file

@ -14,16 +14,25 @@ import java.math.BigDecimal
sealed class WalletAction : Action {
object ResetState : WalletAction()
object LoadData : WalletAction() {
data class Failure(val error: TapError): WalletAction()
data class Failure(val error: TapError) : WalletAction()
}
object LoadWallet : WalletAction() {
data class Success(val wallet: Wallet) : WalletAction()
data class NoAccount(val amountToCreateAccount: Int) : WalletAction()
data class NoAccount(val amountToCreateAccount: String) : WalletAction()
data class Failure(val errorMessage: String? = null) : WalletAction()
}
object CheckIfWarningNeeded : WalletAction()
object CheckHashesCountOnline : WalletAction()
object NeedToCheckHashesCountOnline : WalletAction()
object ConfirmHashesCount : WalletAction()
data class ShowWarning(val warningType: WarningType) : WalletAction()
object SaveCardId : WalletAction()
object UpdateWallet : WalletAction() {
data class Success(val wallet: Wallet) : WalletAction()
data class Failure(val errorMessage: String? = null) : WalletAction()
@ -50,6 +59,7 @@ sealed class WalletAction : Action {
data class ChooseCurrency(val amounts: List<Amount>?) : WalletAction()
object Cancel : WalletAction()
}
object CreatePayId : WalletAction() {
data class CompleteCreatingPayId(val payId: String) : WalletAction()
data class Success(val payId: String) : WalletAction()
@ -68,7 +78,7 @@ sealed class WalletAction : Action {
}
object ShowQrCode : WalletAction()
object HideQrCode : WalletAction()
object HideDialog : WalletAction()
data class ExploreAddress(val context: Context) : WalletAction()
object CreateWallet : WalletAction()
object EmptyWallet : WalletAction()

View file

@ -5,7 +5,10 @@ import android.net.Uri
import androidx.core.content.ContextCompat
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.SignatureCountValidator
import com.tangem.blockchain.common.Wallet
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.toHexString
@ -17,7 +20,9 @@ import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.network.NetworkStateChanged
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
@ -65,7 +70,10 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
scope.launch { store.state.globalState.tapWalletManager.updateWallet() }
}
is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet)
is WalletAction.LoadWallet.Success -> setupWalletUpdate(action.wallet)
is WalletAction.LoadWallet.Success -> {
store.dispatch(WalletAction.CheckHashesCountOnline)
setupWalletUpdate(action.wallet)
}
is WalletAction.CreatePayId.CompleteCreatingPayId -> {
scope.launch {
val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
@ -100,13 +108,21 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
}
}
}
is WalletAction.LoadData, is NetworkStateChanged -> {
is WalletAction.LoadData -> {
scope.launch {
store.state.globalState.scanNoteResponse?.let {
store.state.globalState.tapWalletManager.onCardScanned(it)
}
}
}
is NetworkStateChanged -> {
store.state.globalState.scanNoteResponse?.let { scanNoteResponse ->
store.dispatch(WalletAction.CheckHashesCountOnline)
scope.launch {
store.state.globalState.tapWalletManager.onCardScanned(scanNoteResponse)
}
}
}
is WalletAction.CopyAddress -> {
store.state.walletState.addressData?.address?.let {
action.context.copyToClipboard(it)
@ -125,6 +141,20 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
}
}
is WalletAction.CheckIfWarningNeeded -> {
val card = store.state.globalState.scanNoteResponse?.card
val validator = store.state.globalState.scanNoteResponse?.walletManager
as? SignatureCountValidator
if (card != null && !preferencesStorage.wasCardScannedBefore(card.cardId)) {
val result = checkIfWarningNeeded(card, validator)
if (result != null) store.dispatch(WalletAction.ShowWarning(result))
}
}
is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline()
is WalletAction.SaveCardId -> {
val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
cardId?.let { preferencesStorage.saveScannedCardId(it) }
}
}
next(action)
}
@ -132,7 +162,7 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
}
private fun setupWalletUpdate(wallet: Wallet) {
if (!wallet.recentTransactions.isNullOrEmpty()) {
if (!wallet.recentTransactions.isNullOrEmpty() && !store.state.walletState.updatingWallet) {
scope.launch(Dispatchers.IO) {
delay(10000)
withContext(Dispatchers.Main) {
@ -159,4 +189,48 @@ private fun prepareSendAction(amount: Amount?): Action {
PrepareSendScreen(amountToSend)
}
}
}
private fun checkIfWarningNeeded(
card: Card, signatureCountValidator: SignatureCountValidator? = null
): WarningType? {
// if (card.getType() != CardType.Release) {
// return WarningType.DevCard
// }
return if (signatureCountValidator == null) {
if (card.walletSignedHashes ?: 0 > 0) {
WarningType.CardSignedHashesBefore
} else {
store.dispatch(WalletAction.SaveCardId)
null
}
} else {
store.dispatch(WalletAction.NeedToCheckHashesCountOnline)
null
}
}
private fun checkHashesCountOnline() {
if (store.state.walletState.hashesCountVerified != false) return
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return
val card = store.state.globalState.scanNoteResponse?.card
val validator = store.state.globalState.scanNoteResponse?.walletManager
as? SignatureCountValidator
scope.launch {
val result = validator?.validateSignatureCount(card?.walletSignedHashes
?: 0)
withContext(Dispatchers.Main) {
when (result) {
SimpleResult.Success -> store.dispatch(WalletAction.ConfirmHashesCount)
is SimpleResult.Failure -> store.dispatch(
WalletAction.ShowWarning(WarningType.CardSignedHashesBefore)
)
}
}
}
}

View file

@ -31,7 +31,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
var newState = state.walletState
when (action) {
is WalletAction.EmptyWallet -> newState = WalletState(
is WalletAction.ResetState -> newState = WalletState()
is WalletAction.EmptyWallet -> newState = newState.copy(
state = ProgressState.Done,
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
mainButton = WalletMainButton.CreateWalletButton(true)
@ -45,7 +46,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
} else {
AddressData(wallet.address, wallet.shareUrl, wallet.exploreUrl)
}
newState = WalletState(
newState = newState.copy(
state = ProgressState.Error,
error = ErrorType.NoInternetConnection,
addressData = addressData,
@ -56,7 +57,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
}
is TapError.UnknownBlockchain -> {
newState = WalletState(
newState = newState.copy(
state = ProgressState.Done,
currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain)
)
@ -77,7 +78,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
} else {
null
}
newState = WalletState(
newState = newState.copy(
state = ProgressState.Loading,
cardImage = cardImage,
currencyData = BalanceWidgetData(
@ -109,7 +110,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
errorMessage = action.errorMessage
)
)
is WalletAction.UpdateWallet -> newState = newState.copy(updatingWallet = true)
is WalletAction.UpdateWallet.Success -> newState = onWalletLoaded(action.wallet, newState)
is WalletAction.UpdateWallet.Failure -> newState = newState.copy(updatingWallet = false)
is WalletAction.LoadFiatRate -> {
newState.copy(currencyData = newState.currencyData.copy(
fiatAmount = null,
@ -158,7 +161,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
)
}
is WalletAction.HideQrCode -> {
is WalletAction.HideDialog -> {
newState = newState.copy(walletDialog = null)
}
is WalletAction.LoadPayId.Success -> newState = newState.copy(
@ -185,7 +188,12 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
}
is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null)
is WalletAction.ShowWarning ->
newState = newState.copy(walletDialog = WalletDialog.WarningDialog(action.warningType))
is WalletAction.NeedToCheckHashesCountOnline ->
newState = newState.copy(hashesCountVerified = false)
is WalletAction.ConfirmHashesCount ->
newState = newState.copy(hashesCountVerified = true)
}
return newState
}
@ -223,6 +231,7 @@ private fun onWalletLoaded(wallet: Wallet, walletState: WalletState): WalletStat
fiatAmount = fiatAmount
),
pendingTransactions = pendingTransactions,
updatingWallet = false,
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
)
}

View file

@ -15,10 +15,12 @@ data class WalletState(
val cardImage: Artwork? = null,
val wallet: Wallet? = null,
val pendingTransactions: List<PendingTransaction> = emptyList(),
val hashesCountVerified: Boolean? = null,
val addressData: AddressData? = null,
val currencyData: BalanceWidgetData = BalanceWidgetData(),
val payIdData: PayIdData = PayIdData(),
val walletDialog: WalletDialog? = null,
val updatingWallet: Boolean = false,
val mainButton: WalletMainButton = WalletMainButton.SendButton(false)
) : StateType {
val showDetails: Boolean =
@ -33,8 +35,11 @@ sealed class WalletDialog {
data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog()
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog()
data class WarningDialog(val type: WarningType) : WalletDialog()
}
enum class WarningType { CardSignedHashesBefore, DevCard }
enum class ProgressState { Loading, Done, Error }

View file

@ -26,7 +26,7 @@ data class BalanceWidgetData(
val amount: String? = null,
val fiatAmount: String? = null,
val token: TokenData? = null,
val amountToCreateAccount: Int? = null,
val amountToCreateAccount: String? = null,
val errorMessage: String? = null
)
@ -100,7 +100,7 @@ class BalanceWidget(
fragment.tv_error_descriptions.text =
fragment.getString(
R.string.wallet_no_account_description,
data.amountToCreateAccount?.toString(), data.currencySymbol
data.amountToCreateAccount, data.currencySymbol
)
}
BalanceStatus.UnknownBlockchain -> {

View file

@ -23,6 +23,7 @@ import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
import com.tangem.tap.features.wallet.ui.dialogs.PayIdDialog
import com.tangem.tap.features.wallet.ui.dialogs.QrDialog
import com.tangem.tap.features.wallet.ui.dialogs.WarningDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.card_balance.*
@ -229,6 +230,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
this.show(walletDialog.amounts)
}
}
is WalletDialog.WarningDialog -> {
if (dialog == null) dialog = WarningDialog(requireContext()).apply {
this.show(walletDialog.type)
}
}
null -> {
dialog?.dismiss()
dialog = null

View file

@ -18,8 +18,8 @@ class QrDialog(context: Context) : Dialog(context) {
}
fun showQr(qrCode: Bitmap, shareUrl: String, currencyName: CryptoCurrencyName?) {
this.setOnDismissListener { store.dispatch(WalletAction.HideQrCode) }
this.btn_done?.setOnClickListener { store.dispatch(WalletAction.HideQrCode) }
this.setOnDismissListener { store.dispatch(WalletAction.HideDialog) }
this.btn_done?.setOnClickListener { store.dispatch(WalletAction.HideDialog) }
this.tv_qr_dialog_address?.text = shareUrl
this.iv_qrcode?.setImageBitmap(qrCode)

View file

@ -0,0 +1,30 @@
package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WarningType
import com.tangem.tap.store
import com.tangem.wallet.R
class WarningDialog(context: Context) : AlertDialog(context) {
private val dialog: AlertDialog = Builder(context)
.setTitle(context.getString(R.string.generic_warning))
.setPositiveButton(context.getString(R.string.general_ok)) { _, _ ->
dismiss()
}.setOnDismissListener {
store.dispatch(WalletAction.SaveCardId)
store.dispatch(WalletAction.HideDialog)
}
.create()
fun show(warningType: WarningType) {
val messageRes = when (warningType) {
WarningType.CardSignedHashesBefore -> R.string.warning_card_signed_transactions
WarningType.DevCard -> R.string.wallet_warning_dev_card
}
dialog.setMessage(dialog.context.getString(messageRes))
dialog.show()
}
}

View file

@ -48,15 +48,29 @@ class PreferencesStorage(applicationContext: Application) {
fun isFirstLaunch(): Boolean {
val isFirst = !preferences.contains(FIRST_LAUNCH_CHECK_KEY)
if (isFirst) preferences.edit().putInt(FIRST_LAUNCH_CHECK_KEY, System.currentTimeMillis().toInt()).apply()
return isFirst
}
fun saveScannedCardId(cardId: String) {
val scannedCardsIds: String = restoreScannedCardIds()
if (!scannedCardsIds.contains(cardId)) {
preferences.edit().putString(SCANNED_CARDS_IDS_KEY, "$scannedCardsIds$cardId, ").apply()
}
}
fun wasCardScannedBefore(cardId: String): Boolean {
return restoreScannedCardIds().contains(cardId)
}
private fun restoreScannedCardIds(): String =
preferences.getString(SCANNED_CARDS_IDS_KEY, "") ?: ""
companion object {
private const val PREFERENCES_NAME = "tapPrefs"
private const val APP_CURRENCY_KEY = "appCurrency"
private const val FIAT_CURRENCIES_KEY = "fiatCurrencies"
private const val FIRST_LAUNCH_CHECK_KEY = "firstLaunchCheck"
private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds"
}
}

View file

@ -177,20 +177,6 @@
android:background="@color/separatorGrey2"
app:layout_constraintTop_toBottomOf="@id/tv_card_title" />
<TextView
android:id="@+id/tv_validate_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="7dp"
android:drawablePadding="15dp"
android:textColor="@color/darkGray6"
android:textSize="16sp"
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_card_title"
android:text="@string/details_validate_card" />
<TextView
android:id="@+id/tv_security"
android:layout_width="wrap_content"
@ -202,7 +188,7 @@
android:textSize="16sp"
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_validate_card"
app:layout_constraintTop_toBottomOf="@id/tv_card_title"
tools:text="Long Tap" />
<TextView
@ -215,7 +201,7 @@
android:textColor="@color/darkGray6"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_validate_card" />
app:layout_constraintTop_toBottomOf="@id/tv_card_title" />
<TextView
@ -236,5 +222,4 @@
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -29,7 +29,7 @@
android:layout_height="wrap_content"
android:ellipsize="middle"
android:paddingStart="16dp"
android:paddingTop="20dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:singleLine="true"
android:textColor="@color/darkGray1"
@ -46,7 +46,7 @@
android:layout_height="wrap_content"
android:drawablePadding="9dp"
android:paddingStart="16dp"
android:paddingTop="6dp"
android:paddingTop="4dp"
android:paddingEnd="16dp"
android:paddingBottom="25dp"
android:text="@string/wallet_explore_address"
@ -109,7 +109,7 @@
<View
android:id="@+id/v_payid_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_height="0.5dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/lightGray5"
@ -117,8 +117,8 @@
<ImageView
android:id="@+id/iv_payid_icon"
android:layout_width="58dp"
android:layout_height="18dp"
android:layout_width="68dp"
android:layout_height="22dp"
android:layout_margin="16dp"
android:src="@drawable/ic_payid"
app:layout_constraintStart_toStartOf="parent"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 372 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 B

After

Width:  |  Height:  |  Size: 3 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 B

After

Width:  |  Height:  |  Size: 237 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,006 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 515 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 B

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 902 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

After

Width:  |  Height:  |  Size: 5.5 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

@ -6,6 +6,7 @@
<string name="generic_cancel">Cancel</string>
<string name="generic_retry">Retry</string>
<string name="generic_and">and</string>
<string name="generic_warning">Warning</string>
<string name="notification_address_copied">Address was successfully copied</string>
<string name="notification_no_internet">No internet connection</string>
@ -13,7 +14,7 @@
<string name="home_do_you_have_card">Welcome to Tangem.\nDo you have one of our cards?</string>
<string name="home_welcome_back">Welcome back to Tangem Tap. \nScan your card to start.</string>
<string name="home_button_yes">Yes!</string>
<string name="home_button_yes">Yes. Scan it!</string>
<string name="home_button_scan">Scan card</string>
<string name="home_button_shop">Shop</string>
<string name="home_button_no_card">No</string>
@ -47,10 +48,15 @@
<string name="wallet_dialog_pay_id_description">Your PayID is information unique to you, like your phone number, email or ABN.</string>
<string name="wallet_dialog_qr_code_header">%s wallet</string>
<string name="wallet_warning_dev_card">The card you scanned is a development card. Don\'t accept it as a payment.</string>
<string name="warning_card_signed_transactions">Warning: This card has been already topped up and signed transactions in the past.
Consider immediate withdrawal of all funds if you have received this card from an untrusted source.</string>
<string name="error_payid_already_created">This PayID already exists. Try a different one.</string>
<string name="error_creating_payid">Error response while creating PayID.</string>
<string name="error_card_for_different_app">This card it is not designed to work with Tangem Tap</string>
<string name="error_unknown">Unknown error</string>
<string name="error_payid_verification_failed">PayID verification failed</string>
<string name="error_payid_unsupported_by_blockchain">PayID unsupported by blockchain</string>