Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-23 11:32:54 +03:00
parent 6e14ad22a8
commit 3366b1ef75
17 changed files with 167 additions and 81 deletions

View file

@ -64,7 +64,7 @@ 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.40.0'
implementation 'com.tangem:blockchain:1.41.0'
//lifecycle
implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"

View file

@ -1,10 +1,7 @@
package com.tangem.tap
import android.app.Activity
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import com.tangem.CardFilter
import com.tangem.Config
@ -67,7 +64,8 @@ class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
notificationsHandler = NotificationsHandler(fragment_container)
if (supportFragmentManager.backStackEntryCount == 0) {
if (supportFragmentManager.backStackEntryCount == 0 ||
store.state.globalState.scanNoteResponse == null) {
store.dispatch(HomeAction.CheckIfFirstLaunch)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
}
@ -82,15 +80,4 @@ class MainActivity : AppCompatActivity() {
store.dispatch(NavigationAction.ActivityDestroyed)
super.onDestroy()
}
}
fun setTranslucent(activity: Activity, translucent: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val w = activity.getWindow();
if (translucent) {
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
} else {
w.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
}

View file

@ -9,6 +9,8 @@ import com.tangem.tap.TapConfig
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.extensions.isNoAccountError
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.features.wallet.redux.PayIdState
import com.tangem.tap.features.wallet.redux.WalletAction
@ -30,7 +32,7 @@ class TapWalletManager {
store.dispatch(WalletAction.LoadWallet.Failure())
return
}
updateWallet(walletManager)
loadWallet(walletManager)
}
suspend fun loadPayId() {
@ -38,6 +40,28 @@ class TapWalletManager {
result?.let { handlePayIdResult(it) }
}
suspend fun updateWallet() {
val walletManager = store.state.globalState.scanNoteResponse?.walletManager
if (walletManager == null) {
store.dispatch(WalletAction.UpdateWallet.Failure())
return
}
val result = try {
walletManager.update()
Result.Success(walletManager.wallet)
} catch (exception: Exception) {
Result.Failure(exception)
}
withContext(Dispatchers.Main) {
when (result) {
is Result.Success -> store.dispatch(WalletAction.UpdateWallet.Success(result.data))
is Result.Failure ->
store.dispatch(WalletAction.UpdateWallet.Failure(result.error?.localizedMessage))
}
}
}
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName) {
val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet
val blockchainCurrency = wallet?.blockchain?.currency
@ -76,7 +100,7 @@ class TapWalletManager {
}
}
private suspend fun updateWallet(walletManager: WalletManager) {
private suspend fun loadWallet(walletManager: WalletManager) {
val result = try {
walletManager.update()
Result.Success(walletManager.wallet)

View file

@ -0,0 +1,11 @@
package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.extensions.isAboveZero
fun Map<AmountType, Amount>.toSendableAmounts(): List<Amount> {
return this.toList().unzip().second
.filter { it.type != AmountType.Reserve }
.filter { it.isAboveZero() }
}

View file

@ -1,4 +1,4 @@
package com.tangem.tap.domain
package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import org.stellar.sdk.requests.ErrorResponse

View file

@ -2,8 +2,8 @@ package com.tangem.tap.features.details.redux
import com.tangem.commands.Card
import com.tangem.commands.Settings
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.extensions.toSendableAmounts
import org.rekotlin.Action
import java.util.*
@ -60,7 +60,7 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS
DetailsAction.EraseWallet.Check -> {
val notAllowedByCard = state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true
val notEmpty = state.wallet?.recentTransactions?.isNullOrEmpty() != true ||
state.wallet.amounts.toList().unzip().second.map { it.value?.isZero() }.contains(false)
state.wallet.amounts.toSendableAmounts().isNotEmpty()
val eraseWalletState = when {
notAllowedByCard -> EraseWalletState.NotAllowedByCard
notEmpty -> EraseWalletState.NotEmpty

View file

@ -13,6 +13,7 @@ import com.tangem.tap.features.send.redux.FeeAction.RequestFee
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.SendActionUi
import com.tangem.tap.features.send.redux.states.SendButtonState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.tangemSdk
import kotlinx.coroutines.Dispatchers
@ -63,6 +64,7 @@ private fun verifyAndSendTransaction(appState: AppState?, dispatch: (Action) ->
when (result) {
is SimpleResult.Success -> {
dispatch(SendAction.SendSuccess)
dispatch(WalletAction.UpdateWallet)
dispatch(NavigationAction.PopBackTo())
}
is SimpleResult.Failure -> {

View file

@ -1,18 +1,21 @@
package com.tangem.tap.features.wallet.models
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.tap.common.extensions.toFormattedString
data class PendingTransaction(
val address: String,
val amount: String,
val address: String?,
val amount: String?,
val currency: String,
val type: PendingTransactionType
)
enum class PendingTransactionType { Incoming, Outcoming }
fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransaction {
fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransaction? {
if (this.status == TransactionStatus.Confirmed) return null
val type: PendingTransactionType = if (this.sourceAddress == walletAddress) {
PendingTransactionType.Outcoming
} else {
@ -23,14 +26,15 @@ fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransact
} else {
this.sourceAddress
}
return PendingTransaction(
address,
this.amount.value?.toFormattedString(amount.decimals) ?: "?",
if (address == "unknown") null else address,
this.amount.value?.toFormattedString(amount.decimals),
this.amount.currencySymbol,
type
)
}
fun List<TransactionData>.toPendingTransactions(walletAddress: String): List<PendingTransaction>{
return this.map { it.toPendingTransaction(walletAddress) }
return this.mapNotNull { it.toPendingTransaction(walletAddress) }
}

View file

@ -24,6 +24,11 @@ sealed class WalletAction : Action {
data class Failure(val errorMessage: String? = null) : WalletAction()
}
object UpdateWallet : WalletAction() {
data class Success(val wallet: Wallet) : WalletAction()
data class Failure(val errorMessage: String? = null) : WalletAction()
}
object LoadFiatRate : WalletAction() {
data class Success(val fiatRates: Pair<CryptoCurrencyName, BigDecimal?>) : WalletAction()
object Failure : WalletAction()
@ -42,7 +47,7 @@ sealed class WalletAction : Action {
object Scan : WalletAction()
data class Send(val amount: Amount? = null) : WalletAction() {
object ChooseCurrency : WalletAction()
data class ChooseCurrency(val amounts: List<Amount>?) : WalletAction()
object Cancel : WalletAction()
}
object CreatePayId : WalletAction() {

View file

@ -5,6 +5,7 @@ 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.Wallet
import com.tangem.commands.common.network.Result
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
@ -14,12 +15,14 @@ import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
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.NetworkStateChanged
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Action
@ -58,6 +61,11 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
}
}
}
is WalletAction.UpdateWallet -> {
scope.launch { store.state.globalState.tapWalletManager.updateWallet() }
}
is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet)
is WalletAction.LoadWallet.Success -> setupWalletUpdate(action.wallet)
is WalletAction.CreatePayId.CompleteCreatingPayId -> {
scope.launch {
val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
@ -123,6 +131,17 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
}
}
private fun setupWalletUpdate(wallet: Wallet) {
if (!wallet.recentTransactions.isNullOrEmpty()) {
scope.launch(Dispatchers.IO) {
delay(10000)
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.UpdateWallet)
}
}
}
}
private fun prepareSendAction(amount: Amount?): Action {
return if (amount != null) {
@ -132,10 +151,11 @@ private fun prepareSendAction(amount: Amount?): Action {
PrepareSendScreen(amount)
}
} else {
if (store.state.walletState.wallet?.amounts?.size ?: 0 > 1) {
WalletAction.Send.ChooseCurrency
val amounts = store.state.walletState.wallet?.amounts?.toSendableAmounts()
if (amounts?.size ?: 0 > 1) {
WalletAction.Send.ChooseCurrency(amounts)
} else {
val amountToSend = store.state.walletState.wallet?.amounts?.toList()?.get(0)?.second
val amountToSend = amounts?.first()
PrepareSendScreen(amountToSend)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.wallet.redux
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Wallet
import com.tangem.common.extensions.isZero
import com.tangem.common.extensions.toHexString
import com.tangem.tap.common.extensions.toFiatString
@ -88,42 +89,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
}
is WalletAction.LoadWallet.Success -> {
val fiatCurrencySymbol = state.globalState.appCurrency
val token = action.wallet.amounts[AmountType.Token]
val tokenData = if (token != null) {
val tokenFiatRate = state.globalState.conversionRates.getRate(token.currencySymbol)
val tokenFiatAmount = tokenFiatRate?.let { token.value?.toFiatString(it, fiatCurrencySymbol) }
TokenData(
token.value?.toFormattedString(token.decimals) ?: "",
token.currencySymbol, tokenFiatAmount)
} else {
null
}
val amount = action.wallet.amounts[AmountType.Coin]?.value
val formattedAmount = amount?.toFormattedCurrencyString(
action.wallet.blockchain.decimals(),
action.wallet.blockchain.currency)
val fiatRate = state.globalState.conversionRates.getRate(action.wallet.blockchain.currency)
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
is WalletAction.LoadWallet.Success -> newState = onWalletLoaded(action.wallet, newState)
val pendingTransactions = action.wallet.recentTransactions
.toPendingTransactions(action.wallet.address)
val sendButtonEnabled = amount?.isZero() == false || token?.value?.isZero() == false
newState = newState.copy(
state = ProgressState.Done, wallet = action.wallet,
currencyData = BalanceWidgetData(
BalanceStatus.VerifiedOnline, action.wallet.blockchain.fullName,
currencySymbol = action.wallet.blockchain.currency,
formattedAmount,
token = tokenData,
fiatAmount = fiatAmount
),
pendingTransactions = pendingTransactions,
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
)
}
is WalletAction.LoadWallet.NoAccount -> {
val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet
newState = newState.copy(
@ -142,6 +109,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
errorMessage = action.errorMessage
)
)
is WalletAction.UpdateWallet.Success -> newState = onWalletLoaded(action.wallet, newState)
is WalletAction.LoadFiatRate -> {
newState.copy(currencyData = newState.currencyData.copy(
fiatAmount = null,
@ -212,13 +180,49 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
is WalletAction.CreatePayId.Cancel -> newState = newState.copy(walletDialog = null)
is WalletAction.Send.ChooseCurrency -> {
val amounts = newState.wallet?.amounts?.toList()?.unzip()?.second
newState = newState.copy(
walletDialog = WalletDialog.SelectAmountToSendDialog(amounts)
walletDialog = WalletDialog.SelectAmountToSendDialog(action.amounts)
)
}
is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null)
}
return newState
}
private fun onWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState {
val fiatCurrencySymbol = store.state.globalState.appCurrency
val token = wallet.amounts[AmountType.Token]
val tokenData = if (token != null) {
val tokenFiatRate = store.state.globalState.conversionRates.getRate(token.currencySymbol)
val tokenFiatAmount = tokenFiatRate?.let { token.value?.toFiatString(it, fiatCurrencySymbol) }
TokenData(
token.value?.toFormattedString(token.decimals) ?: "",
token.currencySymbol, tokenFiatAmount)
} else {
null
}
val amount = wallet.amounts[AmountType.Coin]?.value
val formattedAmount = amount?.toFormattedCurrencyString(
wallet.blockchain.decimals(),
wallet.blockchain.currency)
val fiatRate = store.state.globalState.conversionRates.getRate(wallet.blockchain.currency)
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
val pendingTransactions = wallet.recentTransactions
.toPendingTransactions(wallet.address)
val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
return walletState.copy(
state = ProgressState.Done, wallet = wallet,
currencyData = BalanceWidgetData(
BalanceStatus.VerifiedOnline, wallet.blockchain.fullName,
currencySymbol = wallet.blockchain.currency,
formattedAmount,
token = tokenData,
fiatAmount = fiatAmount
),
pendingTransactions = pendingTransactions,
mainButton = WalletMainButton.SendButton(sendButtonEnabled)
)
}

View file

@ -4,7 +4,6 @@ import android.graphics.Bitmap
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Wallet
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.extensions.toBitmap
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
@ -65,6 +64,4 @@ data class Artwork(
val artworkId: String? = null,
val artwork: Bitmap? = null,
val artworkResId: Int? = null
) {
constructor(artworkId: String, artworkBytes: ByteArray) : this(artworkId, artworkBytes.toBitmap())
}
)

View file

@ -44,14 +44,24 @@ class PendingTransactionsAdapter
PendingTransactionType.Incoming -> R.string.wallet_pending_transaction_incoming
PendingTransactionType.Outcoming -> R.string.wallet_pending_transaction_outcoming
}
val transactionAddressRes = when (transaction.type) {
PendingTransactionType.Incoming -> R.string.wallet_pending_transaction_incoming_address
PendingTransactionType.Outcoming -> R.string.wallet_pending_transaction_outcoming_address
}
val image = when (transaction.type) {
PendingTransactionType.Incoming -> R.drawable.ic_arrow_down
PendingTransactionType.Outcoming -> R.drawable.ic_arrow_right
}
view.tv_pending_transaction.text = view.context.getString(
transactionDescriptionRes, transaction.amount, transaction.currency
)
view.tv_pending_transaction_address.text = transaction.address
view.tv_pending_transaction.text = view.context.getString(transactionDescriptionRes)
transaction.amount?.let { view.tv_pending_transaction_amount.text = "$it " }
view.tv_pending_transaction_currency.text = "${transaction.currency}"
if (transaction.address != null) {
view.tv_pending_transaction_address.text =
view.context.getString(transactionAddressRes, transaction.address)
}
view.iv_pending_transaction.setImageDrawable(view.context.getDrawableCompat(image))
}
}

View file

@ -108,6 +108,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
setupCardImage(state.cardImage)
viewAdapter.submitList(state.pendingTransactions)
if (state.pendingTransactions.isEmpty()) {
rv_pending_transaction.hide()
} else {
rv_pending_transaction.show()
}
handleDialogs(state.walletDialog)

View file

@ -46,7 +46,7 @@ class PreferencesStorage(applicationContext: Application) {
}
fun isFirstLaunch(): Boolean {
val isFirst = preferences.contains(FIRST_LAUNCH_CHECK_KEY)
val isFirst = !preferences.contains(FIRST_LAUNCH_CHECK_KEY)
if (isFirst) preferences.edit().putInt(FIRST_LAUNCH_CHECK_KEY, System.currentTimeMillis().toInt()).apply()
return isFirst

View file

@ -36,9 +36,24 @@
android:id="@+id/tv_pending_transaction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Receiving 0.001 BTS from "
tools:text="Receiving "
android:textSize="12sp"/>
<TextView
android:id="@+id/tv_pending_transaction_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="0.001 "
android:textSize="12sp"/>
<TextView
android:id="@+id/tv_pending_transaction_currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="BTS"
android:textSize="12sp"/>
<TextView
android:id="@+id/tv_pending_transaction_address"
android:layout_width="wrap_content"
@ -47,7 +62,7 @@
android:singleLine="true"
android:textSize="12sp"
android:paddingStart="2dp"
tools:text="0x4313bcd3425235bda1232141"/>
tools:text=" from 0x4313bcd3425235bda1232141"/>
</LinearLayout>

View file

@ -35,8 +35,10 @@
<string name="wallet_no_account">Account is not created</string>
<string name="wallet_no_account_description">Load %1s+ %2s to create account</string>
<string name="wallet_tokens">Tokens</string>
<string name="wallet_pending_transaction_incoming">Receiving %1s %2s from </string>
<string name="wallet_pending_transaction_outcoming">Sending %1s %2s to </string>
<string name="wallet_pending_transaction_incoming">Receiving\u0020</string>
<string name="wallet_pending_transaction_outcoming">Sending\u0020</string>
<string name="wallet_pending_transaction_incoming_address"> from %s </string>
<string name="wallet_pending_transaction_outcoming_address"> to %s </string>
<string name="wallet_dialog_pay_id_button">Create PayID</string>
<string name="wallet_create_payid_empty">Enter desired PayID first</string>