Updated on 2026-08-14
This commit is contained in:
commit
025ffe01f1
46 changed files with 747 additions and 206 deletions
|
|
@ -81,9 +81,33 @@
|
|||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="wc"/>
|
||||
|
||||
<data android:scheme="wc" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="success.tangem.com"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="sell-request.tangem.com"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
|
|
|||
21
app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt
Normal file
21
app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import com.tangem.tap.common.extensions.getColorCompat
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class CustomTabsManager {
|
||||
fun openUrl(url: String, context: Context) {
|
||||
val customTabsIntent = CustomTabsIntent.Builder()
|
||||
.setDefaultColorSchemeParams(
|
||||
CustomTabColorSchemeParams.Builder()
|
||||
.setNavigationBarColor(context.getColorCompat(R.color.toolbarColor))
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
customTabsIntent.launchUrl(context, Uri.parse(url))
|
||||
}
|
||||
}
|
||||
|
|
@ -5,16 +5,20 @@ import android.nfc.NfcAdapter
|
|||
import android.nfc.Tag
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.topup.TradeCryptoHelper
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
|
||||
class IntentHandler {
|
||||
|
||||
fun handleIntent(intent: Intent?) {
|
||||
handleBackgroundScan(intent)
|
||||
handleWalletConnectLink(intent)
|
||||
handleSellCurrencyCallback(intent)
|
||||
}
|
||||
|
||||
private fun handleBackgroundScan(intent: Intent?) {
|
||||
|
|
@ -38,4 +42,26 @@ class IntentHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleSellCurrencyCallback(intent: Intent?) {
|
||||
val transactionID =
|
||||
intent?.data?.getQueryParameter(TradeCryptoHelper.TRANSACTION_ID_PARAM) ?: return
|
||||
val currency =
|
||||
intent.data?.getQueryParameter(TradeCryptoHelper.CURRENCY_CODE_PARAM) ?: return
|
||||
val amount =
|
||||
intent.data?.getQueryParameter(TradeCryptoHelper.CURRENCY_AMOUNT_PARAM) ?: return
|
||||
val destinationAddress =
|
||||
intent.data?.getQueryParameter(TradeCryptoHelper.DEPOSIT_WALLET_ADDRESS_PARAM)
|
||||
?: return
|
||||
|
||||
Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
|
||||
|
||||
store.dispatch(WalletAction.TradeCryptoAction.SendCrypto(
|
||||
currencyId = currency,
|
||||
amount = amount,
|
||||
destinationAddress = destinationAddress,
|
||||
transactionId = transactionID
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -30,6 +30,11 @@ fun Fragment.getColor(@ColorRes colorRes: Int): Int {
|
|||
return ContextCompat.getColor(requireContext(), colorRes)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun Context.getColorCompat(@ColorRes colorRes: Int): Int {
|
||||
return ContextCompat.getColor(this, colorRes)
|
||||
}
|
||||
|
||||
fun View.getString(@StringRes id: Int): String {
|
||||
return context.getString(id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.tap.domain.tasks.ScanNoteResponse
|
|||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import com.tangem.tap.features.feedback.EmailData
|
||||
import com.tangem.tap.features.feedback.FeedbackManager
|
||||
import com.tangem.tap.network.moonpay.MoonPayUserStatus
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class GlobalAction : Action {
|
||||
|
|
@ -43,4 +44,8 @@ sealed class GlobalAction : Action {
|
|||
|
||||
data class ShowDialog(val stateDialog: StateDialog) : GlobalAction()
|
||||
object HideDialog : GlobalAction()
|
||||
|
||||
object GetMoonPayUserStatus : GlobalAction() {
|
||||
data class Success(val moonPayUserStatus: MoonPayUserStatus) : GlobalAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.moonpay.MoonpayService
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
val globalMiddleware: Middleware<AppState> = { dispatch, appState ->
|
||||
|
|
@ -60,6 +65,19 @@ val globalMiddleware: Middleware<AppState> = { dispatch, appState ->
|
|||
store.state.globalState.feedbackManager?.infoHolder
|
||||
?.setWalletsInfo(action.walletManagers)
|
||||
}
|
||||
is GlobalAction.GetMoonPayUserStatus -> {
|
||||
val apiKey = appState()?.globalState?.configManager?.config?.moonPayApiKey
|
||||
if (apiKey != null) {
|
||||
scope.launch {
|
||||
val userStatusResponse = MoonpayService().getUserStatus(apiKey)
|
||||
if (userStatusResponse is Result.Success) {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.GetMoonPayUserStatus.Success(userStatusResponse.data)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nextDispatch(action)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
is GlobalAction.HideDialog -> {
|
||||
globalState.copy(dialog = null)
|
||||
}
|
||||
is GlobalAction.GetMoonPayUserStatus.Success -> {
|
||||
globalState.copy(moonPayUserStatus = action.moonPayUserStatus)
|
||||
}
|
||||
else -> globalState
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
|||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.features.feedback.FeedbackManager
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
import com.tangem.tap.network.moonpay.MoonPayUserStatus
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class GlobalState(
|
||||
|
|
@ -22,7 +23,8 @@ data class GlobalState(
|
|||
val feedbackManager: FeedbackManager? = null,
|
||||
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
|
||||
val scanCardFailsCounter: Int = 0,
|
||||
val dialog: StateDialog? = null
|
||||
val dialog: StateDialog? = null,
|
||||
val moonPayUserStatus: MoonPayUserStatus? = null
|
||||
) : StateType
|
||||
|
||||
typealias CryptoCurrencyName = String
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ sealed class NavigationAction : Action {
|
|||
|
||||
data class PopBackTo(val screen: AppScreen? = null) : NavigationAction()
|
||||
|
||||
data class OpenUrl(val url: String) : NavigationAction()
|
||||
|
||||
data class ActivityCreated(val activity: WeakReference<FragmentActivity>) : NavigationAction()
|
||||
object ActivityDestroyed : NavigationAction()
|
||||
}
|
||||
|
|
@ -1,25 +1,30 @@
|
|||
package com.tangem.tap.common.redux.navigation
|
||||
|
||||
import com.tangem.tap.common.CustomTabsManager
|
||||
import com.tangem.tap.common.extensions.openFragment
|
||||
import com.tangem.tap.common.extensions.popBackTo
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
val navigationMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is NavigationAction) {
|
||||
val navState = store.state.navigationState
|
||||
val navState = state()?.navigationState
|
||||
when (action) {
|
||||
is NavigationAction.NavigateTo -> {
|
||||
navState.activity?.get()?.openFragment(action.screen, action.addToBackstack)
|
||||
navState?.activity?.get()?.openFragment(action.screen, action.addToBackstack)
|
||||
}
|
||||
is NavigationAction.PopBackTo -> {
|
||||
if (action.screen == AppScreen.Home) {
|
||||
navState.activity?.get()?.popBackTo(null, true)
|
||||
navState?.activity?.get()?.popBackTo(null, true)
|
||||
} else {
|
||||
navState.activity?.get()?.popBackTo(action.screen)
|
||||
navState?.activity?.get()?.popBackTo(action.screen)
|
||||
}
|
||||
}
|
||||
is NavigationAction.OpenUrl -> {
|
||||
navState?.activity?.get()?.let {
|
||||
CustomTabsManager().openUrl(action.url, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,5 +27,6 @@ private fun internalReduce(action: Action, state: AppState): NavigationState {
|
|||
}
|
||||
is NavigationAction.ActivityCreated -> navState.copy(activity = navigationAction.activity)
|
||||
is NavigationAction.ActivityDestroyed -> navState.copy(activity = null)
|
||||
else -> navState
|
||||
}
|
||||
}
|
||||
|
|
@ -174,7 +174,11 @@ class TapWalletManager {
|
|||
}
|
||||
}
|
||||
store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id))
|
||||
store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
|
||||
val moonPayUserStatus = store.state.globalState.moonPayUserStatus
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
allowToBuy = config.isTopUpEnabled && moonPayUserStatus?.isBuyAllowed == true,
|
||||
allowToSell = config.isTopUpEnabled && moonPayUserStatus?.isSellAllowed == true,
|
||||
))
|
||||
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
|
||||
store.dispatch(WalletAction.LoadFiatRate())
|
||||
}
|
||||
|
|
@ -245,7 +249,11 @@ class TapWalletManager {
|
|||
return@withContext
|
||||
}
|
||||
val config = store.state.globalState.configManager?.config ?: return@withContext
|
||||
store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
|
||||
val moonpayUserStatus = store.state.globalState.moonPayUserStatus
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
allowToBuy = config.isTopUpEnabled && moonpayUserStatus?.isBuyAllowed == true,
|
||||
allowToSell = config.isTopUpEnabled && moonpayUserStatus?.isSellAllowed == true,
|
||||
))
|
||||
store.dispatch(WalletAction.LoadFiatRate())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ object TapWorkarounds {
|
|||
val cardData = this.cardData ?: return false
|
||||
val productMask = cardData.productMask
|
||||
val excludedBatch = excludedBatches.contains(cardData.batchId)
|
||||
val excludedIssuerName = excludedIssuers.contains(cardData.issuerName?.toUpperCase(Locale.US))
|
||||
val excludedIssuerName = excludedIssuers.contains(cardData.issuerName?.uppercase(Locale.ROOT))
|
||||
val excludedProductMask = (productMask != null && // product mask is on cards v2.30 and later
|
||||
!productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard))
|
||||
return excludedBatch || excludedIssuerName || excludedProductMask
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
package com.tangem.tap.domain.topup
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class TopUpHelper {
|
||||
|
||||
companion object {
|
||||
// const val REDIRECT_URL = "https://success.tangem.com"
|
||||
|
||||
private const val BASE_URL = "https://buy.moonpay.io"
|
||||
private const val API_KEY_PATH = "?apiKey="
|
||||
private const val CURRENCY_PATH = "¤cyCode="
|
||||
private const val WALLET_ADDRESS_PATH = "&walletAddress="
|
||||
|
||||
// private const val REDIRECT_URL_PATH = "&redirectUrl="
|
||||
private const val SIGNATURE_PATH = "&signature="
|
||||
|
||||
fun getUrl(
|
||||
blockchain: Blockchain?,
|
||||
cryptoCurrencyName: CryptoCurrencyName,
|
||||
walletAddress: String,
|
||||
apiKey: String,
|
||||
secretKey: String,
|
||||
): String {
|
||||
if (blockchain?.isTestnet() == true) {
|
||||
return blockchain.getTestnetTopUpUrl() ?: ""
|
||||
}
|
||||
val originalQuery = API_KEY_PATH + apiKey.urlEncode() +
|
||||
CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
|
||||
WALLET_ADDRESS_PATH + walletAddress.urlEncode()
|
||||
// REDIRECT_URL_PATH + REDIRECT_URL.urlEncode()
|
||||
val signature = createSignature(originalQuery, secretKey)
|
||||
|
||||
return BASE_URL + originalQuery + SIGNATURE_PATH + signature.urlEncode()
|
||||
}
|
||||
|
||||
private fun String.urlEncode(): String {
|
||||
return Uri.encode(this)
|
||||
}
|
||||
|
||||
|
||||
private fun createSignature(data: String, key: String): String {
|
||||
val sha256Hmac = Mac.getInstance("HmacSHA256")
|
||||
val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
|
||||
sha256Hmac.init(secretKey)
|
||||
val sha256encoded = sha256Hmac.doFinal(data.toByteArray())
|
||||
return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.tap.domain.topup
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class TradeCryptoHelper {
|
||||
|
||||
companion object {
|
||||
|
||||
const val TRANSACTION_ID_PARAM = "transactionId"
|
||||
const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
|
||||
const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
|
||||
const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
|
||||
|
||||
private const val REDIRECT_URL_BUY = "tangem://success.tangem.com"
|
||||
private const val REDIRECT_URL_SELL_REQUEST = "tangem://sell-request.tangem.com"
|
||||
|
||||
private const val BASE_URL_BUY =
|
||||
"https://buy.moonpay.io"
|
||||
// "https://buy-staging.moonpay.io" // TESTNET
|
||||
private const val BASE_URL_SELL =
|
||||
"https://sell.moonpay.com"
|
||||
// "https://sell-staging.moonpay.com" // TESTNET
|
||||
|
||||
private const val API_KEY_PATH = "?apiKey="
|
||||
private const val CURRENCY_PATH = "¤cyCode="
|
||||
private const val WALLET_ADDRESS_PATH = "&walletAddress="
|
||||
|
||||
private const val REFUND_WALLET_ADDRESS_PATH = "&refundWalletAddress="
|
||||
private const val BASE_CURRENCY_PATH = "&baseCurrencyCode="
|
||||
|
||||
private const val REDIRECT_URL_PATH = "&redirectURL="
|
||||
private const val SIGNATURE_PATH = "&signature="
|
||||
|
||||
private const val TRANSACTION_RECEIPT_PATH = "transaction_receipt?transactionId="
|
||||
|
||||
val AVAILABLE_TO_BUY: Set<String> = setOf(
|
||||
"ZRX", "AAVE", "ALGO", "AXS", "BAT", "BNB", "BUSD", "BTC", "BCH", "BTT", "ADA", "CELO", "CUSD", "LINK", "CHZ", "COMP", "ATOM", "DAI", "DASH", "MANA", "DGB", "DOGE", "EGLD",
|
||||
"ENJ", "EOS", "ETC", "ETH", "KETH", "RINKETH", "FIL", "HBAR", "MIOTA", "KAVA", "KLAY", "LBC", "LTC", "LUNA", "MKR", "OM", "MATIC", "NANO", "NEAR", "XEM", "NEO", "NIM", "OKB",
|
||||
"OMG", "ONG", "ONT", "DOT", "QTUM", "RVN", "RFUEL", "KEY", "SRM", "SOL", "XLM", "STMX", "SNX", "KRT", "UST", "USDT", "XTZ", "RUNE", "SAND", "TOMO", "AVA", "TRX", "TUSD", "UNI",
|
||||
"USDC", "UTK", "VET", "WAXP", "WBTC", "XRP", "ZEC", "ZIL"
|
||||
)
|
||||
|
||||
val AVAILABLE_TO_SELL: Set<String> = setOf("BTC", "ETH", "BCH")
|
||||
|
||||
fun getUrl(
|
||||
action: Action,
|
||||
blockchain: Blockchain?,
|
||||
cryptoCurrencyName: CryptoCurrencyName,
|
||||
walletAddress: String,
|
||||
apiKey: String,
|
||||
secretKey: String,
|
||||
): String {
|
||||
val originalQuery: String
|
||||
val baseUrl: String
|
||||
when (action) {
|
||||
Action.Buy -> {
|
||||
if (blockchain?.isTestnet() == true) {
|
||||
return blockchain.getTestnetTopUpUrl() ?: ""
|
||||
}
|
||||
baseUrl = BASE_URL_BUY
|
||||
originalQuery = API_KEY_PATH + apiKey.urlEncode() +
|
||||
CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
|
||||
WALLET_ADDRESS_PATH + walletAddress.urlEncode() +
|
||||
REDIRECT_URL_PATH + REDIRECT_URL_BUY.urlEncode()
|
||||
}
|
||||
Action.Sell -> {
|
||||
baseUrl = BASE_URL_SELL
|
||||
originalQuery = API_KEY_PATH + apiKey.urlEncode() +
|
||||
BASE_CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
|
||||
REFUND_WALLET_ADDRESS_PATH + walletAddress.urlEncode() +
|
||||
REDIRECT_URL_PATH + REDIRECT_URL_SELL_REQUEST.urlEncode()
|
||||
}
|
||||
}
|
||||
val signature = createSignature(originalQuery, secretKey)
|
||||
return baseUrl + originalQuery + SIGNATURE_PATH + signature.urlEncode()
|
||||
|
||||
}
|
||||
|
||||
fun getSellCryptoReceiptUrl(transactionId: String): String {
|
||||
return BASE_URL_SELL + TRANSACTION_RECEIPT_PATH + transactionId
|
||||
}
|
||||
|
||||
private fun String.urlEncode(): String {
|
||||
return Uri.encode(this)
|
||||
}
|
||||
|
||||
private fun createSignature(data: String, key: String): String {
|
||||
val sha256Hmac = Mac.getInstance("HmacSHA256")
|
||||
val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
|
||||
sha256Hmac.init(secretKey)
|
||||
val sha256encoded = sha256Hmac.doFinal(data.toByteArray())
|
||||
return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Action { Buy, Sell }
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.global.StateDialog
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeDialog
|
||||
|
|
@ -66,6 +67,8 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState
|
|||
|
||||
btn_yes?.setOnClickListener { store.dispatch(HomeAction.ReadCard) }
|
||||
btn_shop?.setOnClickListener { store.dispatch(HomeAction.GoToShop(requireContext())) }
|
||||
|
||||
store.dispatch(GlobalAction.GetMoonPayUserStatus)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.transition.TransitionInflater
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_wallet.*
|
||||
|
|
@ -28,7 +29,14 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
|
|||
super.onCreate(savedInstanceState)
|
||||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
val externalTransactionData = store.state.sendState.externalTransactionData
|
||||
if (externalTransactionData == null) {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
} else {
|
||||
store.dispatch(
|
||||
WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
|
|
@ -43,7 +51,7 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
toolbar.setNavigationOnClickListener { store.dispatch(NavigationAction.PopBackTo()) }
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ sealed class ReceiptAction : SendScreenAction {
|
|||
|
||||
sealed class SendActionUi : SendScreenActionUi {
|
||||
data class SendAmountToRecipient(val messageForSigner: Message) : SendScreenActionUi
|
||||
object CheckIfTransactionDataWasProvided : SendScreenActionUi
|
||||
}
|
||||
|
||||
sealed class SendAction : SendScreenAction {
|
||||
|
|
@ -158,4 +159,11 @@ sealed class SendAction : SendScreenAction {
|
|||
object Update : SendAction()
|
||||
data class Set(val warningList: List<WarningMessage>) : SendAction()
|
||||
}
|
||||
|
||||
data class SendSpecificTransaction(
|
||||
val sendAmount: String,
|
||||
val destinationAddress: String,
|
||||
val transactionId: String
|
||||
) : SendAction()
|
||||
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.blockchain.extensions.SimpleResult
|
|||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -20,6 +21,8 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
|||
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.ExternalTransactionData
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.redux.states.SendButtonState
|
||||
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
|
|
@ -49,6 +52,18 @@ val sendMiddleware: Middleware<AppState> = { dispatch, appState ->
|
|||
verifyAndSendTransaction(action, appState(), dispatch)
|
||||
is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch)
|
||||
is SendAction.Warnings.Update -> updateWarnings(dispatch)
|
||||
is SendActionUi.CheckIfTransactionDataWasProvided -> {
|
||||
val transactionData = appState()?.sendState?.externalTransactionData
|
||||
if (transactionData != null) {
|
||||
store.dispatchOnMain(AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
|
||||
transactionData.destinationAddress, false
|
||||
))
|
||||
store.dispatchOnMain(AmountActionUi.SetMainCurrency(MainCurrencyType.CRYPTO))
|
||||
store.dispatchOnMain(AmountActionUi.HandleUserInput(transactionData.amount))
|
||||
store.dispatchOnMain(AmountAction.SetAmount(transactionData.amount.toBigDecimal(),
|
||||
false))
|
||||
}
|
||||
}
|
||||
}
|
||||
nextDispatch(action)
|
||||
}
|
||||
|
|
@ -76,8 +91,11 @@ private fun verifyAndSendTransaction(
|
|||
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
|
||||
dispatch(AmountActionUi.CheckAmountToSend)
|
||||
}, sendAllCallback = {
|
||||
sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
|
||||
sendState.transactionExtrasState, card, dispatch)
|
||||
sendTransaction(
|
||||
action, walletManager, amountToSend, feeAmount, destinationAddress,
|
||||
sendState.transactionExtrasState, card, sendState.externalTransactionData,
|
||||
dispatch
|
||||
)
|
||||
}, reduceAmount))
|
||||
}
|
||||
transactionErrors.isNotEmpty() -> {
|
||||
|
|
@ -85,7 +103,7 @@ private fun verifyAndSendTransaction(
|
|||
}
|
||||
else -> {
|
||||
sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
|
||||
sendState.transactionExtrasState, card, dispatch)
|
||||
sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +116,7 @@ private fun sendTransaction(
|
|||
destinationAddress: String,
|
||||
transactionExtras: TransactionExtrasState,
|
||||
card: Card,
|
||||
externalTransactionData: ExternalTransactionData?,
|
||||
dispatch: (Action) -> Unit,
|
||||
) {
|
||||
dispatch(SendAction.ChangeSendButtonState(SendButtonState.PROGRESS))
|
||||
|
|
@ -134,7 +153,12 @@ private fun sendTransaction(
|
|||
blockchain = walletManager.wallet.blockchain
|
||||
)
|
||||
dispatch(SendAction.SendSuccess)
|
||||
dispatch(NavigationAction.PopBackTo())
|
||||
|
||||
if (externalTransactionData != null) {
|
||||
dispatch(WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId))
|
||||
} else {
|
||||
dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
scope.launch(Dispatchers.IO) {
|
||||
withContext(Dispatchers.Main) {
|
||||
dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.send.redux.reducers
|
|||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.tap.common.CurrencyConverter
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
import com.tangem.tap.features.send.redux.states.ExternalTransactionData
|
||||
import com.tangem.tap.features.send.redux.states.IdStateHolder
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -45,11 +46,31 @@ private class SendReducer : SendInternalReducer {
|
|||
is SendAction.Dialog.SendTransactionFails -> sendState.copy(dialog = action)
|
||||
is SendAction.Dialog.Hide -> sendState.copy(dialog = null)
|
||||
is SendAction.Warnings.Set -> sendState.copy(sendWarningsList = action.warningList)
|
||||
is SendAction.SendSpecificTransaction ->
|
||||
handleSendSpecificTransactionAction(action, sendState)
|
||||
else -> return sendState
|
||||
}
|
||||
|
||||
return updateLastState(result, result)
|
||||
}
|
||||
|
||||
private fun handleSendSpecificTransactionAction(
|
||||
action: SendAction.SendSpecificTransaction, state: SendState
|
||||
): SendState {
|
||||
return state.copy(
|
||||
externalTransactionData =
|
||||
ExternalTransactionData(action.sendAmount, action.destinationAddress, action.transactionId),
|
||||
feeState = state.feeState.copy(
|
||||
includeFeeSwitcherIsEnabled = false
|
||||
),
|
||||
amountState = state.amountState.copy(
|
||||
inputIsEnabled = false
|
||||
),
|
||||
addressPayIdState = state.addressPayIdState.copy(
|
||||
inputIsEnabled = false
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class EmptyReducer : SendInternalReducer {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ data class AddressPayIdState(
|
|||
val error: AddressPayIdVerifyAction.Error? = null,
|
||||
val truncateHandler: ((String) -> String)? = null,
|
||||
val sendingToPayIdEnabled: Boolean = false,
|
||||
val pasteIsEnabled: Boolean = false
|
||||
val pasteIsEnabled: Boolean = false,
|
||||
val inputIsEnabled: Boolean = true
|
||||
) : SendScreenState {
|
||||
|
||||
override val stateId: StateId = StateId.ADDRESS_PAY_ID
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ data class SendState(
|
|||
val receiptState: ReceiptState = ReceiptState(),
|
||||
val sendWarningsList: List<WarningMessage> = listOf(),
|
||||
val sendButtonState: SendButtonState = SendButtonState.DISABLED,
|
||||
val dialog: StateDialog? = null
|
||||
val dialog: StateDialog? = null,
|
||||
val externalTransactionData: ExternalTransactionData? = null
|
||||
) : SendScreenState {
|
||||
|
||||
override val stateId: StateId = StateId.SEND_SCREEN
|
||||
|
|
@ -131,7 +132,8 @@ data class AmountState(
|
|||
val cursorAtTheSamePosition: Boolean = true,
|
||||
val maxLengthOfAmount: Int = 2,
|
||||
val decimalSeparator: String = ".",
|
||||
val error: TapError? = null
|
||||
val error: TapError? = null,
|
||||
val inputIsEnabled: Boolean = true,
|
||||
) : SendScreenState {
|
||||
|
||||
override val stateId: StateId = StateId.AMOUNT
|
||||
|
|
@ -166,4 +168,12 @@ data class MainCurrency(
|
|||
val type: MainCurrencyType,
|
||||
val currencySymbol: String,
|
||||
val isEnabled: Boolean = true
|
||||
)
|
||||
|
||||
data class ExternalTransactionData(
|
||||
val amount: String,
|
||||
val destinationAddress: String,
|
||||
val transactionId: String,
|
||||
val canAmountBeModified: Boolean = false,
|
||||
val canDestinationAddressBeModified: Boolean = false
|
||||
)
|
||||
|
|
@ -71,6 +71,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
setupAmountLayout()
|
||||
setupFeeLayout()
|
||||
setupWarningMessages()
|
||||
store.dispatch(SendActionUi.CheckIfTransactionDataWasProvided)
|
||||
}
|
||||
|
||||
private fun initSendButtonStates() {
|
||||
|
|
@ -250,7 +251,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE)
|
||||
val mainCurrency = sp.getString("mainCurrency", TapCurrency.DEFAULT_FIAT_CURRENCY)
|
||||
val foundType = MainCurrencyType.values()
|
||||
.firstOrNull { it.name.toLowerCase() == mainCurrency!!.toLowerCase() }
|
||||
.firstOrNull { it.name.equals(mainCurrency!!, ignoreCase = true) }
|
||||
?: MainCurrencyType.CRYPTO
|
||||
return foundType
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,6 +156,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
val til = fg.tilAddressOrPayId
|
||||
val parsedError = parseError(til.context, state.error)
|
||||
|
||||
til.isEnabled = state.inputIsEnabled
|
||||
fg.imvPaste.show(state.inputIsEnabled)
|
||||
fg.imvQrCode.show(state.inputIsEnabled)
|
||||
fg.flPaste.show(state.inputIsEnabled)
|
||||
fg.flQrCode.show(state.inputIsEnabled)
|
||||
|
||||
val hintResId = if (state.sendingToPayIdEnabled) {
|
||||
R.string.send_destination_hint_address_payid
|
||||
} else {
|
||||
|
|
@ -198,6 +204,14 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
state.mainCurrency.currencySymbol,
|
||||
state.viewBalanceValue)
|
||||
fg.tvBalance.update(balanceText)
|
||||
|
||||
fg.tilAmountToSend.isEnabled = state.inputIsEnabled
|
||||
|
||||
val imageRes = if (state.inputIsEnabled) R.drawable.ic_arrows_up_down else 0
|
||||
fg.tvAmountCurrency.setCompoundDrawablesWithIntrinsicBounds(0, 0, imageRes, 0)
|
||||
val textColor = if (state.inputIsEnabled) R.color.blue else R.color.textGray
|
||||
fg.tvAmountCurrency.setTextColor(fg.getColor(textColor))
|
||||
|
||||
}
|
||||
|
||||
private fun handleFeeState(fg: BaseStoreFragment, state: FeeState) {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,11 @@ sealed class WalletAction : Action {
|
|||
}
|
||||
|
||||
|
||||
data class LoadWallet(val allowTopUp: Boolean? = null, val blockchain: Blockchain? = null) : WalletAction() {
|
||||
data class LoadWallet(
|
||||
val allowToSell: Boolean? = null, val allowToBuy: Boolean? = null,
|
||||
val blockchain: Blockchain? = null,
|
||||
) :
|
||||
WalletAction() {
|
||||
data class Success(val wallet: Wallet) : WalletAction()
|
||||
data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : WalletAction()
|
||||
data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction()
|
||||
|
|
@ -39,13 +43,16 @@ sealed class WalletAction : Action {
|
|||
data class AddWalletManagers(val walletManagers: List<WalletManager>) : MultiWallet() {
|
||||
constructor(walletManager: WalletManager) : this(listOf(walletManager))
|
||||
}
|
||||
|
||||
data class AddBlockchain(val blockchain: Blockchain) : MultiWallet()
|
||||
data class AddBlockchains(val blockchains: List<Blockchain>) : MultiWallet()
|
||||
data class AddTokens(val tokens: List<Token>) : MultiWallet()
|
||||
data class AddToken(val token: Token) : MultiWallet()
|
||||
data class SaveCurrencies(val cardCurrencies: CardCurrencies) : MultiWallet()
|
||||
object FindTokensInUse : MultiWallet()
|
||||
data class FindBlockchainsInUse(val card: Card, val factory: WalletManagerFactory) : MultiWallet()
|
||||
data class FindBlockchainsInUse(val card: Card, val factory: WalletManagerFactory) :
|
||||
MultiWallet()
|
||||
|
||||
data class TokenLoaded(val amount: Amount, val token: Token) : MultiWallet()
|
||||
data class SelectWallet(val walletData: WalletData?) : MultiWallet()
|
||||
data class RemoveWallet(val walletData: WalletData) : MultiWallet()
|
||||
|
|
@ -69,6 +76,7 @@ sealed class WalletAction : Action {
|
|||
object SetNeverToShow : Warnings()
|
||||
object RemindLater : Warnings()
|
||||
}
|
||||
|
||||
class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings()
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +87,7 @@ sealed class WalletAction : Action {
|
|||
}
|
||||
|
||||
data class LoadFiatRate(
|
||||
val wallet: Wallet? = null, val currency: Currency? = null
|
||||
val wallet: Wallet? = null, val currency: Currency? = null,
|
||||
) : WalletAction() {
|
||||
data class Success(val fiatRate: Pair<Currency, BigDecimal?>) : WalletAction()
|
||||
object Failure : WalletAction()
|
||||
|
|
@ -114,6 +122,7 @@ sealed class WalletAction : Action {
|
|||
object QrCode : WalletAction()
|
||||
object ScanFails : WalletAction()
|
||||
object SignedHashesMultiWalletDialog : WalletAction()
|
||||
object ChooseTradeActionDialog : WalletAction()
|
||||
}
|
||||
|
||||
object HideDialog : WalletAction()
|
||||
|
|
@ -123,8 +132,16 @@ sealed class WalletAction : Action {
|
|||
object CreateWallet : WalletAction()
|
||||
object EmptyWallet : WalletAction()
|
||||
|
||||
sealed class TopUpAction : WalletAction() {
|
||||
data class TopUp(val context: Context, val toolbarColor: Int) : TopUpAction()
|
||||
sealed class TradeCryptoAction : WalletAction() {
|
||||
object Buy : TradeCryptoAction()
|
||||
object Sell : TradeCryptoAction()
|
||||
data class FinishSelling(val transactionId: String) : TradeCryptoAction()
|
||||
data class SendCrypto(
|
||||
val currencyId: String,
|
||||
val amount: String,
|
||||
val destinationAddress: String,
|
||||
val transactionId: String
|
||||
) : TradeCryptoAction()
|
||||
}
|
||||
|
||||
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
|
||||
|
|
@ -133,8 +150,8 @@ sealed class WalletAction : Action {
|
|||
object ShowOnboarding : TwinsAction()
|
||||
object SetOnboardingShown : TwinsAction()
|
||||
data class SetTwinCard(
|
||||
val secondCardId: String, val number: TwinCardNumber,
|
||||
val isCreatingTwinCardsAllowed: Boolean,
|
||||
val secondCardId: String, val number: TwinCardNumber,
|
||||
val isCreatingTwinCardsAllowed: Boolean,
|
||||
) : TwinsAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
|||
import com.tangem.tap.common.redux.global.StateDialog
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.topup.TradeCryptoHelper
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
|
|
@ -34,6 +35,7 @@ data class WalletState(
|
|||
val primaryBlockchain: Blockchain? = null,
|
||||
val primaryToken: Token? = null,
|
||||
val isTestnet: Boolean = false,
|
||||
val tradeCryptoAllowed: TradeCryptoAvailability = TradeCryptoAvailability()
|
||||
) : StateType {
|
||||
|
||||
val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null
|
||||
|
|
@ -141,6 +143,7 @@ sealed class WalletDialog: StateDialog {
|
|||
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog()
|
||||
object ScanFailsDialog : WalletDialog()
|
||||
object SignedHashesMultiWalletDialog : WalletDialog()
|
||||
object ChooseTradeActionDialog : WalletDialog()
|
||||
}
|
||||
|
||||
enum class ProgressState { Loading, Done, Error }
|
||||
|
|
@ -180,10 +183,15 @@ data class Artwork(
|
|||
}
|
||||
}
|
||||
|
||||
data class TopUpState(
|
||||
val allowed: Boolean = true,
|
||||
val url: String? = null,
|
||||
val redirectUrl: String? = null
|
||||
data class TradeCryptoState(
|
||||
val sellingAllowed: Boolean = false,
|
||||
val buyingAllowed: Boolean = false,
|
||||
)
|
||||
|
||||
data class TradeCryptoAvailability(
|
||||
val sellingAllowed: Boolean = false,
|
||||
val buyingAllowed: Boolean = false,
|
||||
val availableToSell: Set<String> = TradeCryptoHelper.AVAILABLE_TO_SELL,
|
||||
)
|
||||
|
||||
data class TwinCardsState(
|
||||
|
|
@ -194,17 +202,17 @@ data class TwinCardsState(
|
|||
)
|
||||
|
||||
data class WalletData(
|
||||
val pendingTransactions: List<PendingTransaction> = emptyList(),
|
||||
val hashesCountVerified: Boolean? = null,
|
||||
val walletAddresses: WalletAddresses? = null,
|
||||
val currencyData: BalanceWidgetData = BalanceWidgetData(),
|
||||
val updatingWallet: Boolean = false,
|
||||
val topUpState: TopUpState = TopUpState(),
|
||||
val allowToSend: Boolean = true,
|
||||
val fiatRateString: String? = null,
|
||||
val fiatRate: BigDecimal? = null,
|
||||
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
|
||||
val currency: Currency? = null
|
||||
val pendingTransactions: List<PendingTransaction> = emptyList(),
|
||||
val hashesCountVerified: Boolean? = null,
|
||||
val walletAddresses: WalletAddresses? = null,
|
||||
val currencyData: BalanceWidgetData = BalanceWidgetData(),
|
||||
val updatingWallet: Boolean = false,
|
||||
val tradeCryptoState: TradeCryptoState = TradeCryptoState(),
|
||||
val allowToSend: Boolean = true,
|
||||
val fiatRateString: String? = null,
|
||||
val fiatRate: BigDecimal? = null,
|
||||
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
|
||||
val currency: Currency? = null
|
||||
) {
|
||||
fun shouldShowMultipleAddress(): Boolean {
|
||||
val listOfAddresses = walletAddresses?.list ?: return false
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.tap.domain.topup.TopUpHelper
|
||||
import com.tangem.tap.domain.topup.TopUpManager
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class TopUpMiddleware {
|
||||
fun handle(action: WalletAction.TopUpAction) {
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction.TopUp -> {
|
||||
val selectedWalletData = store.state.walletState.getSelectedWalletData()
|
||||
val config = store.state.globalState.configManager?.config ?: return
|
||||
val addresses = selectedWalletData?.walletAddresses ?: return
|
||||
if (addresses.list.isEmpty()) return
|
||||
|
||||
val defaultAddress = addresses.list[0].address
|
||||
val currency = selectedWalletData.currency
|
||||
|
||||
if (currency is Currency.Token && currency.blockchain.isTestnet()) {
|
||||
val walletManager = store.state.walletState.getWalletManager(currency.token)
|
||||
if (walletManager !is EthereumWalletManager) return
|
||||
|
||||
scope.launch {
|
||||
TopUpManager().topUpTestErc20Tokens(
|
||||
walletManager = walletManager, token = currency.token
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val url = TopUpHelper.getUrl(
|
||||
currency?.blockchain,
|
||||
selectedWalletData.currencyData.currencySymbol!!,
|
||||
defaultAddress,
|
||||
config.moonPayApiKey,
|
||||
config.moonPayApiSecretKey
|
||||
)
|
||||
|
||||
val customTabsIntent = CustomTabsIntent.Builder()
|
||||
.setToolbarColor(action.toolbarColor)
|
||||
.build()
|
||||
customTabsIntent.launchUrl(action.context, Uri.parse(url))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.topup.TopUpManager
|
||||
import com.tangem.tap.domain.topup.TradeCryptoHelper
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
class TradeCryptoMiddleware {
|
||||
fun handle(action: WalletAction.TradeCryptoAction) {
|
||||
when (action) {
|
||||
is WalletAction.TradeCryptoAction.Buy -> startExchange(action)
|
||||
is WalletAction.TradeCryptoAction.Sell -> startExchange(action)
|
||||
is WalletAction.TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action)
|
||||
is WalletAction.TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startExchange(action: WalletAction.TradeCryptoAction) {
|
||||
val selectedWalletData = store.state.walletState.getSelectedWalletData()
|
||||
val config = store.state.globalState.configManager?.config ?: return
|
||||
val addresses = selectedWalletData?.walletAddresses ?: return
|
||||
if (addresses.list.isEmpty()) return
|
||||
|
||||
val defaultAddress = addresses.list[0].address
|
||||
val currency = selectedWalletData.currency
|
||||
val currencySymbol = selectedWalletData.currency?.currencySymbol ?: return
|
||||
|
||||
val exchangeAction = if (action is WalletAction.TradeCryptoAction.Buy) {
|
||||
TradeCryptoHelper.Action.Buy
|
||||
} else {
|
||||
TradeCryptoHelper.Action.Sell
|
||||
}
|
||||
|
||||
if (exchangeAction == TradeCryptoHelper.Action.Buy &&
|
||||
currency is Currency.Token && currency.blockchain.isTestnet()
|
||||
) {
|
||||
val walletManager = store.state.walletState.getWalletManager(currency.token)
|
||||
if (walletManager !is EthereumWalletManager) return
|
||||
|
||||
scope.launch {
|
||||
TopUpManager().topUpTestErc20Tokens(
|
||||
walletManager = walletManager, token = currency.token
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val url = TradeCryptoHelper.getUrl(
|
||||
exchangeAction,
|
||||
currency?.blockchain,
|
||||
currencySymbol,
|
||||
defaultAddress,
|
||||
config.moonPayApiKey,
|
||||
config.moonPayApiSecretKey
|
||||
)
|
||||
Timber.d("Moonpay $exchangeAction URL: $url")
|
||||
store.dispatchOnMain(NavigationAction.OpenUrl(url))
|
||||
}
|
||||
|
||||
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
|
||||
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
|
||||
val walletManager =
|
||||
store.state.walletState.getWalletManager(selectedWalletData.currency)
|
||||
store.dispatchOnMain(PrepareSendScreen(
|
||||
coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin),
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
walletManager = walletManager
|
||||
))
|
||||
store.dispatchOnMain(SendAction.SendSpecificTransaction(
|
||||
sendAmount = action.amount,
|
||||
destinationAddress = action.destinationAddress,
|
||||
transactionId = action.transactionId
|
||||
))
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
|
||||
private fun openReceiptUrl(transactionId: String) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
val url = TradeCryptoHelper.getSellCryptoReceiptUrl(transactionId)
|
||||
store.dispatchOnMain(NavigationAction.OpenUrl(url))
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ import org.rekotlin.Action
|
|||
import org.rekotlin.Middleware
|
||||
|
||||
class WalletMiddleware {
|
||||
private val topUpMiddleware = TopUpMiddleware()
|
||||
private val tradeCryptoMiddleware = TradeCryptoMiddleware()
|
||||
private val twinsMiddleware = TwinsMiddleware()
|
||||
private val warningsMiddleware = WarningsMiddleware()
|
||||
private val multiWalletMiddleware = MultiWalletMiddleware()
|
||||
|
|
@ -39,7 +39,7 @@ class WalletMiddleware {
|
|||
val globalState = state()?.globalState
|
||||
val walletState = state()?.walletState
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction -> topUpMiddleware.handle(action)
|
||||
is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(action)
|
||||
is WalletAction.TwinsAction -> twinsMiddleware.handle(action)
|
||||
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
|
||||
is WalletAction.MultiWallet ->
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.extensions.toFiatString
|
||||
|
|
@ -38,8 +37,12 @@ class MultiWalletReducer {
|
|||
),
|
||||
walletAddresses = createAddressList(wallet),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = false),
|
||||
currency = Currency.Blockchain(blockchain)
|
||||
currency = Currency.Blockchain(blockchain),
|
||||
tradeCryptoState = TradeCryptoState(
|
||||
sellingAllowed = state.tradeCryptoAllowed.sellingAllowed &&
|
||||
state.tradeCryptoAllowed.availableToSell.contains(blockchain.currency),
|
||||
buyingAllowed = state.tradeCryptoAllowed.buyingAllowed
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -63,8 +66,12 @@ class MultiWalletReducer {
|
|||
),
|
||||
walletAddresses = createAddressList(wallet),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = false),
|
||||
currency = Currency.Blockchain(action.blockchain)
|
||||
currency = Currency.Blockchain(action.blockchain),
|
||||
tradeCryptoState = TradeCryptoState(
|
||||
sellingAllowed = state.tradeCryptoAllowed.sellingAllowed &&
|
||||
state.tradeCryptoAllowed.availableToSell.contains(action.blockchain.currency),
|
||||
buyingAllowed = state.tradeCryptoAllowed.buyingAllowed
|
||||
)
|
||||
)
|
||||
val newState = state.copy(wallets = state.replaceWalletInWallets(walletData))
|
||||
if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) {
|
||||
|
|
@ -165,7 +172,11 @@ fun Token.toWallet(state: WalletState): WalletData? {
|
|||
),
|
||||
walletAddresses = walletAddresses,
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = false),
|
||||
currency = Currency.Token(this)
|
||||
currency = Currency.Token(this),
|
||||
tradeCryptoState = TradeCryptoState(
|
||||
sellingAllowed = state.tradeCryptoAllowed.sellingAllowed &&
|
||||
state.tradeCryptoAllowed.availableToSell.contains(this.symbol),
|
||||
buyingAllowed = state.tradeCryptoAllowed.buyingAllowed
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -50,7 +50,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
WalletData(
|
||||
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
|
||||
mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed),
|
||||
topUpState = TopUpState(false)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -78,7 +77,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
wallets = listOf(
|
||||
WalletData(
|
||||
currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
|
||||
topUpState = TopUpState(false)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -95,6 +93,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
is WalletAction.LoadWallet -> {
|
||||
if (action.blockchain == null) {
|
||||
val wallets = newState.wallets.map { wallet ->
|
||||
|
||||
wallet.copy(
|
||||
currencyData = wallet.currencyData.copy(
|
||||
status = BalanceStatus.Loading,
|
||||
|
|
@ -102,15 +101,22 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
currencySymbol = wallet.currencyData.currencySymbol,
|
||||
),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(
|
||||
allowed = action.allowTopUp
|
||||
?: wallet.topUpState.allowed
|
||||
tradeCryptoState = TradeCryptoState(
|
||||
sellingAllowed = action.allowToSell ?: wallet.tradeCryptoState.sellingAllowed &&
|
||||
state.walletState.tradeCryptoAllowed.availableToSell.contains(wallet.currencyData.currencySymbol),
|
||||
buyingAllowed = action.allowToBuy
|
||||
?: wallet.tradeCryptoState.buyingAllowed
|
||||
)
|
||||
)
|
||||
}
|
||||
val tradeCryptoAllowed = TradeCryptoAvailability(
|
||||
sellingAllowed = action.allowToSell ?: false,
|
||||
buyingAllowed = action.allowToBuy ?: false
|
||||
)
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Loading,
|
||||
wallets = wallets
|
||||
wallets = wallets,
|
||||
tradeCryptoAllowed = tradeCryptoAllowed
|
||||
)
|
||||
} else {
|
||||
val walletManager = newState.getWalletManager(action.blockchain) ?: return newState
|
||||
|
|
@ -126,8 +132,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
currencySymbol = wallet.currencyData.currencySymbol,
|
||||
),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(
|
||||
allowed = action.allowTopUp ?: wallet.topUpState.allowed
|
||||
tradeCryptoState = TradeCryptoState(
|
||||
sellingAllowed = action.allowToSell ?: wallet.tradeCryptoState.sellingAllowed &&
|
||||
state.walletState.tradeCryptoAllowed.availableToSell.contains(wallet.currencyData.currencySymbol),
|
||||
buyingAllowed = action.allowToBuy
|
||||
?: wallet.tradeCryptoState.buyingAllowed
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -172,7 +181,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
status = BalanceStatus.Unreachable,
|
||||
errorMessage = message
|
||||
),
|
||||
topUpState = TopUpState(false)
|
||||
)
|
||||
val tokenWallets = action.wallet.getTokens()
|
||||
.mapNotNull { newState.getWalletData(it) }
|
||||
|
|
@ -242,6 +250,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
is WalletAction.ShowDialog.SignedHashesMultiWalletDialog -> {
|
||||
newState = newState.copy(walletDialog = WalletDialog.SignedHashesMultiWalletDialog)
|
||||
}
|
||||
is WalletAction.ShowDialog.ChooseTradeActionDialog -> {
|
||||
newState = newState.copy(walletDialog = WalletDialog.ChooseTradeActionDialog)
|
||||
}
|
||||
is WalletAction.HideDialog -> {
|
||||
newState = newState.copy(walletDialog = null)
|
||||
}
|
||||
|
|
@ -251,7 +262,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
)
|
||||
}
|
||||
is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null)
|
||||
is WalletAction.TopUpAction -> return newState
|
||||
is WalletAction.TradeCryptoAction -> return newState
|
||||
is WalletAction.ChangeSelectedAddress -> {
|
||||
val selectedWalletData = newState.getWalletData(newState.selectedWallet)
|
||||
|
||||
|
|
|
|||
|
|
@ -86,11 +86,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
|
|||
|
||||
btn_share.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
|
||||
|
||||
btn_top_up.setOnClickListener {
|
||||
store.dispatch(
|
||||
WalletAction.TopUpAction.TopUp(requireContext(), R.color.backgroundLightGray)
|
||||
)
|
||||
}
|
||||
btn_top_up.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) }
|
||||
|
||||
btn_sell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
|
||||
}
|
||||
|
||||
override fun newState(state: WalletState) {
|
||||
|
|
@ -131,8 +129,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
|
|||
srl_wallet_details.setOnRefreshListener {
|
||||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
selectedWallet.topUpState.allowed,
|
||||
selectedWallet.currency?.blockchain
|
||||
allowToBuy = selectedWallet.tradeCryptoState.buyingAllowed,
|
||||
allowToSell = selectedWallet.tradeCryptoState.sellingAllowed,
|
||||
blockchain = selectedWallet.currency?.blockchain
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -140,6 +139,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
|
|||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
srl_wallet_details.isRefreshing = false
|
||||
}
|
||||
|
||||
btn_top_up.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed
|
||||
btn_sell.show(selectedWallet.tradeCryptoState.sellingAllowed)
|
||||
}
|
||||
|
||||
private fun handleCurrencyIcon(wallet: WalletData) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.dialog_wallet_trade.*
|
||||
|
||||
class ChooseTradeActionDialog(context: Context) : BottomSheetDialog(context) {
|
||||
|
||||
init {
|
||||
this.setContentView(R.layout.dialog_wallet_trade)
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
|
||||
this.setOnDismissListener {
|
||||
store.dispatch(WalletAction.HideDialog)
|
||||
}
|
||||
|
||||
dialog_btn_buy.setOnClickListener {
|
||||
store.dispatch(WalletAction.TradeCryptoAction.Buy)
|
||||
dismiss()
|
||||
}
|
||||
dialog_btn_sell.setOnClickListener {
|
||||
store.dispatch(WalletAction.TradeCryptoAction.Sell)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,10 +16,7 @@ import com.tangem.tap.features.wallet.ui.BalanceWidget
|
|||
import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.QrDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.*
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.card_balance.*
|
||||
|
|
@ -124,12 +121,17 @@ class SingleWalletView : WalletView {
|
|||
|
||||
setupButtonsType(state, fragment)
|
||||
|
||||
val btnConfirm = if (state.topUpState.allowed) {
|
||||
val btnConfirm = if (state.tradeCryptoState.sellingAllowed ||
|
||||
state.tradeCryptoState.buyingAllowed
|
||||
) {
|
||||
btn_confirm_short
|
||||
} else {
|
||||
btn_confirm_long
|
||||
}
|
||||
val btnScan = if (state.topUpState.allowed) {
|
||||
|
||||
val btnScan = if (state.tradeCryptoState.sellingAllowed ||
|
||||
state.tradeCryptoState.buyingAllowed
|
||||
) {
|
||||
btn_scan_short
|
||||
} else {
|
||||
btn_scan_long
|
||||
|
|
@ -149,14 +151,26 @@ class SingleWalletView : WalletView {
|
|||
btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
|
||||
|
||||
btn_top_up.setOnClickListener {
|
||||
store.dispatch(
|
||||
WalletAction.TopUpAction.TopUp(fragment.requireContext(), R.color.backgroundLightGray)
|
||||
)
|
||||
tradeCryptoAction(state.tradeCryptoState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tradeCryptoAction(tradeCryptoState: TradeCryptoState) {
|
||||
val allowedToBuy = tradeCryptoState.buyingAllowed
|
||||
val allowedToSell = tradeCryptoState.sellingAllowed
|
||||
val action = when {
|
||||
allowedToBuy && !allowedToSell -> WalletAction.TradeCryptoAction.Buy
|
||||
!allowedToBuy && allowedToSell -> WalletAction.TradeCryptoAction.Sell
|
||||
allowedToBuy && allowedToSell -> WalletAction.ShowDialog.ChooseTradeActionDialog
|
||||
else -> null
|
||||
}
|
||||
if (action != null) store.dispatch(action)
|
||||
}
|
||||
|
||||
private fun setupButtonsType(state: WalletData, fragment: WalletFragment) = with(fragment) {
|
||||
if (state.topUpState.allowed) {
|
||||
if (state.tradeCryptoState.sellingAllowed ||
|
||||
state.tradeCryptoState.buyingAllowed
|
||||
) {
|
||||
l_buttons_long.hide()
|
||||
l_buttons_short.show()
|
||||
} else {
|
||||
|
|
@ -244,6 +258,9 @@ class SingleWalletView : WalletView {
|
|||
dialog = SignedHashesWarningDialog.create(context).apply { show() }
|
||||
}
|
||||
}
|
||||
is WalletDialog.ChooseTradeActionDialog -> {
|
||||
if (dialog == null) dialog = ChooseTradeActionDialog(context).apply { show() }
|
||||
}
|
||||
null -> {
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.tap.network.moonpay
|
||||
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface MoonpayApi {
|
||||
@GET( MOOONPAY_IP_ADDRESS_REQUEST_URL)
|
||||
suspend fun getUserStatus(
|
||||
@Query("apiKey") moonpayApiKey: String
|
||||
): MoonPayUserStatus
|
||||
|
||||
companion object {
|
||||
const val MOOONPAY_BASE_URL = "https://api.moonpay.com/v4/"
|
||||
const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "ip_address/"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.network.moonpay
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.commands.common.network.performRequest
|
||||
import com.tangem.tap.network.createRetrofitInstance
|
||||
|
||||
class MoonpayService {
|
||||
|
||||
private val moonpayApi: MoonpayApi by lazy {
|
||||
createRetrofitInstance(MoonpayApi.MOOONPAY_BASE_URL)
|
||||
.create(MoonpayApi::class.java)
|
||||
}
|
||||
|
||||
suspend fun getUserStatus(moonpayApiKey: String): Result<MoonPayUserStatus> {
|
||||
return performRequest { moonpayApi.getUserStatus(moonpayApiKey) }
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MoonPayUserStatus(
|
||||
val isBuyAllowed: Boolean,
|
||||
val isSellAllowed: Boolean
|
||||
)
|
||||
24
app/src/main/res/drawable/ic_arrows_up_down_short_btn.xml
Normal file
24
app/src/main/res/drawable/ic_arrows_up_down_short_btn.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="20">
|
||||
<group
|
||||
android:translateX="4"
|
||||
android:translateY="0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M11.4765,5.7026L6.7623,10.0101C6.4406,10.2874 6.412,10.8356 6.692,11.1572C6.972,11.4788 7.5063,11.5112 7.8099,11.2131L11.2146,8.1003L11.2146,18.6923C11.2146,19.1384 11.5664,19.5 12.0003,19.5C12.4342,19.5 12.7859,19.1384 12.7859,18.6923L12.7859,8.1003L16.1906,11.2131C16.4942,11.5112 17.0275,11.4709 17.3076,11.1493C17.5876,10.8277 17.5599,10.2874 17.2382,10.0101L12.524,5.7026C12.1429,5.4214 11.8008,5.4438 11.4765,5.7026L11.4765,5.7026Z" />
|
||||
|
||||
</group>
|
||||
<group
|
||||
android:pivotX="9"
|
||||
android:pivotY="12.5"
|
||||
android:rotation="180">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M11.4765,5.7026L6.7623,10.0101C6.4406,10.2874 6.412,10.8356 6.692,11.1572C6.972,11.4788 7.5063,11.5112 7.8099,11.2131L11.2146,8.1003L11.2146,18.6923C11.2146,19.1384 11.5664,19.5 12.0003,19.5C12.4342,19.5 12.7859,19.1384 12.7859,18.6923L12.7859,8.1003L16.1906,11.2131C16.4942,11.5112 17.0275,11.4709 17.3076,11.1493C17.5876,10.8277 17.5599,10.2874 17.2382,10.0101L12.524,5.7026C12.1429,5.4214 11.8008,5.4438 11.4765,5.7026L11.4765,5.7026Z" />
|
||||
</group>
|
||||
|
||||
</vector>
|
||||
47
app/src/main/res/layout/dialog_wallet_trade.xml
Normal file
47
app/src/main/res/layout/dialog_wallet_trade.xml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/wallet_dialog_choose_trade_action"
|
||||
android:textColor="@color/darkGray2"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_btn_buy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="32dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:text="@string/wallet_button_buy"
|
||||
android:textColor="@color/darkGray3"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
app:drawableStartCompat="@drawable/ic_arrow_up" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_btn_sell"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="32dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:text="@string/wallet_button_sell"
|
||||
android:textColor="@color/darkGray3"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
app:drawableStartCompat="@drawable/ic_arrow_down" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="@drawable/ic_close"
|
||||
app:title="@string/wallet_button_topup" />
|
||||
app:title="@string/wallet_button_buy" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/web_view"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:background="@color/toolbarColor"
|
||||
android:fitsSystemWindows="true"
|
||||
app:liftOnScroll="true">
|
||||
|
||||
|
|
|
|||
|
|
@ -83,12 +83,12 @@
|
|||
android:id="@+id/rv_pending_transaction"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:overScrollMode="never"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier_currency_title" />
|
||||
|
||||
<include
|
||||
|
|
@ -114,13 +114,13 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/l_wallet_details">
|
||||
|
||||
<TextView
|
||||
android:padding="16dp"
|
||||
android:id="@+id/tv_pending_transaction_warning"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/wallet_pending_transaction_warning"
|
||||
android:textColor="@android:color/white" />
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp" />
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
|
|
@ -136,24 +136,46 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@string/wallet_button_topup"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:text="@string/wallet_button_buy"
|
||||
app:icon="@drawable/ic_arrow_up"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_sell"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier_left_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:barrierDirection="end"
|
||||
app:constraint_referenced_ids="btn_top_up, btn_sell" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier_right_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="start"
|
||||
app:constraint_referenced_ids="btn_sell, btn_confirm" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_sell"
|
||||
style="@style/TapButtonWithIcon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:text="@string/wallet_button_sell"
|
||||
app:icon="@drawable/ic_arrow_down"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_top_up"
|
||||
app:layout_constraintEnd_toStartOf="@id/btn_confirm"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_confirm"
|
||||
|
|
@ -164,11 +186,12 @@
|
|||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:text="@string/wallet_button_send"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_sell"
|
||||
app:icon="@drawable/ic_send"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
android:id="@+id/etAddressOrPayId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:ellipsize="middle"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:paddingStart="0dp"
|
||||
|
|
@ -150,6 +151,7 @@
|
|||
android:id="@+id/etMemo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:ellipsize="middle"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
|
|
@ -180,6 +182,7 @@
|
|||
android:id="@+id/etDestinationTag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:ellipsize="end"
|
||||
android:inputType="number"
|
||||
android:paddingStart="0dp"
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
android:id="@+id/etAmountToSend"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="82dp"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:inputType="numberDecimal"
|
||||
android:paddingStart="0dp"
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@
|
|||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:drawableTop="@drawable/ic_arrow_up_short_btn"
|
||||
android:drawableTop="@drawable/ic_arrows_up_down_short_btn"
|
||||
android:gravity="bottom|center_horizontal"
|
||||
android:paddingTop="1dp"
|
||||
android:text="@string/wallet_button_topup"
|
||||
android:text="@string/wallet_button_trade"
|
||||
app:drawableTint="@android:color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_confirm_short"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
<color name="colorSecondary">#0022D4</color>
|
||||
<color name="accent">#1ACE80</color>
|
||||
|
||||
<color name="toolbarColor">@color/backgroundLightGray</color>
|
||||
|
||||
<color name="tapButtonColor">@color/accent</color>
|
||||
<color name="tapButtonDisabled">#661ACE80</color>
|
||||
<color name="tapButtonColorBlack">@color/darkGray6</color>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<resources>
|
||||
<string name="wallet_button_topup" translatable="false">Top Up</string>
|
||||
<string name="wallet_button_buy" translatable="false">Buy</string>
|
||||
<string name="wallet_button_sell" translatable="false">Sell</string>
|
||||
<string name="wallet_button_trade" translatable="false">Trade</string>
|
||||
<string name="wallet_address_chip_default" translatable="false">Default</string>
|
||||
<string name="wallet_address_chip_compatibility" translatable="false">Compatibility</string>
|
||||
<string name="wallet_address_chip_legacy" translatable="false">Legacy</string>
|
||||
|
|
@ -161,4 +163,7 @@ this wallet.
|
|||
<string name="wallet_connect_error_timeout" translatable="false">Failed to establish WalletConnect session: timeout error. Please, try again later.</string>
|
||||
|
||||
<string name="warning_testnet_card_message" translatable="false">This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes.</string>
|
||||
|
||||
|
||||
<string name="wallet_dialog_choose_trade_action" translatable="false">Do you want to buy or sell crypto?</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue