Updated on 2026-08-14
This commit is contained in:
commit
98ac9a23e9
12 changed files with 117 additions and 39 deletions
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.tap.domain.termsOfUse
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import com.tangem.commands.common.card.Card
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CardTou {
|
||||
private val locale: Locale = ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)
|
||||
|
||||
fun getUrl(card: Card): Uri? {
|
||||
val issuerName = card.cardData?.issuerName ?: return null
|
||||
if (issuerName.toLowerCase() != "start2coin") return null
|
||||
|
||||
val baseUrl = "https://app.tangem.com/tou/"
|
||||
val regionCode = regionCode(card.cardId) ?: "fr"
|
||||
val filename = filename(locale.language, regionCode)
|
||||
return Uri.parse(baseUrl + filename)
|
||||
}
|
||||
|
||||
private fun filename(languageCode: String, regionCode: String): String {
|
||||
return when {
|
||||
languageCode == "fr" && regionCode == "ch" -> "Start2Coin-fr-ch-tangem.pdf"
|
||||
languageCode == "de" && regionCode == "ch" -> "Start2Coin-de-ch-tangem.pdf"
|
||||
languageCode == "en" && regionCode == "ch" -> "Start2Coin-en-ch-tangem.pdf"
|
||||
languageCode == "it" && regionCode == "ch" -> "Start2Coin-it-ch-tangem.pdf"
|
||||
languageCode == "fr" && regionCode == "fr" -> "Start2Coin-fr-fr-atangem.pdf"
|
||||
languageCode == "de" && regionCode == "at" -> "Start2Coin-de-at-tangem.pdf"
|
||||
regionCode == "fr" -> "Start2Coin-fr-fr-atangem.pdf"
|
||||
regionCode == "ch" -> "Start2Coin-en-ch-tangem.pdf"
|
||||
regionCode == "at" -> "Start2Coin-de-at-tangem.pdf"
|
||||
else -> "Start2Coin-fr-fr-atangem.pdf"
|
||||
}
|
||||
}
|
||||
|
||||
private fun regionCode(cardId: String): String? = when (cardId[1]) {
|
||||
'0' -> "fr"
|
||||
'1' -> "ch"
|
||||
'2' -> "at"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.commands.common.card.Card
|
|||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.termsOfUse.CardTou
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.network.coinmarketcap.FiatCurrency
|
||||
|
|
@ -19,6 +20,7 @@ sealed class DetailsAction : Action {
|
|||
val scanNoteResponse: ScanNoteResponse,
|
||||
val wallet: Wallet?,
|
||||
val isCreatingTwinWalletAllowed: Boolean?,
|
||||
val cardTou: CardTou,
|
||||
val fiatCurrencyName: FiatCurrencyName,
|
||||
val fiatCurrencies: List<FiatCurrencyName>? = null,
|
||||
) : DetailsAction()
|
||||
|
|
|
|||
|
|
@ -73,7 +73,8 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta
|
|||
action.fiatCurrencyName
|
||||
),
|
||||
securityScreenState = SecurityScreenState(currentOption = securityOption),
|
||||
createTwinWalletState = twinsState
|
||||
createTwinWalletState = twinsState,
|
||||
cardTermsOfUseUrl = action.cardTou.getUrl(action.card)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.tap.common.entities.Button
|
||||
|
|
@ -18,13 +19,14 @@ data class DetailsState(
|
|||
val eraseWalletState: EraseWalletState? = null,
|
||||
val confirmScreenState: ConfirmScreenState? = null,
|
||||
val securityScreenState: SecurityScreenState? = null,
|
||||
val createTwinWalletState: CreateTwinWalletState? = null
|
||||
val createTwinWalletState: CreateTwinWalletState? = null,
|
||||
val cardTermsOfUseUrl: Uri? = null,
|
||||
) : StateType
|
||||
|
||||
data class CardInfo(
|
||||
val cardId: String,
|
||||
val issuer: String,
|
||||
val signedHashes: Int
|
||||
val signedHashes: Int,
|
||||
)
|
||||
|
||||
enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty }
|
||||
|
|
@ -33,7 +35,7 @@ data class SecurityScreenState(
|
|||
val currentOption: SecurityOption = SecurityOption.LongTap,
|
||||
val selectedOption: SecurityOption = currentOption,
|
||||
val allowedOptions: EnumSet<SecurityOption> = EnumSet.allOf(SecurityOption::class.java),
|
||||
val buttonProceed: Button = Button(true)
|
||||
val buttonProceed: Button = Button(true),
|
||||
)
|
||||
|
||||
enum class SecurityOption { LongTap, PassCode, AccessCode }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.details.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
|
|
@ -80,6 +81,13 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
|
||||
tv_disclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
|
||||
|
||||
tv_card_tou.show(state.cardTermsOfUseUrl != null)
|
||||
tv_card_tou.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = state.cardTermsOfUseUrl
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
if (state.createTwinWalletState != null) {
|
||||
if (state.createTwinWalletState.allowRecreatingWallet == true) {
|
||||
tv_erase_wallet.show()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
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.store
|
||||
|
|
@ -30,6 +31,9 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
|
|||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
package com.tangem.tap.features.send.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
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.isPayIdSupported
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetAddressError
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetWalletAddress
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress
|
||||
import com.tangem.tap.features.send.redux.FeeAction
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -50,7 +48,7 @@ internal class AddressPayIdMiddleware {
|
|||
|
||||
private fun setAddressAndCheck(data: String, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
||||
val potentialPayId = data.toLowerCase()
|
||||
if (PayIdManager.isPayId(potentialPayId) && isPayIdEnabled()) {
|
||||
if (isPayIdEnabled() && PayIdManager.isPayId(potentialPayId)) {
|
||||
dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput))
|
||||
} else {
|
||||
dispatch(SetWalletAddress(data, isUserInput))
|
||||
|
|
@ -64,7 +62,7 @@ internal class AddressPayIdMiddleware {
|
|||
val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return
|
||||
val isUserInput = sendState.addressPayIdState.viewFieldValue.isFromUserInput
|
||||
|
||||
if (PayIdManager.isPayId(addressPayId) && isPayIdEnabled()) {
|
||||
if (isPayIdEnabled() && PayIdManager.isPayId(addressPayId)) {
|
||||
verifyPayId(addressPayId, wallet, isUserInput, dispatch)
|
||||
} else {
|
||||
verifyAddress(addressPayId, wallet, isUserInput, dispatch)
|
||||
|
|
@ -110,7 +108,11 @@ internal class AddressPayIdMiddleware {
|
|||
}
|
||||
|
||||
private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
||||
val addressSchemeSplit = address.split(":")
|
||||
val addressSchemeSplit = if (wallet.blockchain == Blockchain.BitcoinCash) {
|
||||
listOf(address)
|
||||
} else {
|
||||
address.split(":")
|
||||
}
|
||||
val noSchemeAddress = when (addressSchemeSplit.size) {
|
||||
1 -> address // no scheme
|
||||
2 -> { // scheme
|
||||
|
|
@ -131,6 +133,9 @@ internal class AddressPayIdMiddleware {
|
|||
|
||||
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress)
|
||||
if (failReason == null) {
|
||||
noSchemeAddress.getQueryParameter("amount")?.toBigDecimalOrNull()?.let {
|
||||
dispatch(AmountAction.SetAmount(it, false))
|
||||
}
|
||||
dispatch(SetWalletAddress(supposedAddress, isUserInput))
|
||||
dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, null))
|
||||
} else {
|
||||
|
|
@ -141,7 +146,7 @@ internal class AddressPayIdMiddleware {
|
|||
|
||||
private fun isValidBlockchainAddressAndNotTheSameAsWallet(wallet: Wallet, address: String): Error? {
|
||||
return if (wallet.blockchain.validateAddress(address)) {
|
||||
if (wallet.addresses.all { it.value != address } ) {
|
||||
if (wallet.addresses.all { it.value != address }) {
|
||||
null
|
||||
} else {
|
||||
Error.ADDRESS_SAME_AS_WALLET
|
||||
|
|
@ -151,14 +156,10 @@ internal class AddressPayIdMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
//TODO: move to the blockchainSDK
|
||||
private fun extractAddressFromShareUri(shareUri: String): String {
|
||||
val sharePrefix = listOf("bitcoin:", "ethereum:", "xrpl:", "litecoin:", "bnb:")
|
||||
val prefixes = sharePrefix.filter { shareUri.contains(it) }
|
||||
return if (prefixes.isEmpty()) shareUri else shareUri.replace(prefixes[0], "")
|
||||
}
|
||||
|
||||
private fun String.removeShareUriQuery(): String = this.substringBefore("?")
|
||||
private fun String.getQueryParameter(name: String): String? {
|
||||
return this.substringAfter("?").splitToMap("&", "=")[name]
|
||||
}
|
||||
|
||||
private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) {
|
||||
val addressPayId = input ?: return
|
||||
|
|
@ -186,4 +187,11 @@ internal class AddressPayIdMiddleware {
|
|||
private fun isPayIdEnabled(): Boolean {
|
||||
return store.state.globalState.configManager?.config?.isSendingToPayIdEnabled ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun String.splitToMap(firstDelimiter: String, secondDelimiter: String): Map<String, String> {
|
||||
return this.split(firstDelimiter)
|
||||
.map { it.split(secondDelimiter) }
|
||||
.map { it.first() to it.last().toString() }
|
||||
.toMap()
|
||||
}
|
||||
|
|
@ -139,9 +139,14 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
val scannedCode = data?.getStringExtra(ScanQrCodeActivity.SCAN_RESULT) ?: ""
|
||||
if (scannedCode.isEmpty()) return
|
||||
|
||||
store.dispatch(PasteAddressPayId(scannedCode))
|
||||
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
|
||||
store.dispatch(FeeAction.RequestFee)
|
||||
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
|
||||
// If do not use the delay, then etAmount error field is not displayed when
|
||||
// inserting an incorrect amount by shareUri
|
||||
imvQrCode.postDelayed({
|
||||
store.dispatch(PasteAddressPayId(scannedCode))
|
||||
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
|
||||
store.dispatch(FeeAction.RequestFee)
|
||||
}, 200)
|
||||
}
|
||||
|
||||
private fun setupAmountLayout() {
|
||||
|
|
|
|||
|
|
@ -177,22 +177,6 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
val amountToSend = state.viewAmountValue
|
||||
if (!amountToSend.isFromUserInput) fg.etAmountToSend.update(amountToSend.value)
|
||||
|
||||
// fg.tvAmountToSendShadow.text = amountToSend
|
||||
// if (amountToSend.length > 10) {
|
||||
// post is needed to wait for text size changes
|
||||
// fg.tvAmountToSendShadow.post {
|
||||
// fg.etAmountToSend.setTextSize(TypedValue.COMPLEX_UNIT_PX, fg.tvAmountToSendShadow.textSize - 2)
|
||||
// fg.etAmountToSend.update(amountToSend)
|
||||
// if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length)
|
||||
// }
|
||||
// } else {
|
||||
// val textSize = fg.resources.getDimension(R.dimen.text_size_amount_to_send)
|
||||
// fg.tvAmountToSendShadow.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
// fg.etAmountToSend.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
// fg.etAmountToSend.update(amountToSend)
|
||||
// if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length)
|
||||
// }
|
||||
|
||||
fg.tvAmountCurrency.update(state.mainCurrency.currencySymbol)
|
||||
(fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.blockchain.common.address.AddressType
|
|||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.termsOfUse.CardTou
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
|
|
@ -312,6 +313,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
scanNoteResponse.card, scanNoteResponse,
|
||||
store.state.walletState.wallet,
|
||||
store.state.globalState.configManager?.config?.isCreatingTwinCardsAllowed,
|
||||
CardTou(),
|
||||
store.state.globalState.appCurrency
|
||||
))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Details))
|
||||
|
|
|
|||
|
|
@ -126,6 +126,21 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes_title"
|
||||
android:text="@string/disclaimer_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_card_tou"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:drawablePadding="15dp"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
android:visibility="gone"
|
||||
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_disclaimer"
|
||||
android:text="@string/details_row_title_card_tou" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_settings_title"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -137,7 +152,7 @@
|
|||
android:textColor="@color/colorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_disclaimer" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_card_tou" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ this wallet.
|
|||
<string name="details_twins_recreate_subtitle" translatable="false">The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning.</string>
|
||||
<string name="details_twins_recreate_button_format" translatable="false">Tap the card #%s</string>
|
||||
<string name="details_twins_recreate_alert" translatable="false">You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet.</string>
|
||||
<string name="details_row_title_card_tou" translatable="false">Card terms of use</string>
|
||||
|
||||
<string name="notification_twins_recreate_success" translatable="false">The twin address was successfully created</string>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue