Updated on 2026-08-14

This commit is contained in:
Tangem 2021-02-12 11:03:11 +03:00
commit 2bb6a2b17d
28 changed files with 303 additions and 155 deletions

View file

@ -72,12 +72,12 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
implementation 'com.tangem:blockchain:1.128.0'
implementation 'com.tangem:core:1.92.0'
implementation 'com.tangem:sdk:1.92.0'
implementation 'com.tangem:blockchain:1.141.0'
implementation 'com.tangem:core:1.101.0'
implementation 'com.tangem:sdk:1.101.0'
// WebView
implementation "androidx.browser:browser:1.2.0"
implementation "androidx.browser:browser:1.3.0"
//lifecycle
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
@ -103,7 +103,7 @@ dependencies {
implementation 'com.google.firebase:firebase-config-ktx'
implementation 'com.google.firebase:firebase-analytics-ktx'
testImplementation 'junit:junit:4.13'
testImplementation 'junit:junit:4.13.1'
testImplementation "com.google.truth:truth:1.0.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

View file

@ -36,17 +36,17 @@ fun String.toQrCode(): Bitmap {
return bmp
}
fun BigDecimal.toFormattedString(decimals: Int): String {
val symbols = DecimalFormatSymbols(Locale.US)
symbols.decimalSeparator = '.'
fun BigDecimal.toFormattedString(
decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US
): String {
val symbols = DecimalFormatSymbols(locale)
val df = DecimalFormat()
df.decimalFormatSymbols = symbols
df.maximumFractionDigits = decimals
df.minimumFractionDigits = 0
df.isGroupingUsed = false
val bd = BigDecimal(unscaledValue(), scale())
bd.setScale(decimals, RoundingMode.DOWN)
return df.format(bd)
df.roundingMode = roundingMode
return df.format(this)
}
fun BigDecimal.toFormattedCurrencyString(decimals: Int, currency: String): String {

View file

@ -2,6 +2,7 @@ package com.tangem.tap.common.redux.global
import com.tangem.tap.domain.config.ConfigManager
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.features.details.redux.SecurityOption
import org.rekotlin.Action
import java.math.BigDecimal
@ -17,4 +18,5 @@ sealed class GlobalAction : Action {
}
data class UpdateWalletSignedHashes(val walletSignedHashes: Int?) : GlobalAction()
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
data class UpdateSecurityOptions(val securityOption: SecurityOption) : GlobalAction()
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.common.redux.global
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.details.redux.SecurityOption
import org.rekotlin.Action
fun globalReducer(action: Action, state: AppState): GlobalState {
@ -36,6 +37,24 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.SetConfigManager -> {
globalState.copy(configManager = action.configManager)
}
is GlobalAction.UpdateSecurityOptions -> {
val card = when (action.securityOption) {
SecurityOption.LongTap -> globalState.scanNoteResponse?.card?.copy(
isPin1Default = true, isPin2Default = true
)
SecurityOption.PassCode -> globalState.scanNoteResponse?.card?.copy(
isPin1Default = true, isPin2Default = false
)
SecurityOption.AccessCode -> globalState.scanNoteResponse?.card?.copy(
isPin1Default = false, isPin2Default = true
)
}
if (card != null) {
globalState.copy(scanNoteResponse = globalState.scanNoteResponse?.copy(card = card))
} else {
globalState
}
}
else -> globalState
}
}

View file

@ -38,6 +38,10 @@ sealed class TapError(
object DustChange : TapError(R.string.send_error_dust_change)
data class CreateAccountUnderfunded(override val args: List<Any>) : TapError(R.string.send_error_no_target_account)
sealed class XmlError {
object AssetAccountNotCreated: TapError(R.string.send_error_no_account_xlm)
}
data class ValidateTransactionErrors(
override val errorList: List<TapError>,
override val builder: (List<String>) -> String

View file

@ -76,7 +76,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
)))
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(TangemSdkError.VerificationFailed()))
callback(CompletionResult.Failure(TangemSdkError.CardVerificationFailed()))
}
}
}

View file

@ -140,6 +140,9 @@ class DetailsMiddleware {
withContext(Dispatchers.Main) {
when (result) {
is CompletionResult.Success -> {
selectedOption?.let {
store.dispatch(GlobalAction.UpdateSecurityOptions(it))
}
if (selectedOption != SecurityOption.LongTap) {
store.dispatch(NavigationAction.PopBackTo())
}

View file

@ -144,23 +144,8 @@ private fun handleSecurityAction(
allowedOptions = EnumSet.noneOf(SecurityOption::class.java)
))
}
val prohibitDefaultPin = state.card.settingsMask?.contains(Settings.ProhibitDefaultPIN1) == true
val allowSetPin1 = state.card.settingsMask?.contains(Settings.AllowSetPIN1) != false
val allowSetPin2 = state.card.settingsMask?.contains(Settings.AllowSetPIN2) != false
val isDefaultPin1 = state.card.isPin1Default != false
val isDefaultPin2 = state.card.isPin2Default != false
val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java)
if ((isDefaultPin1 && isDefaultPin2) || !prohibitDefaultPin) {
allowedSecurityOptions.add(SecurityOption.LongTap)
}
if (allowSetPin1 && (isDefaultPin2 || !prohibitDefaultPin)) {
allowedSecurityOptions.add(SecurityOption.AccessCode)
}
if (allowSetPin2 && (isDefaultPin1 || !prohibitDefaultPin)) {
allowedSecurityOptions.add(SecurityOption.PassCode)
}
val allowedSecurityOptions = prepareAllowedSecurityOptions(state.card)
state.copy(securityScreenState = state.securityScreenState?.copy(
allowedOptions = allowedSecurityOptions,
selectedOption = state.securityScreenState.currentOption
@ -180,15 +165,37 @@ private fun handleSecurityAction(
state.copy(confirmScreenState = confirmScreenState)
}
is DetailsAction.ManageSecurity.SaveChanges.Success -> {
state.copy(securityScreenState = state.securityScreenState?.copy(
currentOption = state.securityScreenState.selectedOption
))
// Setting options to show only LongTap from now on
state.copy(
card = state.card?.copy(isPin1Default = true, isPin2Default = true),
securityScreenState = state.securityScreenState?.copy(
currentOption = state.securityScreenState.selectedOption,
allowedOptions = EnumSet.of(SecurityOption.LongTap)
))
}
else -> state
}
}
private fun prepareAllowedSecurityOptions(card: Card): EnumSet<SecurityOption> {
val prohibitDefaultPin = card.settingsMask?.contains(Settings.ProhibitDefaultPIN1) == true
val isDefaultPin1 = card.isPin1Default != false
val isDefaultPin2 = card.isPin2Default != false
val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java)
if ((isDefaultPin1 && isDefaultPin2) || !prohibitDefaultPin) {
allowedSecurityOptions.add(SecurityOption.LongTap)
}
if (!isDefaultPin1) {
allowedSecurityOptions.add(SecurityOption.AccessCode)
}
if (!isDefaultPin2) {
allowedSecurityOptions.add(SecurityOption.PassCode)
}
return allowedSecurityOptions
}
private fun Card.toCardInfo(): CardInfo? {
val cardId = this.cardId.chunked(4).joinToString(separator = " ")

View file

@ -5,6 +5,7 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
@ -91,6 +92,7 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
}
private fun enableLongTap(enable: Boolean) {
group_long_tap.show(enable)
val alpha = if (enable) 1f else 0.5f
tv_long_tap_description.alpha = alpha
tv_long_tap_title.alpha = alpha
@ -105,6 +107,7 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
}
private fun enablePasscode(enable: Boolean) {
group_passcode.show(enable)
val alpha = if (enable) 1f else 0.5f
tv_passcode_description.alpha = alpha
tv_passcode_title.alpha = alpha
@ -119,6 +122,7 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
}
private fun enableAccessCode(enable: Boolean) {
group_access_code.show(enable)
val alpha = if (enable) 1f else 0.5f
tv_access_code_description.alpha = alpha
tv_access_code_title.alpha = alpha

View file

@ -98,7 +98,9 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_details_twin_cards),
btn_tap.setOnClickListener {
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep(
Message(getString(
R.string.details_twins_recreate_title_format, twinCardNumber)
R.string.details_twins_recreate_title_format,
twinCardNumberString
)
)))
}
btn_tap.text = getString(R.string.details_twins_recreate_button_format,

View file

@ -110,7 +110,24 @@ internal class AddressPayIdMiddleware {
}
private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) {
val supposedAddress = extractAddressFromShareUri(address).removeNonAddressData()
val addressSchemeSplit = address.split(":")
val noSchemeAddress = when (addressSchemeSplit.size) {
1 -> address // no scheme
2 -> { // scheme
if (wallet.blockchain.validateShareScheme(addressSchemeSplit[0])) {
addressSchemeSplit[1]
} else {
dispatch(SetAddressError(Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN))
return
}
}
else -> { // invalid URI
dispatch(SetAddressError(Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN))
return
}
}
val supposedAddress = noSchemeAddress.removeShareUriQuery() //TODO: parse query?
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress)
if (failReason == null) {
@ -141,7 +158,7 @@ internal class AddressPayIdMiddleware {
return if (prefixes.isEmpty()) shareUri else shareUri.replace(prefixes[0], "")
}
private fun String.removeNonAddressData(): String = this.substringBefore("?")
private fun String.removeShareUriQuery(): String = this.substringBefore("?")
private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) {
val addressPayId = input ?: return

View file

@ -140,6 +140,11 @@ private fun sendTransaction(
message.contains("50002") -> {
// user was cancelled the operation by closing the Sdk bottom sheet
}
// make it easier latter by handling an appropriate enumError or, like on iOS,
// accept a string identifier of the error message
message.contains("Target account is not created. To create account send 1+ XLM.")-> {
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
else -> {
Timber.e(throwable)
FirebaseCrashlytics.getInstance().recordException(throwable)

View file

@ -24,8 +24,9 @@ class TransactionExtrasReducer : SendInternalReducer {
val emptyResult = TransactionExtrasState()
val result = when (action.blockchain) {
Blockchain.XRP -> {
val address = action.walletAddress.substringAfter(":")
// 'r' - without tag, 'x' - with tag
if (action.walletAddress.startsWith("r", true)) {
if (address.startsWith("r", true)) {
val tag = action.xrpTag?.toLongOrNull()
if (tag == null) {
TransactionExtrasState(xrpDestinationTag = XrpDestinationTagState())
@ -54,7 +55,7 @@ class TransactionExtrasReducer : SendInternalReducer {
sendState: SendState,
infoState: TransactionExtrasState,
): SendState {
fun clearMemo(memo: XlmMemoState): XlmMemoState = memo.copy(text = null, id = null)
fun clearMemo(memo: XlmMemoState): XlmMemoState = memo.copy(text = null, id = null, error = null)
val result = when (action) {
// is XlmMemo.ChangeSelectedMemo -> {
@ -68,13 +69,22 @@ class TransactionExtrasReducer : SendInternalReducer {
// }
is XlmMemo.HandleUserInput -> {
val inputViewValue = InputViewValue(action.data, true)
var memo = infoState.xlmMemo?.copy(viewFieldValue = inputViewValue) ?: XlmMemoState(inputViewValue)
var memo = infoState.xlmMemo?.copy(viewFieldValue = inputViewValue)
?: XlmMemoState(inputViewValue)
memo = clearMemo(memo)
memo = when (infoState.xlmMemo?.selectedMemoType) {
XlmMemoType.TEXT -> memo.copy(text = StellarMemo.Text(action.data))
XlmMemoType.ID -> {
val id = action.data.toIntOrNull()?.toBigInteger()
if (id != null) memo.copy(id = StellarMemo.Id(id)) else memo
val id = action.data.toBigIntegerOrNull()
if (id != null) {
if (id > XlmMemoState.MAX_NUMBER) {
memo.copy(error = TransactionExtraError.INVALID_XLM_MEMO)
} else {
memo.copy(id = StellarMemo.Id(id))
}
} else {
memo
}
}
null -> memo
}
@ -93,7 +103,12 @@ class TransactionExtrasReducer : SendInternalReducer {
is XrpDestinationTag.HandleUserInput -> {
val tag = action.data.toLongOrNull()
if (tag != null) {
val tagState = XrpDestinationTagState(InputViewValue(action.data, true), tag)
val input = InputViewValue(action.data, true)
val tagState = if (tag <= XrpDestinationTagState.MAX_NUMBER){
XrpDestinationTagState(input, tag)
} else {
XrpDestinationTagState(input, error = TransactionExtraError.INVALID_DESTINATION_TAG)
}
infoState.copy(xrpDestinationTag = tagState)
} else {
infoState

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.send.redux.states
import com.tangem.blockchain.blockchains.stellar.StellarMemo
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
import java.math.BigInteger
data class AddressPayIdState(
val viewFieldValue: InputViewValue = InputViewValue(""),
@ -39,16 +40,31 @@ data class XlmMemoState(
val selectedMemoType: XlmMemoType = XlmMemoType.ID,
val text: StellarMemo.Text? = null,
val id: StellarMemo.Id? = null,
val error: TransactionExtraError? = null,
) {
val memo: StellarMemo?
get() = when (selectedMemoType) {
XlmMemoType.TEXT -> text
XlmMemoType.ID -> id
}
companion object {
val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16)
}
}
// tag must contains only digits
data class XrpDestinationTagState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val tag: Long? = null
)
val tag: Long? = null,
val error: TransactionExtraError? = null
) {
companion object {
const val MAX_NUMBER: Long = 4294967295
}
}
enum class TransactionExtraError {
INVALID_DESTINATION_TAG,
INVALID_XLM_MEMO
}

View file

@ -70,9 +70,25 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
}
if (!it.viewFieldValue.isFromUserInput) fg.etMemo.setText(it.viewFieldValue.value)
if (it.error != null) {
if (it.error == TransactionExtraError.INVALID_XLM_MEMO) {
fg.tilMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
}
} else {
fg.tilMemo.error = null
}
}
infoState.xrpDestinationTag?.let {
if (!it.viewFieldValue.isFromUserInput) fg.etDestinationTag.setText(it.viewFieldValue.value)
if (infoState.xrpDestinationTag.error != null) {
if (infoState.xrpDestinationTag.error == TransactionExtraError.INVALID_DESTINATION_TAG) {
fg.tilDestinationTag.error = fg.getText(R.string.send_error_invalid_destination_tag)
}
} else {
fg.tilDestinationTag.error = null
}
if (!it.viewFieldValue.isFromUserInput) {
fg.etDestinationTag.setText(it.viewFieldValue.value)
}
}
}

View file

@ -32,11 +32,13 @@ data class WalletState(
currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain
val showSegwitAddress: Boolean
val showMultipleAddress: Boolean
get() {
val listOfAddresses = walletAddresses?.list ?: return false
return (wallet?.blockchain == Blockchain.Bitcoin || wallet?.blockchain == Blockchain.BitcoinTestnet)
&& listOfAddresses.size > 1
return (wallet?.blockchain == Blockchain.Bitcoin ||
wallet?.blockchain == Blockchain.BitcoinTestnet ||
wallet?.blockchain == Blockchain.CardanoShelley) &&
listOfAddresses.size > 1
}
}

View file

@ -12,6 +12,8 @@ import androidx.transition.TransitionInflater
import com.google.android.material.snackbar.Snackbar
import com.squareup.picasso.Picasso
import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
import com.tangem.blockchain.blockchains.cardano.CardanoAddressType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.address.AddressType
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.navigation.AppScreen
@ -228,28 +230,23 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
private fun setupAddressCard(state: WalletState) {
if (state.walletAddresses != null) {
l_address?.show()
val tvAddressPaddingTop = tv_address.resources.getDimension(R.dimen.dimen16).toInt()
if (state.showSegwitAddress) {
if (state.showMultipleAddress) {
(l_address as? ViewGroup)?.beginDelayedTransition()
tv_address.setPadding(tv_address.paddingStart, tvAddressPaddingTop / 2,
tv_address.paddingEnd, tv_address.paddingBottom)
chip_group_segwit.show()
chip_group_segwit.fitChipsByGroupWidth()
chip_group_address_type.show()
chip_group_address_type.fitChipsByGroupWidth()
val checkedId = SegwitUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) chip_group_segwit.check(checkedId)
val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
chip_group_segwit.setOnCheckedChangeListener { group, checkedId ->
chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
if (checkedId == -1) return@setOnCheckedChangeListener
SegwitUiHelper.idToType(checkedId)?.let {
MultipleAddressUiHelper.idToType(checkedId, state.wallet?.blockchain)?.let {
store.dispatch(WalletAction.ChangeSelectedAddress(it))
}
}
} else {
tv_address.setPadding(tv_address.paddingStart, tvAddressPaddingTop,
tv_address.paddingEnd, tv_address.paddingBottom)
chip_group_segwit.hide()
chip_group_address_type.hide()
}
tv_address.text = state.walletAddresses.selectedAddress.address
tv_explore?.setOnClickListener {
@ -332,20 +329,34 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
}
class SegwitUiHelper {
class MultipleAddressUiHelper {
companion object {
fun typeToId(type: AddressType): Int {
return when (type) {
is BitcoinAddressType.Legacy -> R.id.chip_legacy
is BitcoinAddressType.Segwit -> R.id.chip_default
is CardanoAddressType.Byron -> R.id.chip_legacy
is CardanoAddressType.Shelley -> R.id.chip_default
else -> View.NO_ID
}
}
fun idToType(id: Int): AddressType? {
fun idToType(id: Int, blockchain: Blockchain?): AddressType? {
return when (id) {
R.id.chip_default -> BitcoinAddressType.Segwit
R.id.chip_legacy -> BitcoinAddressType.Legacy
R.id.chip_default -> {
when (blockchain) {
Blockchain.Bitcoin -> BitcoinAddressType.Segwit
Blockchain.CardanoShelley -> CardanoAddressType.Shelley
else -> null
}
}
R.id.chip_legacy -> {
when (blockchain) {
Blockchain.Bitcoin -> BitcoinAddressType.Legacy
Blockchain.CardanoShelley -> CardanoAddressType.Byron
else -> null
}
}
else -> null
}
}

View file

@ -20,154 +20,168 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/details_row_title_manage_security"
app:navigationIcon="@drawable/ic_baseline_arrow_back_24" />
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
app:title="@string/details_row_title_manage_security" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_details_confirm"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="33dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="33dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.Group
android:id="@+id/group_long_tap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="tv_long_tap_title, radiobutton_long_tap, tv_long_tap_description, v_long_tap" />
<TextView
android:id="@+id/tv_long_tap_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
app:layout_constraintTop_toTopOf="parent"
android:paddingBottom="8dp"
android:text="@string/details_manage_security_long_tap"
android:textSize="16sp"
/>
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radiobutton_long_tap"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_long_tap_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingBottom="8dp"
android:paddingEnd="60dp"
android:textColor="@color/darkGray1"
app:layout_constraintTop_toBottomOf="@id/tv_long_tap_title"
android:paddingBottom="8dp"
android:text="@string/details_manage_security_long_tap_description"
android:textColor="@color/darkGray1"
android:textSize="13sp"
/>
app:layout_constraintTop_toBottomOf="@id/tv_long_tap_title" />
<View
android:id="@+id/v_long_tap"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/tv_long_tap_title"
app:layout_constraintBottom_toBottomOf="@id/tv_long_tap_description"/>
app:layout_constraintBottom_toBottomOf="@id/tv_long_tap_description"
app:layout_constraintTop_toTopOf="@id/tv_long_tap_title" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_passcode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="tv_passcode_title, radiobutton_passcode, tv_passcode_description, v_passcode" />
<TextView
android:id="@+id/tv_passcode_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
app:layout_constraintTop_toBottomOf="@id/v_long_tap"
android:paddingBottom="8dp"
android:text="@string/details_manage_security_passcode"
android:textSize="16sp"
/>
app:layout_constraintTop_toBottomOf="@id/v_long_tap" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radiobutton_passcode"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/v_passcode"/>
app:layout_constraintTop_toTopOf="@id/v_passcode" />
<TextView
android:id="@+id/tv_passcode_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingBottom="8dp"
android:paddingEnd="60dp"
android:textColor="@color/darkGray1"
app:layout_constraintTop_toBottomOf="@id/tv_passcode_title"
android:paddingBottom="8dp"
android:text="@string/details_manage_security_passcode_description"
android:textColor="@color/darkGray1"
android:textSize="13sp"
/>
app:layout_constraintTop_toBottomOf="@id/tv_passcode_title" />
<View
android:id="@+id/v_passcode"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/tv_passcode_title"
app:layout_constraintBottom_toBottomOf="@id/tv_passcode_description"/>
app:layout_constraintBottom_toBottomOf="@id/tv_passcode_description"
app:layout_constraintTop_toTopOf="@id/tv_passcode_title" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_access_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="tv_access_code_title, radiobutton_access_code, tv_access_code_description, v_access_code" />
<TextView
android:id="@+id/tv_access_code_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
app:layout_constraintTop_toBottomOf="@id/v_passcode"
android:paddingBottom="8dp"
android:text="@string/details_manage_security_access_code"
android:textSize="16sp"
/>
app:layout_constraintTop_toBottomOf="@id/v_passcode" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radiobutton_access_code"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/v_access_code"/>
app:layout_constraintTop_toTopOf="@id/v_access_code" />
<TextView
android:id="@+id/tv_access_code_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingBottom="8dp"
android:paddingEnd="60dp"
android:textColor="@color/darkGray1"
app:layout_constraintTop_toBottomOf="@id/tv_access_code_title"
android:paddingBottom="8dp"
android:text="@string/details_manage_security_access_code_description"
android:textColor="@color/darkGray1"
android:textSize="13sp"
/>
app:layout_constraintTop_toBottomOf="@id/tv_access_code_title" />
<View
android:id="@+id/v_access_code"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/tv_access_code_title"
app:layout_constraintBottom_toBottomOf="@id/tv_access_code_description"/>
app:layout_constraintBottom_toBottomOf="@id/tv_access_code_description"
app:layout_constraintTop_toTopOf="@id/tv_access_code_title" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
@ -187,9 +201,9 @@
android:layout_marginEnd="16dp"
android:text="@string/common_continue"
app:icon="@drawable/ic_save"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
app:layout_constraintStart_toEndOf="@+id/guideline" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -43,8 +43,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_wallet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="33dp">
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_card"

View file

@ -24,7 +24,7 @@
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group_segwit"
android:id="@+id/chip_group_address_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
@ -60,14 +60,13 @@
android:layout_height="wrap_content"
android:ellipsize="middle"
android:paddingStart="@dimen/dimen16"
android:paddingTop="@dimen/dimen16"
android:paddingEnd="@dimen/dimen16"
android:singleLine="true"
android:textColor="@color/darkGray1"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@id/btn_copy"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chip_group_segwit"
app:layout_constraintTop_toTopOf="@id/btn_copy"
tools:text="139mrsJgyWnJ**** **** y9BV" />
<TextView
@ -78,14 +77,13 @@
android:paddingStart="16dp"
android:paddingTop="4dp"
android:paddingEnd="16dp"
android:paddingBottom="25dp"
android:text="@string/wallet_address_button_explore"
android:textColor="@color/darkGray6"
android:textSize="14sp"
android:textStyle="bold"
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_address" />
app:layout_constraintBottom_toBottomOf="@id/btn_copy"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
@ -94,12 +92,15 @@
android:layout_height="@dimen/btn_rounded_size"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="25dp"
android:background="@drawable/shape_ellipse"
android:clickable="true"
android:elevation="3dp"
android:focusable="true"
app:layout_constraintVertical_bias="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btn_show_qr"
app:layout_constraintTop_toBottomOf="@id/chip_group_segwit">
app:layout_constraintTop_toBottomOf="@id/chip_group_address_type">
<ImageView
android:id="@+id/iv_copy"
@ -123,7 +124,7 @@
android:elevation="3dp"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/chip_group_segwit">
app:layout_constraintTop_toBottomOf="@id/chip_group_address_type">
<ImageView
android:id="@+id/iv_qr"
@ -143,7 +144,8 @@
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/lightGray5"
app:layout_constraintTop_toBottomOf="@id/tv_explore" />
android:layout_marginTop="25dp"
app:layout_constraintTop_toBottomOf="@id/btn_copy" />
<ImageView
android:id="@+id/iv_payid_icon"
@ -151,9 +153,9 @@
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_payid"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/v_payid_divider"
app:layout_constraintBottom_toBottomOf="parent"/>
app:layout_constraintTop_toBottomOf="@id/v_payid_divider" />
<TextView
android:id="@+id/tv_create_payid"

View file

@ -18,7 +18,8 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btn_top_up"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="1" />
app:layout_constraintVertical_bias="1"
android:layout_marginBottom="33dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_top_up"
@ -34,7 +35,8 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_confirm_short"
app:layout_constraintStart_toEndOf="@+id/btn_scan_short"
app:layout_constraintVertical_bias="1" />
app:layout_constraintVertical_bias="1"
android:layout_marginBottom="33dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_confirm_short"
@ -50,6 +52,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/btn_top_up"
app:layout_constraintVertical_bias="1" />
app:layout_constraintVertical_bias="1"
android:layout_marginBottom="33dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -73,6 +73,7 @@
<string name="send_total_subtitle_asset_format">%s %s und %s %s werden gesendet</string>
<string name="send_balance_subtitle_format">Bilanz: %s %s</string>
<string name="send_transaction_success">Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert</string>
<string name="send_error_no_account_xlm">Um ein Konto zu erstellen, senden Sie 1+ XLM an diese Adresse</string>
<string name="details_title">Details</string>
<string name="details_row_title_cid">KartenID</string>
<string name="details_row_title_issuer">Emittent</string>

View file

@ -74,6 +74,7 @@
<string name="send_total_subtitle_asset_format">Sera envoyé %s %s et %s %s</string>
<string name="send_balance_subtitle_format">Solde : %s %s</string>
<string name="send_transaction_success">La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps </string>
<string name="send_error_no_account_xlm">Pour créer un compte, envoyez 1+ XLM à cette adresse</string>
<string name="details_title">Référénces </string>
<string name="details_row_title_cid">ID de la carte </string>
<string name="details_row_title_issuer">Emetteur </string>

View file

@ -11,12 +11,12 @@
<string name="common_camera_denied_alert_message">Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy</string>
<string name="common_camera_alert_button_settings">Impostazioni</string>
<string name="home_welcome">Benvenuti in Tangem. Hai una delle nostre schede?</string>>
<string name="home_welcome_back">Benevenuti di nuovo su Tangem Tap. Scansiona la tua scheda per iniziare.</string>
<string name="home_button_yes">Si! Scansiona scheda.</string>
<string name="home_welcome_back">Benevenuti di nuovo su Tangem Tap. Scansiona la tua carta per iniziare.</string>
<string name="home_button_yes">Si! Scansiona carta.</string>
<string name="home_button_shop">Shop</string>
<string name="home_ready_title">Preparati ad avvicinare la tua scheda sul retro del telefono.</string>
<string name="home_ready_title">Preparati ad avvicinare la tua carta sul retro del telefono.</string>
<string name="home_button_tapin">Avvicina</string>
<string name="home_button_scan">Scansiona scheda</string>
<string name="home_button_scan">Scansiona carta</string>
<string name="wallet_button_scan">Scansiona</string>
<string name="wallet_button_send">Invia</string>
<string name="wallet_button_create_wallet">Crea portafoglio</string>
@ -29,10 +29,10 @@
<string name="wallet_balance_blockchain_unreachable">Blockchain non raggiungibile</string>
<string name="wallet_error_no_account">Conto non creato</string>
<string name="wallet_error_no_account_subtitle_format">Carica %1$s+ %2$s per creare un conto</string>
<string name="wallet_error_empty_card">Scheda vuota</string>
<string name="wallet_error_empty_card_subtitle">Crea un portafoglio per iniziare ad utilizzare la tua scheda Tangem </string>
<string name="wallet_error_unsupported_blockchain">Questa scheda non è supportata</string>
<string name="wallet_error_unsupported_blockchain_subtitle"> La tua scheda Tangem è stata creata per funzionare con un\'altra applicazione. Leggere il nome e le istruzioni sulla scheda e installare l\'applicazione corretta</string>
<string name="wallet_error_empty_card">Carta vuota</string>
<string name="wallet_error_empty_card_subtitle">Crea un portafoglio per iniziare ad utilizzare la tua carta Tangem </string>
<string name="wallet_error_unsupported_blockchain">Questa carta non è supportata</string>
<string name="wallet_error_unsupported_blockchain_subtitle"> La tua carta Tangem è stata creata per funzionare con un\'altra applicazione. Leggere il nome e le istruzioni sulla carta e installare l\'applicazione corretta</string>
<string name="wallet_balance_tx_in_progress">Transazione in corso&#8230;</string>
<string name="wallet_pending_tx_sending">Invia\u0020</string>
<string name="wallet_pending_tx_sending_address_format"> su %s </string>
@ -42,7 +42,7 @@
<string name="wallet_address_button_create_payid">Crea PayString</string>
<string name="wallet_qr_title_format">portafoglio %s</string>
<string name="wallet_create_payid">Crea PayString</string>
<string name="wallet_create_payid_card_format">Scheda: %s</string>
<string name="wallet_create_payid_card_format">Carta: %s</string>
<string name="wallet_create_payid_hint">Nome PayString</string>
<string name="wallet_create_payid_domain">$payid.tangem.com</string>
<string name="wallet_create_payid_button_title">Crea</string>
@ -53,7 +53,7 @@
<string name="send_destination_hint_address_payid">Indirizzo o PayString</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_network_fee_title">Costi della rete</string>
<string name="send_amount_label">Importo</string>
<string name="send_fee_label">Commissione</string>
<string name="send_total_label">Totale</string>
@ -71,33 +71,34 @@
<string name="send_total_subtitle_asset_format">Sarà inviato %1$s %2$s e %1$s %2$s</string>
<string name="send_balance_subtitle_format">Saldo: %1$s %2$s</string>
<string name="send_transaction_success">La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo </string>
<string name="send_error_no_account_xlm">Per creare un account, invia 1+ XLM a questo indirizzo</string>
<string name="details_title">Requisiti</string>
<string name="details_row_title_cid">ID scheda</string>
<string name="details_row_title_cid">ID carta</string>
<string name="details_row_title_issuer">Emittente</string>
<string name="details_row_title_signed_hashes">Firmato</string>
<string name="details_row_subtitle_signed_hashes_format">Hash %s</string>
<string name="details_row_title_currency">Valuta dell\'applicazione</string>
<string name="details_section_title_settings">Impostazioni</string>
<string name="details_section_title_card">Scheda</string>
<string name="details_row_title_validate">Convalidare la scheda</string>
<string name="details_section_title_card">Carta</string>
<string name="details_row_title_validate">Convalidare la carta</string>
<string name="details_row_title_manage_security">Gestione del portafoglio</string>
<string name="details_row_title_erase_wallet">Cancella il portafoglio</string>
<string name="details_erase_wallet_warning">Questa azione è irreversibile. Se dopo aver eliminato il portafoglio qualcuno invia dei fondi, non potrai ritirarli.</string>
<string name="details_security_management_warning">Se dimentichi il codice, perderai la possibilità di utilizzare la scheda. Non è possibile in alcun modo ripristinare o modificare il codice se lo perdi.</string>
<string name="details_security_management_warning">Se dimentichi il codice, perderai la possibilità di utilizzare la carta. Non è possibile in alcun modo ripristinare o modificare il codice se lo perdi.</string>
<string name="details_manage_security_title">Gestione della sicurezza</string>
<string name="details_manage_security_long_tap">Mantenimento della scheda</string>
<string name="details_manage_security_long_tap_description">Questo meccanismo protegge dagli avvicinamenti senza contatto sulla scheda. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla scheda e le transazioni verranno firmate immediatamente.</string>
<string name="details_manage_security_long_tap">Mantenimento della carta</string>
<string name="details_manage_security_long_tap_description">Questo meccanismo protegge dagli avvicinamenti senza contatto sulla carta. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla carta e le transazioni verranno firmate immediatamente.</string>
<string name="details_manage_security_passcode">Password</string>
<string name="details_manage_security_passcode_description">Dovrai inserire una password prima di eseguire qualsiasi comando che modifichi lo stato della scheda.</string>
<string name="details_manage_security_passcode_description">Dovrai inserire una password prima di eseguire qualsiasi comando che modifichi lo stato della carta.</string>
<string name="details_manage_security_access_code">Codice di accesso</string>
<string name="details_manage_security_access_code_description">Prima di scansionare la scheda sarà necessario inserire il codice di accesso corretto</string>
<string name="details_manage_security_access_code_description">Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto</string>
<string name="alert_old_device_this_card">Potresti riscontrare problemi con l\'NFC su alcuni iPhone 7/7 + durante la rimozione</string>
<string name="alert_card_signed_transactions">Attenzione: questa scheda è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa scheda da una fonte inaffidabile.</string>
<string name="alert_unsupported_card">Questa scheda non è progettata per funzionare con Tangem Tap</string>
<string name="alert_developer_card">La scheda che hai scansionato è una scheda di sviluppo. Non utilizzarla come strumento di pagamento</string>
<string name="alert_card_signed_transactions">Attenzione: questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile.</string>
<string name="alert_unsupported_card">Questa carta non è progettata per funzionare con Tangem Tap</string>
<string name="alert_developer_card">La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento</string>
<string name="alert_old_card">Le schede emesse prima di settembre 2019 non possono attualmente essere utilizzate con un iPhone. Stiamo lavorando a stretto contatto con Apple per renderle disponibili nelle future versioni di iOS. </string>
<string name="initial_message_sign_header">Avvicina per firmare</string>
<string name="initial_message_sign_body">Avvicina la scheda al telefono come mostrato sopra </string>
<string name="initial_message_sign_body">Avvicina la carta al telefono come mostrato sopra </string>
<string name="initial_message_purge_wallet_body">Avvicina per cancellare il portafoglio</string>
<string name="initial_message_create_wallet_body">Avvicina per creare il portafoglio</string>
<string name="initial_message_change_access_code_body">Avvicina per modificare il codice di accesso</string>
@ -161,7 +162,7 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020.
<string name="send_error_no_target_account">Per creare un account, invia %1$s %2$s a questo indirizzo</string>
<string name="send_error_fee_request_failed">Impossibile ottenere la commissione</string>
<string name="common_accept">Accetta</string>
<string name="send_validation_amount_exceeds_balance">Amount Exceeds Balance</string>
<string name="send_validation_amount_exceeds_balance">L\'importo supera il saldo</string>
<string name="disclaimer_title">Termini del servizio</string>
<string name="wallet_notification_no_internet">Nessuna connessione a Internet</string>
@ -184,7 +185,7 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020.
<string name="wallet_create_payid_empty">Inserire prima il PayString desiderato</string>
<string name="initial_message_scan_header">Avvicina per scansionare</string>
<string name="initial_message_tap_header">Avvicina la scheda</string>
<string name="initial_message_tap_header">Avvicina la carta</string>
<string name="details_notification_erase_wallet_not_possible">Il tuo saldo su questo portafoglio non è pari a zero o hai transazioni non confermate. Non è possibile eliminare la funzione portafoglio.</string>
<string name="details_notification_security_option_already_active">E\' l\'opzione attualmente attiva</string>

View file

@ -188,6 +188,7 @@
<string name="send_error_invalid_fee_value">Invalid Fee</string>
<string name="send_error_dust_amount_format">Minimum amount is %s</string>
<string name="send_error_dust_change">Change is too small</string>
<string name="send_error_no_account_xlm">To create account send 1+ XLM to this address</string>
<string name="wallet_notification_address_copied">Address was copied to clipboard</string>

View file

@ -35,5 +35,8 @@ this wallet.
<string name="send_extras_hint_memo" translatable="false">Memo</string>
<string name="send_extras_hint_destination_tag" translatable="false">Destination tag</string>
<string name="send_error_invalid_destination_tag" translatable="false">Invalid destination tag. It won\'t be added to the transaction</string>
<string name="send_error_invalid_memo_id" translatable="false">Invalid Memo ID. It won\'t be added to the transaction</string>
</resources>

View file

@ -10,8 +10,8 @@ buildscript {
classpath "com.android.tools.build:gradle:${versions.build_gradle}"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
classpath "com.github.dcendents:android-maven-gradle-plugin:2.1"
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
// classpath 'com.google.firebase:perf-plugin:1.3.1'
}
}

View file

@ -1,4 +1,4 @@
ext.versions = [
kotlin : '1.4.10',
build_gradle: '4.1.1',
kotlin : '1.4.30',
build_gradle: '4.1.2',
]