Updated on 2026-08-14

This commit is contained in:
Tangem 2020-11-25 20:02:04 +03:00
commit 7a95d4bcd4
22 changed files with 78 additions and 150 deletions

View file

@ -2,8 +2,8 @@ package com.tangem.tap
object TapConfig {
const val usePayId: Boolean = true
const val useTopUp: Boolean = false
const val useTopUp: Boolean = true
const val coinMarketCapKey = "f6622117-c043-47a0-8975-9d673ce484de"
const val moonPayApiKey = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE"
const val moonPayApiSecretKey = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C"
const val moonPayApiKey = "pk_live_YTDl4Uei4411FXLAFe2rn8R2qWSiQcm"
const val moonPayApiSecretKey = "sk_live_7wMjoyGTGAB3i1suQHszCWRRqJBRPsQF"
}

View file

@ -11,7 +11,6 @@ import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.tap.features.home.HomeFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.wallet.ui.topup.TopUpFragment
import com.tangem.wallet.R
fun FragmentActivity.openFragment(screen: AppScreen, addToBackStack: Boolean = true) {
@ -40,7 +39,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
return when (screen) {
AppScreen.Home -> HomeFragment()
AppScreen.Wallet -> WalletFragment()
AppScreen.TopUp -> TopUpFragment()
AppScreen.Send -> SendFragment()
AppScreen.Details -> DetailsFragment()
AppScreen.DetailsConfirm -> DetailsConfirmFragment()

View file

@ -9,4 +9,4 @@ data class NavigationState(
val activity: WeakReference<FragmentActivity>? = null
) : StateType
enum class AppScreen { Home, Wallet, TopUp, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer }
enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer }

View file

@ -26,7 +26,6 @@ import java.math.BigDecimal
class TapWalletManager {
private val payIdManager = PayIdManager()
private val coinMarketCapService = CoinMarketCapService()
private var tapWorkarounds: TapWorkarounds? = null
suspend fun loadWalletData() {
val walletManager = store.state.globalState.scanNoteResponse?.walletManager
@ -84,7 +83,7 @@ class TapWalletManager {
if (addAnalyticsEvent) {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card)
}
tapWorkarounds = TapWorkarounds(data.card)
TapWorkarounds.updateCard(data.card)
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.ResetState)
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
@ -103,7 +102,7 @@ class TapWalletManager {
}
store.dispatch(WalletAction.LoadWallet(
data.walletManager.wallet, data.verifyResponse?.artworkInfo?.id,
tapWorkarounds?.isStart2Coin != true && TapConfig.useTopUp
TapWorkarounds.isStart2Coin != true && TapConfig.useTopUp
))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
store.dispatch(WalletAction.LoadFiatRate)
@ -168,7 +167,7 @@ class TapWalletManager {
when (result) {
is Result.Success -> {
val payId = result.data
if (tapWorkarounds?.isPayIdEnabled() == false) {
if (TapWorkarounds.isPayIdEnabled() == false) {
store.dispatch(WalletAction.DisablePayId)
return@withContext
}

View file

@ -3,17 +3,19 @@ package com.tangem.tap.domain
import com.tangem.commands.Card
import java.util.*
class TapWorkarounds(val card: Card) {
object TapWorkarounds {
val isStart2Coin: Boolean =
card.cardData?.issuerName?.toLowerCase(Locale.US) == START_2_COIN_ISSUER
var isStart2Coin: Boolean = false
private set
fun updateCard(card: Card) {
isStart2Coin = card.cardData?.issuerName?.toLowerCase(Locale.US) == START_2_COIN_ISSUER
}
fun isPayIdEnabled(): Boolean {
if (isStart2Coin) return false
return true
}
companion object {
const val START_2_COIN_ISSUER = "start2coin"
}
const val START_2_COIN_ISSUER = "start2coin"
}

View file

@ -10,20 +10,20 @@ import javax.crypto.spec.SecretKeySpec
class TopUpHelper {
companion object {
const val REDIRECT_URL = "https://success.tangem.com"
// const val REDIRECT_URL = "https://success.tangem.com"
private const val BASE_URL = "https://buy-staging.moonpay.io"
private const val BASE_URL = "https://buy.moonpay.io"
private const val API_KEY_PATH = "?apiKey="
private const val CURRENCY_PATH = "&currencyCode="
private const val WALLET_ADDRESS_PATH = "&walletAddress="
private const val REDIRECT_URL_PATH = "&redirectUrl="
// private const val REDIRECT_URL_PATH = "&redirectUrl="
private const val SIGNATURE_PATH = "&signature="
fun getUrl(cryptoCurrencyName: CryptoCurrencyName, walletAddress: String): String {
val originalQuery = API_KEY_PATH + TapConfig.moonPayApiKey.urlEncode() +
CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
WALLET_ADDRESS_PATH + walletAddress.urlEncode() +
REDIRECT_URL_PATH + REDIRECT_URL.urlEncode()
WALLET_ADDRESS_PATH + walletAddress.urlEncode()
// REDIRECT_URL_PATH + REDIRECT_URL.urlEncode()
val signature = createSignature(originalQuery, TapConfig.moonPayApiSecretKey)
return BASE_URL + originalQuery + SIGNATURE_PATH + signature.urlEncode()

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.details.redux
import com.tangem.commands.Card
import com.tangem.commands.Settings
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TapWorkarounds
import com.tangem.tap.domain.extensions.toSendableAmounts
import org.rekotlin.Action
import java.util.*
@ -112,6 +113,12 @@ private fun handleSecurityAction(
): DetailsState {
return when (action) {
is DetailsAction.ManageSecurity.OpenSecurity -> {
if (TapWorkarounds.isStart2Coin) {
return state.copy(securityScreenState = state.securityScreenState?.copy(
allowedOptions = EnumSet.of(SecurityOption.LongTap),
selectedOption = state.securityScreenState.currentOption
))
}
if (state.card?.isPin2Default == null) {
return state.copy(securityScreenState = state.securityScreenState?.copy(
allowedOptions = EnumSet.noneOf(SecurityOption::class.java)

View file

@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.commands.common.network.Result
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWorkarounds
import com.tangem.tap.domain.isPayIdSupported
import com.tangem.tap.features.send.redux.AddressPayIdActionUi
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
@ -48,7 +49,7 @@ internal class AddressPayIdMiddleware {
private fun setAddressAndCheck(data: String, isUserInput: Boolean, dispatch: (Action) -> Unit) {
val potentialPayId = data.toLowerCase()
if (PayIdManager.isPayId(potentialPayId)) {
if (PayIdManager.isPayId(potentialPayId) && TapWorkarounds.isPayIdEnabled()) {
dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput))
} else {
dispatch(SetWalletAddress(data, isUserInput))
@ -62,7 +63,7 @@ internal class AddressPayIdMiddleware {
val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return
val isUserInput = sendState.addressPayIdState.viewFieldValue.isFromUserInput
if (PayIdManager.isPayId(addressPayId)) {
if (PayIdManager.isPayId(addressPayId) && TapWorkarounds.isPayIdEnabled()) {
verifyPayId(addressPayId, wallet, isUserInput, dispatch)
} else {
verifyAddress(addressPayId, wallet, isUserInput, dispatch)
@ -104,7 +105,7 @@ internal class AddressPayIdMiddleware {
}
private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) {
val supposedAddress = extractAddressFromShareUri(address)
val supposedAddress = extractAddressFromShareUri(address).removeNonAddressData()
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress)
if (failReason == null) {
@ -133,6 +134,8 @@ internal class AddressPayIdMiddleware {
return if (prefixes.isEmpty()) shareUri else shareUri.replace(prefixes[0], "")
}
private fun String.removeNonAddressData(): String = this.substringBefore("?")
private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) {
val addressPayId = input ?: return
val wallet = appState?.sendState?.walletManager?.wallet ?: return
@ -149,7 +152,7 @@ internal class AddressPayIdMiddleware {
}
}
if (PayIdManager.isPayId(addressPayId)) {
if (PayIdManager.isPayId(addressPayId) && TapWorkarounds.isPayIdEnabled()) {
verifyPayId(addressPayId, wallet, false, internalDispatcher)
} else {
verifyAddress(addressPayId, wallet, false, internalDispatcher)

View file

@ -12,11 +12,11 @@ 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.domain.TapWorkarounds
import com.tangem.tap.domain.extensions.minimalAmount
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
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
@ -91,15 +91,19 @@ private fun sendTransaction(
val txData = walletManager.createTransaction(amountToSend, feeAmount, recipientAddress)
scope.launch {
walletManager.update()
val isLinkedTerminal = tangemSdk.config.linkedTerminal
if (TapWorkarounds.isStart2Coin) {
tangemSdk.config.linkedTerminal = false
}
val signer = Signer(tangemSdk, action.messageForSigner)
val result = (walletManager as TransactionSender).send(txData, signer)
withContext(Dispatchers.Main) {
when (result) {
is Result.Success -> {
tangemSdk.config.linkedTerminal = isLinkedTerminal
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.TRANSACTION_IS_SENT, card)
dispatch(SendAction.SendSuccess)
dispatch(GlobalAction.UpdateWalletSignedHashes(result.data.walletSignedHashes))
dispatch(WalletAction.UpdateWallet)
dispatch(NavigationAction.PopBackTo())
}
is Result.Failure -> {

View file

@ -9,6 +9,7 @@ import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.core.view.postDelayed
import androidx.core.widget.addTextChangedListener
import com.google.android.material.textfield.TextInputEditText
import com.tangem.Message
import com.tangem.merchant.common.toggleWidget.ToggleWidget
import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard
@ -48,6 +49,8 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
lateinit var sendBtn: ToggleWidget
private lateinit var etAmountToSend: TextInputEditText
private fun initSendButtonStates() {
sendBtn = ToggleWidget(flSendButtonContainer, btnSend, progress, ProgressState.None())
sendBtn.setupSendButtonStateModifiers(requireContext())
@ -60,6 +63,8 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
etAmountToSend = view.findViewById(R.id.etAmountToSend)
initSendButtonStates()
setupAddressOrPayIdLayout()
setupAmountLayout()

View file

@ -5,14 +5,12 @@ import android.content.Context
import android.text.SpannableStringBuilder
import android.view.ViewGroup
import androidx.core.text.bold
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.enableError
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.common.toggleWidget.ProgressState
import com.tangem.tap.domain.MultiMessageError
import com.tangem.tap.domain.TapWorkarounds
import com.tangem.tap.domain.assembleErrors
import com.tangem.tap.features.send.BaseStoreFragment
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
@ -106,6 +104,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
val til = fg.tilAddressOrPayId
val parsedError = parseError(til.context, state.error)
val hintResId = if (TapWorkarounds.isPayIdEnabled()) {
R.string.send_destination_hint_address_payid
}else {
R.string.send_destination_hint_address
}
til.hint = til.getString(hintResId)
til.parent?.parent?.beginDelayedTransition()
til.error = parsedError
til.isErrorEnabled = parsedError != null

View file

@ -89,8 +89,6 @@ sealed class WalletAction : Action {
object EmptyWallet : WalletAction()
sealed class TopUpAction : WalletAction() {
data class TopUp(val context: Context, val toolbarColor: Int?) : TopUpAction()
data class Start(val url: String, val redirectUrl: String) : TopUpAction()
data class Finish(val topUpCompleted: Boolean) : TopUpAction()
data class TopUp(val context: Context, val toolbarColor: Int) : TopUpAction()
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.redux
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat
import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.SimpleResult
@ -34,7 +35,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Action
import org.rekotlin.Middleware
import timber.log.Timber
class WalletMiddleware {
private val topUpMiddleware = TopUpMiddleware()
@ -73,7 +73,9 @@ class WalletMiddleware {
}
}
is WalletAction.UpdateWallet -> {
scope.launch { store.state.globalState.tapWalletManager.updateWallet() }
if (store.state.walletState.state == ProgressState.Done) {
scope.launch { store.state.globalState.tapWalletManager.updateWallet() }
}
}
is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet)
is WalletAction.LoadWallet.Success -> {
@ -257,16 +259,10 @@ private class TopUpMiddleware {
store.state.walletState.currencyData.currencySymbol!!,
store.state.walletState.addressData!!.address
)
Timber.d(url)
store.dispatch(WalletAction.TopUpAction.Start(url, TopUpHelper.REDIRECT_URL))
store.dispatch(NavigationAction.NavigateTo(AppScreen.TopUp))
}
is WalletAction.TopUpAction.Start -> {
}
is WalletAction.TopUpAction.Finish -> {
if (action.topUpCompleted) store.dispatch(WalletAction.UpdateWallet)
store.dispatch(NavigationAction.PopBackTo())
val customTabsIntent = CustomTabsIntent.Builder()
.setToolbarColor(action.toolbarColor)
.build()
customTabsIntent.launchUrl(action.context, Uri.parse(url));
}
}
}

View file

@ -116,7 +116,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
),
topUpState = TopUpState(false)
)
is WalletAction.UpdateWallet -> newState = newState.copy(updatingWallet = true)
is WalletAction.UpdateWallet -> {
if (store.state.walletState.state == ProgressState.Done) {
newState = newState.copy(updatingWallet = true)
}
}
is WalletAction.UpdateWallet.ScheduleUpdatingWallet ->
newState = newState.copy(updatingWallet = true)
is WalletAction.UpdateWallet.Success -> {
@ -219,9 +223,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
private fun handleTopUpActions(action: WalletAction.TopUpAction, state: TopUpState): TopUpState {
return when (action) {
is WalletAction.TopUpAction.TopUp -> state
is WalletAction.TopUpAction.Start ->
state.copy(url = action.url, redirectUrl = action.redirectUrl)
is WalletAction.TopUpAction.Finish -> state.copy(url = null)
}
}

View file

@ -61,6 +61,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
oldState.walletState == newState.walletState
}.select { it.walletState }
}
store.dispatch(WalletAction.UpdateWallet)
}
override fun onStop() {

View file

@ -1,63 +0,0 @@
package com.tangem.tap.features.wallet.ui.topup
import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_topup.*
import org.rekotlin.StoreSubscriber
class TopUpFragment : Fragment(R.layout.fragment_topup), StoreSubscriber<WalletState> {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
store.dispatch(NavigationAction.PopBackTo())
}
})
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(R.transition.slide_right)
exitTransition = inflater.inflateTransition(R.transition.fade)
}
override fun onStart() {
super.onStart()
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.walletState == newState.walletState
}.select { it.walletState }
}
}
override fun onStop() {
super.onStop()
store.unsubscribe(this)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
web_view.settings.javaScriptEnabled = true
toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
}
override fun newState(state: WalletState) {
if (activity == null) return
if (state.topUpState.url != null) {
web_view.webViewClient = TopUpWebViewClient(progress_bar, state.topUpState.redirectUrl)
web_view.loadUrl(state.topUpState.url)
}
}
}

View file

@ -1,31 +0,0 @@
package com.tangem.tap.features.wallet.ui.topup
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.ProgressBar
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
class TopUpWebViewClient(
private val progressBar: ProgressBar, private val redirectUrl: String?
) : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
if (redirectUrl != null && url.contains(redirectUrl)) {
store.dispatch(WalletAction.TopUpAction.Finish(true))
return true
}
view.loadUrl(url)
return true
}
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
progressBar.hide()
}
init {
progressBar.show()
}
}

View file

@ -11,7 +11,7 @@
android:id="@+id/tilAddressOrPayId"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/send_destination_hint"
android:hint="@string/send_destination_hint_address_payid"
app:boxBackgroundColor="@color/backgroundLightGray"
app:errorIconDrawable="@null"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -51,7 +51,8 @@
<string name="wallet_create_payid_success_message">Ihre PayID wurde erfolgreich erstellt und zum Clipboard kopiert</string>
<string name="wallet_create_payid_error_message">Fehlerantwort beim Erstellen der ZahlungsID</string>
<string name="wallet_create_payid_error_already_created">Diese PayID existiert bereits. Versuchen Sie mit einer anderen erneut.</string>
<string name="send_destination_hint">Adresse oder PayID</string>
<string name="send_destination_hint_address_payid">Adresse oder PayID</string>
<string name="send_destination_hint_address">Adresse</string>
<string name="send_title">Absenden</string>
<string name="send_network_fee_title">Netzgebühr</string>
<string name="send_amount_label">Betrag</string>

View file

@ -52,7 +52,8 @@
<string name="wallet_create_payid_success_message">PayID a été créé avec succès et copié dans le presse-papiers </string>
<string name="wallet_create_payid_error_message">Mauvaise réponse lors de la création de PayID </string>
<string name="wallet_create_payid_error_already_created">Ce PayID existe déjà. Essayez une autre variante.</string>
<string name="send_destination_hint">Adresse ou PayID</string>
<string name="send_destination_hint_address_payid">Adresse ou PayID</string>
<string name="send_destination_hint_address">Adresse</string>
<string name="send_title">Envoyer </string>
<string name="send_network_fee_title">Commissions du réseau </string>
<string name="send_amount_label">Somme </string>

View file

@ -40,7 +40,7 @@
<string name="wallet_pending_tx_receiving_address_format"> da %s </string>
<string name="wallet_address_button_explore">Cerca indirizzo</string>
<string name="wallet_address_button_create_payid">Crea PayID</string>
<string name="wallet_qr_title_format">portafoglio $s</string>
<string name="wallet_qr_title_format">portafoglio %s</string>
<string name="wallet_create_payid">Crea PayID</string>
<string name="wallet_create_payid_card_format">Scheda: %s</string>
<string name="wallet_create_payid_hint">Nome PayID</string>
@ -50,7 +50,8 @@
<string name="wallet_create_payid_success_message">PayID creato con successo e copiato negli appunti</string>
<string name="wallet_create_payid_error_message">Errore di risposta durante la creazione del PayID</string>
<string name="wallet_create_payid_error_already_created">Questo PayID esiste già. Scegline un altro.</string>
<string name="send_destination_hint">Indirizzo o PayID</string>
<string name="send_destination_hint_address_payid">Indirizzo o PayID</string>
<string name="send_destination_hint_address">Indirizzo</string>
<string name="send_title">Invia</string>
<string name="send_network_fee_title">Rete libera</string>
<string name="send_amount_label">Importo</string>

View file

@ -62,7 +62,8 @@
<string name="wallet_create_payid_error_message">Error response while creating PayID</string>
<string name="wallet_create_payid_error_already_created">This PayID already exists. Try a different one.</string>
<string name="send_destination_hint">Address or PayID</string>
<string name="send_destination_hint_address_payid">Address or PayID</string>
<string name="send_destination_hint_address">Address</string>
<string name="send_title">Send</string>
<string name="send_network_fee_title">Network fee</string>
<string name="send_amount_label">Amount</string>