Updated on 2026-08-14
This commit is contained in:
commit
f2a766d0a1
88 changed files with 2600 additions and 310 deletions
|
|
@ -3,7 +3,6 @@ apply plugin: 'kotlin-android'
|
|||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
//apply plugin: 'com.google.firebase.firebase-perf'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
android {
|
||||
|
|
@ -25,6 +24,7 @@ android {
|
|||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"prod\"'
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
|
|
@ -34,6 +34,7 @@ android {
|
|||
firebaseCrashlytics {
|
||||
mappingFileUploadEnabled false
|
||||
}
|
||||
buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"dev\"'
|
||||
}
|
||||
debug_beta {
|
||||
initWith debug
|
||||
|
|
@ -64,21 +65,21 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin"
|
||||
implementation 'androidx.core:core-ktx:1.3.1'
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
|
||||
implementation 'com.tangem:blockchain:1.108.0'
|
||||
implementation 'com.tangem:core:1.80.0'
|
||||
implementation 'com.tangem:sdk:1.80.0'
|
||||
implementation 'com.tangem:blockchain:1.123.0'
|
||||
implementation 'com.tangem:core:1.90.0'
|
||||
implementation 'com.tangem:sdk:1.90.0'
|
||||
|
||||
// WebView
|
||||
implementation "androidx.browser:browser:1.2.0"
|
||||
|
||||
//lifecycle
|
||||
implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
|
||||
|
|
@ -98,6 +99,7 @@ dependencies {
|
|||
// Firebase
|
||||
implementation platform('com.google.firebase:firebase-bom:26.0.0')
|
||||
implementation 'com.google.firebase:firebase-crashlytics'
|
||||
implementation 'com.google.firebase:firebase-config-ktx'
|
||||
implementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
|
||||
testImplementation 'junit:junit:4.13'
|
||||
|
|
|
|||
18
app/src/main/assets/features_dev.json
Normal file
18
app/src/main/assets/features_dev.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
{
|
||||
"name": "isWalletPayIdEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isSendingToPayIdEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isTopUpEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isCreatingTwinCardsAllowed",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
18
app/src/main/assets/features_prod.json
Normal file
18
app/src/main/assets/features_prod.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
{
|
||||
"name": "isWalletPayIdEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isSendingToPayIdEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isTopUpEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isCreatingTwinCardsAllowed",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
|
|
@ -10,7 +10,7 @@ import com.tangem.CardFilter
|
|||
import com.tangem.Config
|
||||
import com.tangem.Log
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.common.extensions.CardType
|
||||
import com.tangem.commands.common.card.CardType
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tap.common.redux.NotificationsHandler
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -87,7 +87,8 @@ class MainActivity : AppCompatActivity() {
|
|||
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
|
||||
if (tag != null) {
|
||||
intent.action = null
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home, false))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatch(HomeAction.ReadCard)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.app.Application
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.google.firebase.remoteconfig.ktx.remoteConfig
|
||||
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
|
||||
import com.tangem.tap.common.images.PicassoHelper
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.appReducer
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.config.ConfigManager
|
||||
import com.tangem.tap.domain.config.LocalLoader
|
||||
import com.tangem.tap.domain.config.RemoteLoader
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.createMoshi
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import org.rekotlin.Store
|
||||
|
|
@ -20,11 +28,31 @@ lateinit var preferencesStorage: PreferencesStorage
|
|||
class TapApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
|
||||
this.minimumFetchIntervalInSeconds = 60
|
||||
})
|
||||
} else {
|
||||
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
|
||||
this.minimumFetchIntervalInSeconds = 3600
|
||||
})
|
||||
}
|
||||
|
||||
NetworkConnectivity.createInstance(store, this)
|
||||
preferencesStorage = PreferencesStorage(this)
|
||||
PicassoHelper.initPicassoWithCaching(this)
|
||||
|
||||
loadConfigs()
|
||||
}
|
||||
|
||||
|
||||
private fun loadConfigs() {
|
||||
val moshi = createMoshi()
|
||||
val localLoader = LocalLoader(this, moshi)
|
||||
val remoteLoader = RemoteLoader(moshi)
|
||||
val configManager = ConfigManager(localLoader, remoteLoader)
|
||||
configManager.load { store.dispatch(GlobalAction.SetConfigManager(configManager)) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.tap
|
||||
|
||||
object TapConfig {
|
||||
const val usePayId: Boolean = true
|
||||
const val useTopUp: Boolean = true
|
||||
const val coinMarketCapKey = "f6622117-c043-47a0-8975-9d673ce484de"
|
||||
const val moonPayApiKey = "pk_live_YTDl4Uei4411FXLAFe2rn8R2qWSiQcm"
|
||||
const val moonPayApiSecretKey = "sk_live_7wMjoyGTGAB3i1suQHszCWRRqJBRPsQF"
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.common.card.Card
|
||||
|
||||
|
||||
interface AnalyticsHandler {
|
||||
fun triggerEvent(event: AnalyticsEvent, card: Card?)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import android.os.Bundle
|
|||
import androidx.core.os.bundleOf
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.common.card.Card
|
||||
|
||||
object FirebaseAnalyticsHandler: AnalyticsHandler {
|
||||
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
|
||||
|
|
@ -17,7 +17,7 @@ object FirebaseAnalyticsHandler: AnalyticsHandler {
|
|||
return bundleOf(
|
||||
AnalyticsParam.BLOCKCHAIN.param to card.cardData?.blockchainName,
|
||||
AnalyticsParam.BATCH_ID.param to card.cardData?.batchId,
|
||||
AnalyticsParam.FIRMWARE.param to card.firmwareVersion
|
||||
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,21 +7,20 @@ import com.tangem.tap.common.redux.navigation.AppScreen
|
|||
import com.tangem.tap.features.details.ui.DetailsConfirmFragment
|
||||
import com.tangem.tap.features.details.ui.DetailsFragment
|
||||
import com.tangem.tap.features.details.ui.DetailsSecurityFragment
|
||||
import com.tangem.tap.features.details.ui.twins.CreateTwinWalletFragment
|
||||
import com.tangem.tap.features.details.ui.twins.TwinWalletWarningFragment
|
||||
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
|
||||
import com.tangem.tap.features.home.HomeFragment
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.TwinsOnboardingFragment
|
||||
import com.tangem.wallet.R
|
||||
|
||||
fun FragmentActivity.openFragment(screen: AppScreen, addToBackStack: Boolean = true) {
|
||||
val transaction = this.supportFragmentManager.beginTransaction()
|
||||
.replace(
|
||||
R.id.fragment_container,
|
||||
fragmentFactory(screen),
|
||||
screen.name
|
||||
)
|
||||
transaction.replace(R.id.fragment_container, fragmentFactory(screen), screen.name)
|
||||
if (addToBackStack && screen != AppScreen.Home) transaction.addToBackStack(null)
|
||||
transaction.commit();
|
||||
transaction.commitAllowingStateLoss()
|
||||
}
|
||||
|
||||
fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {
|
||||
|
|
@ -44,5 +43,8 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.DetailsConfirm -> DetailsConfirmFragment()
|
||||
AppScreen.DetailsSecurity -> DetailsSecurityFragment()
|
||||
AppScreen.Disclaimer -> DisclaimerFragment()
|
||||
AppScreen.CreateTwinWalletWarning -> TwinWalletWarningFragment()
|
||||
AppScreen.CreateTwinWallet -> CreateTwinWalletFragment()
|
||||
AppScreen.TwinsOnboarding -> TwinsOnboardingFragment()
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ import android.util.TypedValue
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
|
|
@ -28,6 +30,11 @@ fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
|
|||
return ContextCompat.getDrawable(this, drawableResId)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun Fragment.getColor(@ColorRes colorRes: Int): Int {
|
||||
return ContextCompat.getColor(requireContext(), colorRes)
|
||||
}
|
||||
|
||||
fun View.getString(@StringRes id: Int): String {
|
||||
return context.getString(id)
|
||||
}
|
||||
|
|
@ -57,22 +64,22 @@ fun View.makeInvisible() {
|
|||
}
|
||||
|
||||
fun Context.dpToPixels(dp: Int): Int =
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics
|
||||
).toInt()
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
fun MaterialCardView.setMargins(
|
||||
marginLeftDp: Int = 16,
|
||||
marginTopDp: Int = 8,
|
||||
marginRightDp: Int = 16,
|
||||
marginBottomDp: Int = 8
|
||||
marginLeftDp: Int = 16,
|
||||
marginTopDp: Int = 8,
|
||||
marginRightDp: Int = 16,
|
||||
marginBottomDp: Int = 8
|
||||
) {
|
||||
val params = this.layoutParams
|
||||
(params as ViewGroup.MarginLayoutParams).setMargins(
|
||||
context.dpToPixels(marginLeftDp),
|
||||
context.dpToPixels(marginTopDp),
|
||||
context.dpToPixels(marginRightDp),
|
||||
context.dpToPixels(marginBottomDp)
|
||||
context.dpToPixels(marginLeftDp),
|
||||
context.dpToPixels(marginTopDp),
|
||||
context.dpToPixels(marginRightDp),
|
||||
context.dpToPixels(marginBottomDp)
|
||||
)
|
||||
this.layoutParams = params
|
||||
}
|
||||
|
|
@ -82,31 +89,30 @@ fun Activity.setSystemBarTextColor(setTextDark: Boolean) {
|
|||
val flags = this.window.decorView.systemUiVisibility
|
||||
// Update the SystemUiVisibility dependening on whether we want a Light or Dark theme.
|
||||
this.window.decorView.systemUiVisibility =
|
||||
if (setTextDark) {
|
||||
flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
||||
} else {
|
||||
flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
}
|
||||
if (setTextDark) {
|
||||
flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
||||
} else {
|
||||
flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun String.colorSegment(
|
||||
context: Context,
|
||||
color: Int,
|
||||
startIndex: Int = 0,
|
||||
endIndex: Int = this.length
|
||||
context: Context,
|
||||
color: Int,
|
||||
startIndex: Int = 0,
|
||||
endIndex: Int = this.length
|
||||
): Spannable {
|
||||
return this.toSpannable()
|
||||
.also { spannable ->
|
||||
spannable.setSpan(
|
||||
ForegroundColorSpan(ContextCompat.getColor(context, color)),
|
||||
startIndex,
|
||||
endIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
.also { spannable ->
|
||||
spannable.setSpan(
|
||||
ForegroundColorSpan(ContextCompat.getColor(context, color)),
|
||||
startIndex,
|
||||
endIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun View.hideKeyboard() {
|
||||
|
|
@ -122,7 +128,8 @@ fun Context.copyToClipboard(value: Any, label: String = "") {
|
|||
}
|
||||
|
||||
fun Context.getFromClipboard(default: CharSequence? = null): CharSequence? {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return default
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
|
||||
?: return default
|
||||
val clipData = clipboard.primaryClip ?: return default
|
||||
if (clipData.itemCount == 0) return default
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.tap.domain.config.ConfigManager
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -15,4 +16,5 @@ sealed class GlobalAction : Action {
|
|||
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
|
||||
}
|
||||
data class UpdateWalletSignedHashes(val walletSignedHashes: Int?) : GlobalAction()
|
||||
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
|
||||
}
|
||||
|
|
@ -33,6 +33,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
globalState
|
||||
}
|
||||
}
|
||||
is GlobalAction.SetConfigManager -> {
|
||||
globalState.copy(configManager = action.configManager)
|
||||
}
|
||||
else -> globalState
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.commands.common.network.TangemService
|
|||
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.domain.config.ConfigManager
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
import org.rekotlin.StateType
|
||||
|
|
@ -16,6 +17,7 @@ data class GlobalState(
|
|||
val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(),
|
||||
val tangemService: TangemService = TangemService(),
|
||||
val conversionRates: ConversionRates = ConversionRates(emptyMap()),
|
||||
val configManager: ConfigManager? = null,
|
||||
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY
|
||||
) : StateType
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,7 @@ data class NavigationState(
|
|||
val activity: WeakReference<FragmentActivity>? = null
|
||||
) : StateType
|
||||
|
||||
enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer }
|
||||
enum class AppScreen {
|
||||
Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer,
|
||||
CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding
|
||||
}
|
||||
|
|
@ -3,8 +3,9 @@ package com.tangem.tap.domain
|
|||
import androidx.activity.ComponentActivity
|
||||
import com.tangem.*
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.CardType
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.CardType
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
|
|
@ -12,6 +13,7 @@ import com.tangem.tap.common.analytics.AnalyticsHandler
|
|||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.tasks.ScanNoteTask
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -64,8 +66,8 @@ class TangemSdkManager(val activity: ComponentActivity) {
|
|||
), cardId, initialMessage = Message(activity.getString(R.string.initial_message_tap_header)))
|
||||
}
|
||||
|
||||
private suspend fun <T : CommandResponse> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null
|
||||
suspend fun <T : CommandResponse> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
): CompletionResult<T> =
|
||||
withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { continuation ->
|
||||
|
|
@ -76,9 +78,13 @@ class TangemSdkManager(val activity: ComponentActivity) {
|
|||
}
|
||||
|
||||
private suspend fun <T : CommandResponse> runTaskAsyncReturnOnMain(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
): CompletionResult<T> {
|
||||
val result = runTaskAsync(runnable, cardId, initialMessage)
|
||||
return withContext(Dispatchers.Main) { result }
|
||||
}
|
||||
|
||||
fun changeDisplayedCardIdNumbersCount(card: Card) {
|
||||
tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTwinCard()) 4 else null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,23 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.commands.CardStatus
|
||||
import com.tangem.commands.common.card.CardStatus
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.TapConfig
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.config.ConfigManager
|
||||
import com.tangem.tap.domain.extensions.amountToCreateAccount
|
||||
import com.tangem.tap.domain.extensions.isNoAccountError
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
|
|
@ -65,17 +69,13 @@ class TapWalletManager {
|
|||
}
|
||||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName) {
|
||||
val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet
|
||||
val blockchainCurrency = wallet?.blockchain?.currency
|
||||
val tokenCurrency = wallet?.token?.symbol
|
||||
val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet ?: return
|
||||
|
||||
val blockchainRate = blockchainCurrency?.let { coinMarketCapService.getRate(it, fiatCurrency) }
|
||||
val tokenRate = tokenCurrency?.let { coinMarketCapService.getRate(it, fiatCurrency) }
|
||||
val currencyList = wallet.getTokens().map { it.symbol }.toMutableList()
|
||||
currencyList.add(wallet.blockchain.currency)
|
||||
|
||||
val results = mutableListOf<Pair<CryptoCurrencyName, Result<BigDecimal>?>>()
|
||||
if (blockchainCurrency != null) results.add(blockchainCurrency to blockchainRate)
|
||||
if (tokenCurrency != null) results.add(tokenCurrency to tokenRate)
|
||||
|
||||
currencyList.forEach { results.add(it to coinMarketCapService.getRate(it, fiatCurrency)) }
|
||||
handleFiatRatesResult(results)
|
||||
}
|
||||
|
||||
|
|
@ -84,9 +84,33 @@ class TapWalletManager {
|
|||
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card)
|
||||
}
|
||||
TapWorkarounds.updateCard(data.card)
|
||||
val configManager = store.state.globalState.configManager
|
||||
if (TapWorkarounds.isStart2Coin) {
|
||||
configManager?.turnOff(ConfigManager.isWalletPayIdEnabled)
|
||||
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.turnOff(ConfigManager.isTopUpEnabled)
|
||||
} else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin
|
||||
|| data.card.cardData?.blockchainName == Blockchain.Bitcoin.id){
|
||||
configManager?.turnOff(ConfigManager.isWalletPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
} else {
|
||||
configManager?.resetToDefault(ConfigManager.isWalletPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.ResetState)
|
||||
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
|
||||
if (data.card.isTwinCard()) {
|
||||
val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId)
|
||||
val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId)
|
||||
if (secondCardId != null && cardNumber != null) {
|
||||
store.dispatch(WalletAction.TwinsAction.SetTwinCard(
|
||||
secondCardId, cardNumber, isCreatingTwinCardsAllowed = true
|
||||
))
|
||||
}
|
||||
}
|
||||
loadData(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -100,14 +124,14 @@ class TapWalletManager {
|
|||
store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
|
||||
return@withContext
|
||||
}
|
||||
val config = store.state.globalState.configManager?.config ?: return@withContext
|
||||
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
data.walletManager.wallet, data.verifyResponse?.artworkInfo?.id,
|
||||
TapWorkarounds.isStart2Coin != true && TapConfig.useTopUp
|
||||
))
|
||||
data.walletManager.wallet, data.verifyResponse?.artworkInfo?.id, config.isTopUpEnabled))
|
||||
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
|
||||
store.dispatch(WalletAction.LoadFiatRate)
|
||||
store.dispatch(WalletAction.LoadPayId)
|
||||
} else if (data.card.status == CardStatus.Empty) {
|
||||
} else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) {
|
||||
store.dispatch(WalletAction.EmptyWallet)
|
||||
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
|
||||
} else {
|
||||
|
|
@ -135,7 +159,8 @@ class TapWalletManager {
|
|||
val error = result.error
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
if (error != null && blockchain.isNoAccountError(error)) {
|
||||
val amountToCreateAccount = blockchain.amountToCreateAccount(walletManager.wallet.token)
|
||||
val token = walletManager.wallet.getFirstToken()
|
||||
val amountToCreateAccount = blockchain.amountToCreateAccount(token)
|
||||
if (amountToCreateAccount != null) {
|
||||
store.dispatch(WalletAction.LoadWallet.NoAccount(amountToCreateAccount.toString()))
|
||||
return@withContext
|
||||
|
|
@ -150,8 +175,8 @@ class TapWalletManager {
|
|||
|
||||
private suspend fun loadPayIdIfNeeded(): Result<String?>? {
|
||||
val scanNoteResponse = store.state.globalState.scanNoteResponse
|
||||
if (!TapConfig.usePayId ||
|
||||
scanNoteResponse?.walletManager?.wallet?.blockchain?.isPayIdSupported() == false) {
|
||||
if (store.state.globalState.configManager?.config?.isWalletPayIdEnabled == false
|
||||
|| scanNoteResponse?.walletManager?.wallet?.blockchain?.isPayIdSupported() == false) {
|
||||
return null
|
||||
}
|
||||
val cardId = scanNoteResponse?.card?.cardId
|
||||
|
|
@ -166,11 +191,12 @@ class TapWalletManager {
|
|||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val payId = result.data
|
||||
if (TapWorkarounds.isPayIdEnabled() == false) {
|
||||
val config = store.state.globalState.configManager?.config
|
||||
if (config?.isWalletPayIdEnabled == false) {
|
||||
store.dispatch(WalletAction.DisablePayId)
|
||||
return@withContext
|
||||
}
|
||||
val payId = result.data
|
||||
if (payId == null) {
|
||||
store.dispatch(WalletAction.LoadPayId.NotCreated)
|
||||
} else {
|
||||
|
|
@ -198,4 +224,8 @@ class TapWalletManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Wallet.getFirstToken(): Token? {
|
||||
return getTokens().toList().getOrNull(0)
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.common.card.Card
|
||||
import java.util.*
|
||||
|
||||
object TapWorkarounds {
|
||||
|
|
@ -12,10 +12,5 @@ object TapWorkarounds {
|
|||
isStart2Coin = card.cardData?.issuerName?.toLowerCase(Locale.US) == START_2_COIN_ISSUER
|
||||
}
|
||||
|
||||
fun isPayIdEnabled(): Boolean {
|
||||
if (isStart2Coin) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const val START_2_COIN_ISSUER = "start2coin"
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.tap.TapConfig
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import org.spongycastle.util.encoders.Base64
|
||||
import javax.crypto.Mac
|
||||
|
|
@ -19,12 +18,12 @@ class TopUpHelper {
|
|||
// private const val REDIRECT_URL_PATH = "&redirectUrl="
|
||||
private const val SIGNATURE_PATH = "&signature="
|
||||
|
||||
fun getUrl(cryptoCurrencyName: CryptoCurrencyName, walletAddress: String): String {
|
||||
val originalQuery = API_KEY_PATH + TapConfig.moonPayApiKey.urlEncode() +
|
||||
fun getUrl(cryptoCurrencyName: CryptoCurrencyName, walletAddress: String, apiKey: String, secretKey: String): String {
|
||||
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, TapConfig.moonPayApiSecretKey)
|
||||
val signature = createSignature(originalQuery, secretKey)
|
||||
|
||||
return BASE_URL + originalQuery + SIGNATURE_PATH + signature.urlEncode()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.tap.domain.config
|
||||
|
||||
import android.content.Context
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.google.firebase.remoteconfig.ktx.remoteConfig
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ConfigLoader {
|
||||
fun loadConfig(onComplete: (ConfigModel) -> Unit)
|
||||
|
||||
companion object {
|
||||
const val featuresName = "features_${BuildConfig.CONFIG_ENVIRONMENT}"
|
||||
const val configValuesName = "config_${BuildConfig.CONFIG_ENVIRONMENT}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LocalLoader(
|
||||
private val context: Context,
|
||||
private val moshi: Moshi
|
||||
) : ConfigLoader {
|
||||
|
||||
override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
|
||||
val config = try {
|
||||
val featureType = Types.newParameterizedType(List::class.java, FeatureModel::class.java)
|
||||
val featureAdapter: JsonAdapter<List<FeatureModel>> = moshi.adapter(featureType)
|
||||
val valuesType = Types.newParameterizedType(List::class.java, ConfigValueModel::class.java)
|
||||
val valuesAdapter: JsonAdapter<List<ConfigValueModel>> = moshi.adapter(valuesType)
|
||||
|
||||
val jsonFeatures = readAssetAsString(ConfigLoader.featuresName)
|
||||
val jsonConfigValues = readAssetAsString(ConfigLoader.configValuesName)
|
||||
|
||||
ConfigModel(featureAdapter.fromJson(jsonFeatures) ?: listOf(),
|
||||
valuesAdapter.fromJson(jsonConfigValues) ?: listOf())
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
ConfigModel.empty()
|
||||
}
|
||||
onComplete(config)
|
||||
}
|
||||
|
||||
private fun readAssetAsString(fileName: String): String {
|
||||
return context.assets.open("$fileName.json").bufferedReader().readText()
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteLoader(
|
||||
private val moshi: Moshi
|
||||
) : ConfigLoader {
|
||||
|
||||
override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
|
||||
val emptyConfig = ConfigModel.empty()
|
||||
val remoteConfig = Firebase.remoteConfig
|
||||
remoteConfig.fetchAndActivate().addOnCompleteListener {
|
||||
if (it.isSuccessful) {
|
||||
val config = remoteConfig.getValue(ConfigLoader.featuresName)
|
||||
val jsonConfig = config.asString()
|
||||
if (jsonConfig.isEmpty()) {
|
||||
onComplete(emptyConfig)
|
||||
return@addOnCompleteListener
|
||||
}
|
||||
val featureType = Types.newParameterizedType(List::class.java, FeatureModel::class.java)
|
||||
val featureAdapter: JsonAdapter<List<FeatureModel>> = moshi.adapter(featureType)
|
||||
onComplete(ConfigModel(featureAdapter.fromJson(jsonConfig) ?: listOf(), listOf()))
|
||||
} else {
|
||||
onComplete(emptyConfig)
|
||||
}
|
||||
}.addOnFailureListener {
|
||||
FirebaseCrashlytics.getInstance().recordException(it)
|
||||
onComplete(emptyConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
108
app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt
Normal file
108
app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package com.tangem.tap.domain.config
|
||||
|
||||
import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class Config(
|
||||
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
|
||||
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
|
||||
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
|
||||
val isWalletPayIdEnabled: Boolean = true,
|
||||
val isSendingToPayIdEnabled: Boolean = true,
|
||||
val isTopUpEnabled: Boolean = false,
|
||||
val isCreatingTwinCardsAllowed: Boolean = false
|
||||
)
|
||||
|
||||
class ConfigManager(
|
||||
private val localLoader: ConfigLoader,
|
||||
private val remoteLoader: ConfigLoader
|
||||
) {
|
||||
|
||||
var config: Config = Config()
|
||||
private set
|
||||
|
||||
private var defaultConfig = Config()
|
||||
|
||||
fun load(onComplete: VoidCallback? = null) {
|
||||
localLoader.loadConfig { config ->
|
||||
config.features?.forEach { setupFeature(it.name, it.value) }
|
||||
config.configValues?.forEach { setupKey(it.name, it.value) }
|
||||
onComplete?.invoke()
|
||||
}
|
||||
// remoteLoader.loadConfig { config ->
|
||||
// config.features?.forEach { setupFeature(it.name, it.value) }
|
||||
// onComplete?.invoke()
|
||||
// }
|
||||
}
|
||||
|
||||
fun turnOff(name: String) {
|
||||
when (name) {
|
||||
isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = false)
|
||||
isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = false)
|
||||
isTopUpEnabled -> config = config.copy(isTopUpEnabled = false)
|
||||
isCreatingTwinCardsAllowed -> config = config.copy(isCreatingTwinCardsAllowed = false)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetToDefault(name: String) {
|
||||
when (name) {
|
||||
isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = defaultConfig.isWalletPayIdEnabled)
|
||||
isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
|
||||
isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
|
||||
isCreatingTwinCardsAllowed -> config =
|
||||
config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFeature(name: String, value: Boolean) {
|
||||
val newValue = value ?: return
|
||||
|
||||
when (name) {
|
||||
isWalletPayIdEnabled -> {
|
||||
config = config.copy(isWalletPayIdEnabled = newValue)
|
||||
defaultConfig = defaultConfig.copy(isWalletPayIdEnabled = newValue)
|
||||
}
|
||||
isSendingToPayIdEnabled -> {
|
||||
config = config.copy(isSendingToPayIdEnabled = newValue)
|
||||
defaultConfig = defaultConfig.copy(isSendingToPayIdEnabled = newValue)
|
||||
}
|
||||
isTopUpEnabled -> {
|
||||
config = config.copy(isTopUpEnabled = newValue)
|
||||
defaultConfig = defaultConfig.copy(isTopUpEnabled = newValue)
|
||||
}
|
||||
isCreatingTwinCardsAllowed -> {
|
||||
config = config.copy(isCreatingTwinCardsAllowed = newValue)
|
||||
defaultConfig = defaultConfig.copy(isCreatingTwinCardsAllowed = newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupKey(name: String, value: String) {
|
||||
when (name) {
|
||||
coinMarketCapKey -> {
|
||||
config = config.copy(coinMarketCapKey = value)
|
||||
defaultConfig = defaultConfig.copy(coinMarketCapKey = value)
|
||||
}
|
||||
moonPayApiKey -> {
|
||||
config = config.copy(moonPayApiKey = value)
|
||||
defaultConfig = defaultConfig.copy(moonPayApiKey = value)
|
||||
}
|
||||
moonPayApiSecretKey -> {
|
||||
config = config.copy(moonPayApiSecretKey = value)
|
||||
defaultConfig = defaultConfig.copy(moonPayApiSecretKey = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val isWalletPayIdEnabled = "isWalletPayIdEnabled"
|
||||
const val isSendingToPayIdEnabled = "isSendingToPayIdEnabled"
|
||||
const val isCreatingTwinCardsAllowed = "isCreatingTwinCardsAllowed"
|
||||
const val isTopUpEnabled = "isTopUpEnabled"
|
||||
const val coinMarketCapKey = "coinMarketCapKey"
|
||||
const val moonPayApiKey = "moonPayApiKey"
|
||||
const val moonPayApiSecretKey = "moonPayApiSecretKey"
|
||||
}
|
||||
}
|
||||
26
app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt
Normal file
26
app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.tap.domain.config
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
interface BaseConfigModel<V> {
|
||||
val name: String
|
||||
val value: V?
|
||||
}
|
||||
|
||||
class FeatureModel(
|
||||
override val name: String,
|
||||
override val value: Boolean
|
||||
) : BaseConfigModel<Boolean>
|
||||
|
||||
class ConfigValueModel(
|
||||
override val name: String,
|
||||
override val value: String
|
||||
) : BaseConfigModel<String>
|
||||
|
||||
class ConfigModel(val features: List<FeatureModel>?, val configValues: List<ConfigValueModel>?) {
|
||||
companion object {
|
||||
fun empty(): ConfigModel = ConfigModel(listOf(), listOf())
|
||||
}
|
||||
}
|
||||
|
|
@ -6,20 +6,25 @@ import com.tangem.TangemError
|
|||
import com.tangem.TangemSdkError
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CardStatus
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.Product
|
||||
import com.tangem.commands.ReadIssuerDataCommand
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.CardStatus
|
||||
import com.tangem.commands.common.card.masks.Product
|
||||
import com.tangem.commands.verifycard.VerifyCardCommand
|
||||
import com.tangem.commands.verifycard.VerifyCardResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tasks.ScanTask
|
||||
|
||||
data class ScanNoteResponse(
|
||||
val walletManager: WalletManager?,
|
||||
val card: Card,
|
||||
val verifyResponse: VerifyCardResponse? = null
|
||||
val verifyResponse: VerifyCardResponse? = null,
|
||||
val secondTwinPublicKey: String? = null
|
||||
) : CommandResponse
|
||||
|
||||
class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteResponse> {
|
||||
|
|
@ -42,31 +47,75 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
return@run
|
||||
}
|
||||
|
||||
if (card.isTwinCard()) {
|
||||
dealWithTwinCard(card, session, callback)
|
||||
return@run
|
||||
}
|
||||
|
||||
val walletManager = try {
|
||||
WalletManagerFactory.makeWalletManager(card)
|
||||
} catch (exception: Exception) {
|
||||
return@run callback(CompletionResult.Success(ScanNoteResponse(null, card)))
|
||||
}
|
||||
VerifyCardCommand(true).run(session) { verifyResult ->
|
||||
when (verifyResult) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(
|
||||
walletManager, card, verifyResult.data
|
||||
)))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(TangemSdkError.VerificationFailed()))
|
||||
}
|
||||
}
|
||||
}
|
||||
verifyCard(walletManager, card, null, session, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyCard(
|
||||
walletManager: WalletManager?, card: Card, publicKey: String? = null,
|
||||
session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
|
||||
) {
|
||||
|
||||
VerifyCardCommand(true).run(session) { verifyResult ->
|
||||
when (verifyResult) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(
|
||||
walletManager, card, verifyResult.data, publicKey
|
||||
)))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(TangemSdkError.VerificationFailed()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dealWithTwinCard(
|
||||
card: Card, session: CardSession,
|
||||
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
|
||||
) {
|
||||
ReadIssuerDataCommand().run(session) { readDataResult ->
|
||||
when (readDataResult) {
|
||||
is CompletionResult.Success -> {
|
||||
val verified = TwinCardsManager.verifyTwinPublicKey(
|
||||
readDataResult.data.issuerData, card.walletPublicKey
|
||||
)
|
||||
if (verified) {
|
||||
val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
|
||||
val walletManager = try {
|
||||
WalletManagerFactory.makeMultisigWalletManager(card, twinPublicKey)
|
||||
} catch (exception: Exception) {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
|
||||
return@run
|
||||
}
|
||||
verifyCard(walletManager, card, twinPublicKey.toHexString(), session, callback)
|
||||
return@run
|
||||
} else {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getErrorIfExcludedCard(card: Card): TangemError? {
|
||||
if (card.cardData?.productMask != null &&
|
||||
card.cardData?.productMask?.contains(Product.Note) != true) {
|
||||
val productMask = card.cardData?.productMask
|
||||
if (productMask != null && // product mask is on cards v2.30 and later
|
||||
!productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard)) {
|
||||
return TapSdkError.CardForDifferentApp
|
||||
}
|
||||
if (excludedBatches.contains(card.cardData?.batchId)) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.CardSessionRunnable
|
||||
import com.tangem.commands.CreateWalletResponse
|
||||
import com.tangem.commands.PurgeWalletCommand
|
||||
import com.tangem.commands.common.card.CardStatus
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tasks.CreateWalletTask
|
||||
|
||||
class CreateFirstTwinWalletTask : CardSessionRunnable<CreateWalletResponse> {
|
||||
override val requiresPin2 = false
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
if (session.environment.card?.walletPublicKey != null) {
|
||||
PurgeWalletCommand().run(session) { response ->
|
||||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card = session.environment.card?.copy(status = CardStatus.Empty)
|
||||
CreateWalletTask().run(session) { callback(it) }
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CreateWalletTask().run(session) { callback(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.CardSessionRunnable
|
||||
import com.tangem.Message
|
||||
import com.tangem.commands.CreateWalletResponse
|
||||
import com.tangem.commands.PurgeWalletCommand
|
||||
import com.tangem.commands.common.card.CardStatus
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.tasks.CreateWalletTask
|
||||
|
||||
class CreateSecondTwinWalletTask(
|
||||
private val firstPublicKey: String,
|
||||
private val preparingMessage: Message,
|
||||
private val creatingWalletMessage: Message
|
||||
) : CardSessionRunnable<CreateWalletResponse> {
|
||||
override val requiresPin2 = true
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
if (session.environment.card?.walletPublicKey != null) {
|
||||
session.setInitialMessage(preparingMessage)
|
||||
PurgeWalletCommand().run(session) { response ->
|
||||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card =
|
||||
session.environment.card?.copy(status = CardStatus.Empty)
|
||||
finishTask(session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finishTask(session, callback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishTask(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
session.setInitialMessage(creatingWalletMessage)
|
||||
CreateWalletTask().run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card =
|
||||
session.environment.card?.copy(status = CardStatus.Loaded)
|
||||
WriteProtectedIssuerDataTask(
|
||||
firstPublicKey.hexToBytes(), TwinCardsManager.issuerKeys
|
||||
).run(session) { writeResult ->
|
||||
when (writeResult) {
|
||||
is CompletionResult.Success -> callback(result)
|
||||
is CompletionResult.Failure ->
|
||||
callback(CompletionResult.Failure(writeResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.CardSessionRunnable
|
||||
import com.tangem.KeyPair
|
||||
import com.tangem.commands.ReadCommand
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.tasks.ScanNoteTask
|
||||
|
||||
class FinalizeTwinTask(
|
||||
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair
|
||||
) : CardSessionRunnable<ScanNoteResponse> {
|
||||
override val requiresPin2 = true
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit) {
|
||||
WriteProtectedIssuerDataTask(twinPublicKey, issuerKeys).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
ReadCommand().run(session) { readResult ->
|
||||
when (readResult) {
|
||||
is CompletionResult.Success -> ScanNoteTask(readResult.data).run(session, callback)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import com.tangem.KeyPair
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
||||
class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
|
||||
|
||||
private val currentCardId: String = scanNoteResponse.card.cardId
|
||||
private val secondCardId: String? = TwinsHelper.getTwinsCardId(currentCardId)
|
||||
|
||||
private var currentCardPublicKey: String? = null
|
||||
private var secondCardPublicKey: String? = null
|
||||
|
||||
suspend fun createFirstWallet(message: Message): SimpleResult {
|
||||
val response = tangemSdkManager.runTaskAsync(
|
||||
CreateFirstTwinWalletTask(), currentCardId, message
|
||||
)
|
||||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
currentCardPublicKey = response.data.walletPublicKey.toHexString()
|
||||
return SimpleResult.Success
|
||||
}
|
||||
is CompletionResult.Failure -> return SimpleResult.failure(response.error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
suspend fun createSecondWallet(
|
||||
initialMessage: Message,
|
||||
preparingMessage: Message,
|
||||
creatingWalletMessage: Message
|
||||
): SimpleResult {
|
||||
val response = tangemSdkManager.runTaskAsync(
|
||||
CreateSecondTwinWalletTask(currentCardPublicKey!!, preparingMessage, creatingWalletMessage),
|
||||
secondCardId, initialMessage
|
||||
)
|
||||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
secondCardPublicKey = response.data.walletPublicKey.toHexString()
|
||||
return SimpleResult.Success
|
||||
}
|
||||
is CompletionResult.Failure -> return SimpleResult.failure(response.error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun complete(message: Message): Result<ScanNoteResponse> {
|
||||
val response = tangemSdkManager.runTaskAsync(
|
||||
FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeys),
|
||||
currentCardId, message
|
||||
)
|
||||
return when (response) {
|
||||
is CompletionResult.Success -> Result.Success(response.data)
|
||||
is CompletionResult.Failure -> Result.failure(response.error)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val issuerKeys = KeyPair(
|
||||
privateKey = "F9F4C50636C9E6FC65F92655BD5C21C85A5F6A34DCD0F1E75FCEA1980FE242F5".hexToBytes(),
|
||||
publicKey = ("048196AA4B410AC44A3B9CCE18E7BE226AEA070ACC83A9CF67540F" +
|
||||
"AC49AF25129F6A538A28AD6341358E3C4F9963064F" +
|
||||
"7E365372A651D374E5C23CDD37FD099BF2").hexToBytes()
|
||||
)
|
||||
|
||||
fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean {
|
||||
if (issuerData.size < 65) return false
|
||||
val publicKey = issuerData.sliceArray(0 until 65)
|
||||
val signedKey = issuerData.sliceArray(65 until issuerData.size)
|
||||
return (cardWalletPublicKey != null &&
|
||||
CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
84
app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt
Normal file
84
app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.masks.Product
|
||||
import com.tangem.tap.common.extensions.isEven
|
||||
|
||||
class TwinsHelper {
|
||||
companion object {
|
||||
const val TWIN_FILE_NAME = "TwinPublicKey"
|
||||
|
||||
fun getTwinCardNumber(cardId: String): TwinCardNumber? {
|
||||
return when {
|
||||
firstCardSeries.map { cardId.startsWith(it) }.contains(true) -> {
|
||||
TwinCardNumber.First
|
||||
}
|
||||
secondCardSeries.map { cardId.startsWith(it) }.contains(true) -> {
|
||||
TwinCardNumber.Second
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getTwinsCardId(cardId: String): String? {
|
||||
val cardIdWithNewSeries = when (getTwinCardNumber(cardId) ?: return null) {
|
||||
TwinCardNumber.First -> {
|
||||
val index = if (cardId.startsWith(firstCardSeries[0])) 0 else 1
|
||||
cardId.replace(firstCardSeries[index], secondCardSeries[index])
|
||||
}
|
||||
TwinCardNumber.Second -> {
|
||||
val index = if (cardId.startsWith(secondCardSeries[0])) 0 else 1
|
||||
cardId.replace(secondCardSeries[index], firstCardSeries[index])
|
||||
}
|
||||
}
|
||||
val cardIdWithoutChecksum = cardIdWithNewSeries.dropLast(1)
|
||||
val checkSum = cardIdWithoutChecksum.calculateLuhn()
|
||||
return cardIdWithoutChecksum + checkSum
|
||||
}
|
||||
|
||||
fun getTwinCardIdForUser(cardId: String): String {
|
||||
if (cardId.length < 16) return cardId
|
||||
|
||||
val twinCardId = cardId.substring(11..14)
|
||||
val twinCardNumber = getTwinCardNumber(cardId)?.number ?: 1
|
||||
return "$twinCardId #$twinCardNumber"
|
||||
}
|
||||
|
||||
private val firstCardSeries = listOf("CB61", "CB64")
|
||||
private val secondCardSeries = listOf("CB62", "CB65")
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.calculateLuhn(): Int {
|
||||
val checksum = this.reversed()
|
||||
.mapIndexed { index, c ->
|
||||
val digit = if (c in '0'..'9') c - '0' else c - 'A'
|
||||
if (!index.isEven()) {
|
||||
digit
|
||||
} else {
|
||||
val newDigit = digit * 2
|
||||
if (newDigit >= 10) newDigit - 9 else newDigit
|
||||
}
|
||||
}.sum()
|
||||
.rem(10)
|
||||
return (10 - checksum) % 10
|
||||
}
|
||||
|
||||
enum class TwinCardNumber(val number: Int) {
|
||||
First(1), Second(2);
|
||||
|
||||
fun pairNumber(): TwinCardNumber = when (this) {
|
||||
First -> Second
|
||||
Second -> First
|
||||
}
|
||||
}
|
||||
|
||||
fun Card.isTwinCard(): Boolean {
|
||||
return this.cardData?.productMask?.contains(Product.TwinCard) == true
|
||||
}
|
||||
|
||||
fun Card.getTwinCardIdForUser(): String {
|
||||
return TwinsHelper.getTwinCardIdForUser(this.cardId)
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.CardSessionRunnable
|
||||
import com.tangem.KeyPair
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.files.FileHashHelper
|
||||
|
||||
class WriteProtectedIssuerDataTask(
|
||||
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair
|
||||
) : CardSessionRunnable<WriteIssuerDataResponse> {
|
||||
override val requiresPin2 = true
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit) {
|
||||
val hashes = arrayOf(twinPublicKey.calculateSha256())
|
||||
SignCommand(hashes).run(session) { signResult ->
|
||||
when (signResult) {
|
||||
is CompletionResult.Success -> {
|
||||
ReadIssuerDataCommand().run(session) { readResult ->
|
||||
when (readResult) {
|
||||
is CompletionResult.Success -> {
|
||||
writeIssuerData(
|
||||
twinPublicKey, issuerKeys, signResult.data.signature,
|
||||
readResult.data, session, callback
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(signResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeIssuerData(
|
||||
twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray,
|
||||
readResponse: ReadIssuerDataResponse,
|
||||
session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>
|
||||
) -> Unit) {
|
||||
val cardId = session.environment.card?.cardId.guard {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
return
|
||||
}
|
||||
val counter = (readResponse.issuerDataCounter ?: 0) + 1
|
||||
val data = twinPublicKey + cardSignature
|
||||
val signedByIssuer = FileHashHelper.prepareHashes(
|
||||
cardId, data, counter, issuerKeys.privateKey
|
||||
)
|
||||
WriteIssuerDataCommand(
|
||||
data, signedByIssuer.finalizingSignature!!,
|
||||
counter, issuerKeys.publicKey
|
||||
).run(session, callback)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.commands.Card
|
||||
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.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.network.coinmarketcap.FiatCurrency
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -12,10 +16,12 @@ sealed class DetailsAction : Action {
|
|||
|
||||
data class PrepareScreen(
|
||||
val card: Card,
|
||||
val scanNoteResponse: ScanNoteResponse,
|
||||
val wallet: Wallet?,
|
||||
val isCreatingTwinWalletAllowed: Boolean?,
|
||||
val fiatCurrencyName: FiatCurrencyName,
|
||||
val fiatCurrencies: List<FiatCurrencyName>? = null,
|
||||
): DetailsAction()
|
||||
) : DetailsAction()
|
||||
|
||||
object ShowDisclaimer : DetailsAction()
|
||||
|
||||
|
|
@ -23,24 +29,62 @@ sealed class DetailsAction : Action {
|
|||
sealed class EraseWallet : DetailsAction() {
|
||||
object Check : EraseWallet()
|
||||
object Proceed : EraseWallet() {
|
||||
object NotAllowedByCard: EraseWallet(), NotificationAction {
|
||||
object NotAllowedByCard : EraseWallet(), NotificationAction {
|
||||
override val messageResource = R.string.error_purge_prohibited
|
||||
}
|
||||
object NotEmpty: EraseWallet(), NotificationAction {
|
||||
|
||||
object NotEmpty : EraseWallet(), NotificationAction {
|
||||
override val messageResource = R.string.details_notification_erase_wallet_not_possible
|
||||
}
|
||||
}
|
||||
|
||||
object Confirm : EraseWallet()
|
||||
object Cancel : EraseWallet()
|
||||
object Failure : EraseWallet()
|
||||
object Success : EraseWallet()
|
||||
}
|
||||
|
||||
sealed class CreateTwinWalletAction : DetailsAction() {
|
||||
data class ShowWarning(
|
||||
val twinCardNumber: TwinCardNumber?,
|
||||
val createTwinWallet: CreateTwinWallet = CreateTwinWallet.RecreateWallet
|
||||
) : CreateTwinWalletAction()
|
||||
object NotEmpty : CreateTwinWalletAction(), NotificationAction {
|
||||
override val messageResource = R.string.details_notification_erase_wallet_not_possible
|
||||
}
|
||||
object ShowAlert : CreateTwinWalletAction()
|
||||
object HideAlert : CreateTwinWalletAction()
|
||||
object Proceed: CreateTwinWalletAction()
|
||||
|
||||
object Cancel : CreateTwinWalletAction() {
|
||||
object Confirm : CreateTwinWalletAction()
|
||||
}
|
||||
|
||||
data class LaunchFirstStep(val message: Message) : CreateTwinWalletAction() {
|
||||
object Success : CreateTwinWalletAction()
|
||||
object Failure : CreateTwinWalletAction()
|
||||
}
|
||||
|
||||
data class LaunchSecondStep(
|
||||
val initialMessage: Message,
|
||||
val preparingMessage: Message,
|
||||
val creatingWalletMessage: Message,
|
||||
) : CreateTwinWalletAction() {
|
||||
object Success : CreateTwinWalletAction()
|
||||
object Failure : CreateTwinWalletAction()
|
||||
}
|
||||
|
||||
data class LaunchThirdStep(val message: Message) : CreateTwinWalletAction() {
|
||||
data class Success(val scanNoteResponse: ScanNoteResponse) : CreateTwinWalletAction()
|
||||
object Failure : CreateTwinWalletAction()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class AppCurrencyAction : DetailsAction() {
|
||||
data class SetCurrencies(val currencies: List<FiatCurrency>) : AppCurrencyAction()
|
||||
object ChooseAppCurrency : AppCurrencyAction()
|
||||
object Cancel: AppCurrencyAction()
|
||||
data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName): AppCurrencyAction()
|
||||
object Cancel : AppCurrencyAction()
|
||||
data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName) : AppCurrencyAction()
|
||||
}
|
||||
|
||||
sealed class ManageSecurity : DetailsAction() {
|
||||
|
|
@ -50,8 +94,9 @@ sealed class DetailsAction : Action {
|
|||
object Success : ManageSecurity()
|
||||
object Failure : ManageSecurity()
|
||||
}
|
||||
|
||||
data class ConfirmSelection(val option: SecurityOption) : ManageSecurity() {
|
||||
object AlreadySet: ManageSecurity(), NotificationAction {
|
||||
object AlreadySet : ManageSecurity(), NotificationAction {
|
||||
override val messageResource = R.string.details_notification_security_option_already_active
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWalletMiddleware
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
|
|
@ -22,6 +23,7 @@ class DetailsMiddleware {
|
|||
private val eraseWalletMiddleware = EraseWalletMiddleware()
|
||||
private val appCurrencyMiddleware = AppCurrencyMiddleware()
|
||||
private val manageSecurityMiddleware = ManageSecurityMiddleware()
|
||||
private val twinWalletMiddleware = CreateTwinWalletMiddleware()
|
||||
val detailsMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
|
|
@ -30,7 +32,8 @@ class DetailsMiddleware {
|
|||
is DetailsAction.EraseWallet -> eraseWalletMiddleware.handle(action)
|
||||
is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action)
|
||||
is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action)
|
||||
is DetailsAction.ShowDisclaimer -> {
|
||||
is DetailsAction.CreateTwinWalletAction -> twinWalletMiddleware.handle(action)
|
||||
is DetailsAction.ShowDisclaimer -> {
|
||||
store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.Settings
|
||||
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.masks.Settings
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TapWorkarounds
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWalletReducer
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState
|
||||
import org.rekotlin.Action
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -31,6 +36,9 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
is DetailsAction.ManageSecurity -> {
|
||||
handleSecurityAction(action, detailsState)
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction -> {
|
||||
CreateTwinWalletReducer.handle(action, detailsState)
|
||||
}
|
||||
else -> detailsState
|
||||
}
|
||||
}
|
||||
|
|
@ -47,13 +55,25 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta
|
|||
SecurityOption.LongTap
|
||||
}
|
||||
}
|
||||
val twinsState = if (action.card.isTwinCard()) {
|
||||
CreateTwinWalletState(
|
||||
scanResponse = action.scanNoteResponse,
|
||||
twinCardNumber = TwinsHelper.getTwinCardNumber(action.card.cardId),
|
||||
createTwinWallet = null,
|
||||
showAlert = false,
|
||||
allowRecreatingWallet = action.isCreatingTwinWalletAllowed
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return DetailsState(
|
||||
card = action.card, wallet = action.wallet,
|
||||
cardInfo = action.card.toCardInfo(),
|
||||
appCurrencyState = AppCurrencyState(
|
||||
action.fiatCurrencyName
|
||||
),
|
||||
securityScreenState = SecurityScreenState(currentOption = securityOption)
|
||||
securityScreenState = SecurityScreenState(currentOption = securityOption),
|
||||
createTwinWalletState = twinsState
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +189,7 @@ private fun handleSecurityAction(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun Card.toCardInfo(): CardInfo? {
|
||||
val cardId = this.cardId.chunked(4).joinToString(separator = " ")
|
||||
val issuer = this.cardData?.issuerName ?: return null
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState
|
||||
import com.tangem.tap.network.coinmarketcap.FiatCurrency
|
||||
import org.rekotlin.StateType
|
||||
import java.util.*
|
||||
|
|
@ -17,6 +18,7 @@ data class DetailsState(
|
|||
val eraseWalletState: EraseWalletState? = null,
|
||||
val confirmScreenState: ConfirmScreenState? = null,
|
||||
val securityScreenState: SecurityScreenState? = null,
|
||||
val createTwinWalletState: CreateTwinWalletState? = null
|
||||
) : StateType
|
||||
|
||||
data class CardInfo(
|
||||
|
|
@ -39,4 +41,4 @@ data class AppCurrencyState(
|
|||
val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
|
||||
val showAppCurrencyDialog: Boolean = false,
|
||||
val fiatCurrencies: List<FiatCurrency>? = null,
|
||||
)
|
||||
)
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package com.tangem.tap.features.details.redux.twins
|
||||
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class CreateTwinWalletMiddleware {
|
||||
var twinsManager: TwinCardsManager? = null
|
||||
fun handle(action: DetailsAction.CreateTwinWalletAction) {
|
||||
when (action) {
|
||||
is DetailsAction.CreateTwinWalletAction.ShowWarning -> {
|
||||
val wallet = store.state.detailsState.wallet
|
||||
if (wallet == null) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning))
|
||||
return
|
||||
}
|
||||
val notEmpty = wallet.recentTransactions.isNotEmpty() || wallet.amounts.toSendableAmounts().isNotEmpty()
|
||||
if (notEmpty) {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.NotEmpty)
|
||||
} else {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning))
|
||||
}
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction.Proceed ->
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWallet))
|
||||
is DetailsAction.CreateTwinWalletAction.Cancel -> {
|
||||
|
||||
val step = store.state.detailsState.createTwinWalletState?.step
|
||||
if (step != null && step != CreateTwinWalletStep.FirstStep
|
||||
) {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.ShowAlert)
|
||||
} else {
|
||||
twinsManager = null
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction.Cancel.Confirm -> {
|
||||
twinsManager = null
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> {
|
||||
store.state.globalState.scanNoteResponse?.let {
|
||||
twinsManager = TwinCardsManager(it)
|
||||
}
|
||||
scope.launch {
|
||||
val result = twinsManager?.createFirstWallet(action.message)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
SimpleResult.Success ->
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Success)
|
||||
is SimpleResult.Failure ->
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Success -> {
|
||||
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Failure -> {
|
||||
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction.LaunchSecondStep ->
|
||||
scope.launch {
|
||||
val result = twinsManager?.createSecondWallet(action.initialMessage,
|
||||
action.preparingMessage, action.creatingWalletMessage)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
SimpleResult.Success ->
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Success)
|
||||
is SimpleResult.Failure ->
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Success -> {
|
||||
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Failure -> {
|
||||
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction.LaunchThirdStep -> {
|
||||
scope.launch {
|
||||
val result = twinsManager?.complete(action.message)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success ->
|
||||
store.dispatch(
|
||||
DetailsAction.CreateTwinWalletAction
|
||||
.LaunchThirdStep.Success(result.data)
|
||||
)
|
||||
is Result.Failure ->
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Success -> {
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.onCardScanned(action.scanNoteResponse)
|
||||
}
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.tap.features.details.redux.twins
|
||||
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
|
||||
class CreateTwinWalletReducer {
|
||||
companion object {
|
||||
fun handle(
|
||||
action: DetailsAction.CreateTwinWalletAction, state: DetailsState
|
||||
): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.CreateTwinWalletAction.ShowWarning -> {
|
||||
state.copy(createTwinWalletState = CreateTwinWalletState(
|
||||
scanResponse = null,
|
||||
twinCardNumber = action.twinCardNumber
|
||||
?: state.createTwinWalletState?.twinCardNumber,
|
||||
createTwinWallet = action.createTwinWallet,
|
||||
showAlert = false,
|
||||
allowRecreatingWallet = state.createTwinWalletState?.allowRecreatingWallet
|
||||
))
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.NotEmpty -> state
|
||||
DetailsAction.CreateTwinWalletAction.ShowAlert -> {
|
||||
state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
|
||||
showAlert = true
|
||||
))
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.HideAlert -> {
|
||||
state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
|
||||
showAlert = false
|
||||
))
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction.Proceed -> {
|
||||
state
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.Cancel -> state
|
||||
DetailsAction.CreateTwinWalletAction.Cancel.Confirm -> state
|
||||
is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> state
|
||||
|
||||
DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Success -> {
|
||||
state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
|
||||
step = CreateTwinWalletStep.SecondStep
|
||||
))
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Failure -> state
|
||||
|
||||
is DetailsAction.CreateTwinWalletAction.LaunchSecondStep -> state
|
||||
DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Success ->
|
||||
state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
|
||||
step = CreateTwinWalletStep.ThirdStep
|
||||
))
|
||||
DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Failure -> {
|
||||
state
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction.LaunchThirdStep -> {
|
||||
state
|
||||
}
|
||||
is DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Success -> {
|
||||
state
|
||||
}
|
||||
DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure -> {
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.tap.features.details.redux.twins
|
||||
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
|
||||
data class CreateTwinWalletState(
|
||||
val scanResponse: ScanNoteResponse?,
|
||||
val step: CreateTwinWalletStep = CreateTwinWalletStep.FirstStep,
|
||||
val twinCardNumber: TwinCardNumber?,
|
||||
val createTwinWallet: CreateTwinWallet?,
|
||||
val showAlert: Boolean,
|
||||
val allowRecreatingWallet: Boolean? = null
|
||||
)
|
||||
|
||||
enum class CreateTwinWalletStep { FirstStep, SecondStep, ThirdStep }
|
||||
|
||||
enum class CreateTwinWallet { CreateWallet, RecreateWallet }
|
||||
|
|
@ -5,10 +5,15 @@ import android.view.View
|
|||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.twins.getTwinCardIdForUser
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_details.*
|
||||
|
|
@ -59,18 +64,44 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
|
||||
|
||||
if (state.cardInfo != null) {
|
||||
tv_card_id.text = state.cardInfo.cardId
|
||||
val cardId = if (state.card?.isTwinCard() == true) {
|
||||
state.card.getTwinCardIdForUser()
|
||||
} else {
|
||||
state.cardInfo.cardId
|
||||
}
|
||||
tv_card_id.text = cardId
|
||||
tv_issuer.text = state.cardInfo.issuer
|
||||
tv_signed_hashes.text = state.cardInfo.signedHashes.toString()
|
||||
}
|
||||
|
||||
tv_signed_hashes.show(state.card?.isTwinCard() != true)
|
||||
tv_signed_hashes_title.show(state.card?.isTwinCard() != true)
|
||||
|
||||
|
||||
tv_disclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
|
||||
|
||||
tv_erase_wallet.setOnClickListener {
|
||||
store.dispatch(DetailsAction.EraseWallet.Check)
|
||||
store.dispatch(DetailsAction.EraseWallet.Proceed)
|
||||
if (state.createTwinWalletState != null) {
|
||||
if (state.createTwinWalletState.allowRecreatingWallet == true) {
|
||||
tv_erase_wallet.show()
|
||||
tv_erase_wallet.text = getText(R.string.details_row_title_twins_recreate)
|
||||
tv_erase_wallet.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning(
|
||||
null, CreateTwinWallet.RecreateWallet
|
||||
))
|
||||
}
|
||||
} else {
|
||||
tv_erase_wallet.hide()
|
||||
}
|
||||
} else {
|
||||
tv_erase_wallet.show()
|
||||
tv_erase_wallet.text = getText(R.string.details_row_title_erase_wallet)
|
||||
tv_erase_wallet.setOnClickListener {
|
||||
store.dispatch(DetailsAction.EraseWallet.Check)
|
||||
store.dispatch(DetailsAction.EraseWallet.Proceed)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tv_app_currency.text = state.appCurrencyState.fiatCurrencyName
|
||||
|
||||
tv_app_currency_title.setOnClickListener {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,171 @@
|
|||
package com.tangem.tap.features.details.ui.twins
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.content.ContextCompat.getColor
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.Message
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWalletStep
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_details_twin_cards.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
class CreateTwinWalletFragment : Fragment(R.layout.fragment_details_twin_cards),
|
||||
StoreSubscriber<DetailsState> {
|
||||
|
||||
private var dialog: Dialog? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel)
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
toolbar.setNavigationOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel)
|
||||
}
|
||||
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_1)
|
||||
.placeholder(R.drawable.card_placeholder)
|
||||
?.error(R.drawable.card_placeholder)
|
||||
?.into(iv_card_1)
|
||||
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_2)
|
||||
.placeholder(R.drawable.card_placeholder)
|
||||
?.error(R.drawable.card_placeholder)
|
||||
?.into(iv_card_2)
|
||||
}
|
||||
|
||||
|
||||
override fun newState(state: DetailsState) {
|
||||
if (activity == null) return
|
||||
|
||||
toolbar.title = when (state.createTwinWalletState?.createTwinWallet) {
|
||||
CreateTwinWallet.CreateWallet -> getText(R.string.wallet_button_create_wallet)
|
||||
CreateTwinWallet.RecreateWallet, null -> getText(R.string.details_twins_recreate_toolbar)
|
||||
|
||||
}
|
||||
|
||||
val selectedColor = getColor(requireContext(), R.color.colorSecondary)
|
||||
val defaultColor = getColor(requireContext(), R.color.blue_pale)
|
||||
|
||||
val twinCardNumber = state.createTwinWalletState?.twinCardNumber ?: TwinCardNumber.First
|
||||
|
||||
val cardNumber = when (state.createTwinWalletState?.step) {
|
||||
CreateTwinWalletStep.FirstStep -> {
|
||||
val twinCardNumberString = twinCardNumber.number.toString()
|
||||
tv_step_number.text =
|
||||
getString(R.string.details_twins_recreate_step_format, "1")
|
||||
v_step_1.setBackgroundColor(selectedColor)
|
||||
v_step_2.setBackgroundColor(defaultColor)
|
||||
v_step_3.setBackgroundColor(defaultColor)
|
||||
btn_tap.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep(
|
||||
Message(getString(
|
||||
R.string.details_twins_recreate_title_format, twinCardNumber)
|
||||
)))
|
||||
}
|
||||
btn_tap.text = getString(R.string.details_twins_recreate_button_format,
|
||||
twinCardNumber.number.toString())
|
||||
twinCardNumberString
|
||||
}
|
||||
CreateTwinWalletStep.SecondStep -> {
|
||||
val twinCardNumberString = twinCardNumber.pairNumber().number.toString()
|
||||
|
||||
tv_step_number.text =
|
||||
getString(R.string.details_twins_recreate_step_format, "2")
|
||||
v_step_1.setBackgroundColor(selectedColor)
|
||||
v_step_2.setBackgroundColor(selectedColor)
|
||||
v_step_3.setBackgroundColor(defaultColor)
|
||||
btn_tap.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep(
|
||||
Message(getString(R.string.details_twins_recreate_title_format, twinCardNumberString)),
|
||||
Message(getString(R.string.details_twins_recreate_title_preparing)),
|
||||
Message(getString(R.string.details_twins_recreate_title_creating_wallet)),
|
||||
))
|
||||
}
|
||||
btn_tap.text = getString(R.string.details_twins_recreate_button_format, "2")
|
||||
twinCardNumberString
|
||||
}
|
||||
CreateTwinWalletStep.ThirdStep -> {
|
||||
val twinCardNumberString = twinCardNumber.number.toString()
|
||||
|
||||
tv_step_number.text =
|
||||
getString(R.string.details_twins_recreate_step_format, "3")
|
||||
v_step_1.setBackgroundColor(selectedColor)
|
||||
v_step_2.setBackgroundColor(selectedColor)
|
||||
v_step_3.setBackgroundColor(selectedColor)
|
||||
btn_tap.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchThirdStep(
|
||||
Message(getString(
|
||||
R.string.details_twins_recreate_title_format, twinCardNumberString)
|
||||
)
|
||||
))
|
||||
}
|
||||
btn_tap.text = getString(R.string.details_twins_recreate_button_format, twinCardNumberString)
|
||||
twinCardNumberString
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
btn_tap.text = getString(R.string.details_twins_recreate_button_format, cardNumber)
|
||||
tv_twin_title.text = getString(R.string.details_twins_recreate_title_format, cardNumber)
|
||||
|
||||
if (state.createTwinWalletState?.showAlert == true) {
|
||||
if (dialog == null) {
|
||||
dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(R.string.details_twins_recreate_alert)
|
||||
.setPositiveButton(R.string.common_ok) { _, _ ->
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel.Confirm)
|
||||
}
|
||||
.setNegativeButton(R.string.common_cancel) { _, _ ->
|
||||
dialog?.cancel()
|
||||
}
|
||||
.setOnCancelListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.HideAlert)
|
||||
}
|
||||
.create()
|
||||
dialog?.show()
|
||||
}
|
||||
} else {
|
||||
dialog?.cancel()
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.tap.features.details.ui.twins
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_details_twin_cards_warning.*
|
||||
import kotlinx.android.synthetic.main.layout_twin_cards_orange.*
|
||||
|
||||
class TwinWalletWarningFragment : Fragment(R.layout.fragment_details_twin_cards_warning) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val createTwinWallet = store.state.detailsState.createTwinWalletState?.createTwinWallet
|
||||
if (createTwinWallet == CreateTwinWallet.CreateWallet) {
|
||||
tv_twin_cards_description.text = getText(R.string.details_twins_recreate_subtitle)
|
||||
} else if (createTwinWallet == CreateTwinWallet.RecreateWallet) {
|
||||
tv_twin_cards_description.text = getText(R.string.details_twins_recreate_warning)
|
||||
}
|
||||
|
||||
btn_cancel.setOnClickListener { store.dispatch(NavigationAction.PopBackTo()) }
|
||||
btn_start.setOnClickListener {
|
||||
store.dispatch(
|
||||
DetailsAction.CreateTwinWalletAction.Proceed)
|
||||
}
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_1)
|
||||
.placeholder(R.drawable.card_placeholder)
|
||||
?.error(R.drawable.card_placeholder)
|
||||
?.into(iv_twin_card_1)
|
||||
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_2)
|
||||
.placeholder(R.drawable.card_placeholder)
|
||||
?.error(R.drawable.card_placeholder)
|
||||
?.into(iv_twin_card_2)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -14,7 +14,16 @@ class DisclaimerMiddleware {
|
|||
when (action) {
|
||||
is DisclaimerAction.AcceptDisclaimer -> {
|
||||
preferencesStorage.saveDisclaimerAccepted()
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
if (store.state.walletState.twinCardsState != null) {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
|
||||
} else {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
} else {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class HomeMiddleware {
|
|||
when (action) {
|
||||
is HomeAction.CheckIfFirstLaunch -> {
|
||||
store.dispatch(
|
||||
HomeAction.CheckIfFirstLaunch.Result(preferencesStorage.isFirstLaunch())
|
||||
HomeAction.CheckIfFirstLaunch.Result(preferencesStorage.getCountOfLaunches() == 1)
|
||||
)
|
||||
}
|
||||
is HomeAction.ReadCard -> {
|
||||
|
|
@ -34,6 +34,7 @@ class HomeMiddleware {
|
|||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
store.state.globalState.tapWalletManager.onCardScanned(result.data)
|
||||
showDisclaimerOrNavigateToWallet()
|
||||
|
|
@ -54,11 +55,18 @@ class HomeMiddleware {
|
|||
}
|
||||
|
||||
private fun showDisclaimerOrNavigateToWallet() {
|
||||
if (preferencesStorage.wasDisclaimerAccepted()) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
} else {
|
||||
if (!preferencesStorage.wasDisclaimerAccepted()) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
return
|
||||
}
|
||||
if (store.state.walletState.twinCardsState != null) {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
|
||||
return
|
||||
}
|
||||
}
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ sealed class AddressPayIdActionUi : SendScreenActionUi {
|
|||
object CheckAddressPayId : AddressPayIdActionUi()
|
||||
data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi()
|
||||
data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi()
|
||||
data class ChangePayIdState(val sendingToPayIdEnabled: Boolean): AddressPayIdActionUi()
|
||||
}
|
||||
|
||||
sealed class AddressPayIdVerifyAction : SendScreenAction {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.tangem.blockchain.common.Wallet
|
|||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapWorkarounds
|
||||
import com.tangem.tap.domain.isPayIdSupported
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
|
||||
|
|
@ -15,6 +14,7 @@ import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerifica
|
|||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress
|
||||
import com.tangem.tap.features.send.redux.FeeAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -49,7 +49,7 @@ internal class AddressPayIdMiddleware {
|
|||
|
||||
private fun setAddressAndCheck(data: String, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
||||
val potentialPayId = data.toLowerCase()
|
||||
if (PayIdManager.isPayId(potentialPayId) && TapWorkarounds.isPayIdEnabled()) {
|
||||
if (PayIdManager.isPayId(potentialPayId) && isPayIdEnabled()) {
|
||||
dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput))
|
||||
} else {
|
||||
dispatch(SetWalletAddress(data, isUserInput))
|
||||
|
|
@ -63,7 +63,7 @@ internal class AddressPayIdMiddleware {
|
|||
val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return
|
||||
val isUserInput = sendState.addressPayIdState.viewFieldValue.isFromUserInput
|
||||
|
||||
if (PayIdManager.isPayId(addressPayId) && TapWorkarounds.isPayIdEnabled()) {
|
||||
if (PayIdManager.isPayId(addressPayId) && isPayIdEnabled()) {
|
||||
verifyPayId(addressPayId, wallet, isUserInput, dispatch)
|
||||
} else {
|
||||
verifyAddress(addressPayId, wallet, isUserInput, dispatch)
|
||||
|
|
@ -117,7 +117,7 @@ internal class AddressPayIdMiddleware {
|
|||
|
||||
private fun isValidBlockchainAddressAndNotTheSameAsWallet(wallet: Wallet, address: String): Error? {
|
||||
return if (wallet.blockchain.validateAddress(address)) {
|
||||
if (wallet.address != address) {
|
||||
if (wallet.addresses.all { it.value != address } ) {
|
||||
null
|
||||
} else {
|
||||
Error.ADDRESS_SAME_AS_WALLET
|
||||
|
|
@ -152,10 +152,14 @@ internal class AddressPayIdMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
if (PayIdManager.isPayId(addressPayId) && TapWorkarounds.isPayIdEnabled()) {
|
||||
if (PayIdManager.isPayId(addressPayId) && isPayIdEnabled()) {
|
||||
verifyPayId(addressPayId, wallet, false, internalDispatcher)
|
||||
} else {
|
||||
verifyAddress(addressPayId, wallet, false, internalDispatcher)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isPayIdEnabled(): Boolean {
|
||||
return store.state.globalState.configManager?.config?.isSendingToPayIdEnabled ?: false
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.Signer
|
||||
import com.tangem.commands.Card
|
||||
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.stripZeroPlainString
|
||||
|
|
@ -39,6 +39,7 @@ val sendMiddleware: Middleware<AppState> = { dispatch, appState ->
|
|||
is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch)
|
||||
is SendActionUi.SendAmountToRecipient ->
|
||||
verifyAndSendTransaction(action, appState(), dispatch)
|
||||
is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch)
|
||||
}
|
||||
nextDispatch(action)
|
||||
}
|
||||
|
|
@ -185,3 +186,9 @@ fun createValidateTransactionError(errorList: EnumSet<TransactionError>, walletM
|
|||
return TapError.ValidateTransactionErrors(tapErrors) { it.joinToString("\r\n") }
|
||||
}
|
||||
|
||||
private fun setIfSendingToPayIdEnabled(appState: AppState?, dispatch: (Action) -> Unit) {
|
||||
val isSendingToPayIdEnabled =
|
||||
appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false
|
||||
dispatch(AddressPayIdActionUi.ChangePayIdState(isSendingToPayIdEnabled))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ class AddressPayIdReducer : SendInternalReducer {
|
|||
is AddressPayIdActionUi.PasteAddressPayId -> return sendState
|
||||
is AddressPayIdActionUi.CheckClipboard -> return sendState
|
||||
is AddressPayIdActionUi.CheckAddressPayId -> return sendState
|
||||
is AddressPayIdActionUi.ChangePayIdState -> state.copy(sendingToPayIdEnabled = action.sendingToPayIdEnabled)
|
||||
}
|
||||
return updateLastState(sendState.copy(addressPayIdState = result), result)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType
|
|||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.tap.common.extensions.scaleToFiat
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.states.*
|
||||
|
|
@ -179,7 +180,7 @@ class ReceiptReducer : SendInternalReducer {
|
|||
return ReceiptSymbols(
|
||||
fiat = store.state.globalState.appCurrency,
|
||||
crypto = wallet.blockchain.currency,
|
||||
token = wallet.amounts[AmountType.Token]?.currencySymbol
|
||||
token = wallet.getFirstToken()?.symbol
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -187,12 +188,12 @@ class ReceiptReducer : SendInternalReducer {
|
|||
return when (mainCurrencyType) {
|
||||
MainCurrencyType.FIAT -> when (amountType) {
|
||||
AmountType.Coin -> ReceiptLayoutType.FIAT
|
||||
AmountType.Token -> ReceiptLayoutType.TOKEN_FIAT
|
||||
is AmountType.Token -> ReceiptLayoutType.TOKEN_FIAT
|
||||
AmountType.Reserve -> ReceiptLayoutType.UNKNOWN
|
||||
}
|
||||
MainCurrencyType.CRYPTO -> when (amountType) {
|
||||
AmountType.Coin -> ReceiptLayoutType.CRYPTO
|
||||
AmountType.Token -> ReceiptLayoutType.TOKEN_CRYPTO
|
||||
is AmountType.Token -> ReceiptLayoutType.TOKEN_CRYPTO
|
||||
AmountType.Reserve -> ReceiptLayoutType.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ data class AddressPayIdState(
|
|||
val recipientWalletAddress: String? = null,
|
||||
val error: AddressPayIdVerifyAction.Error? = null,
|
||||
val truncateHandler: ((String) -> String)? = null,
|
||||
val sendingToPayIdEnabled: Boolean = false,
|
||||
val pasteIsEnabled: Boolean = false
|
||||
) : SendScreenState {
|
||||
|
||||
|
|
|
|||
|
|
@ -78,14 +78,14 @@ data class SendState(
|
|||
|
||||
fun convertFiatToExtractCrypto(fiatValue: BigDecimal): BigDecimal = when (amountState.typeOfAmount) {
|
||||
AmountType.Coin -> convertFiatToCoin(fiatValue)
|
||||
AmountType.Token -> convertFiatToToken(fiatValue)
|
||||
is AmountType.Token -> convertFiatToToken(fiatValue)
|
||||
AmountType.Reserve -> fiatValue
|
||||
}
|
||||
|
||||
fun convertExtractCryptoToFiat(cryptoValue: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
|
||||
return when (amountState.typeOfAmount) {
|
||||
AmountType.Coin -> convertCoinToFiat(cryptoValue, scaleWithPrecision)
|
||||
AmountType.Token -> convertTokenToFiat(cryptoValue, scaleWithPrecision)
|
||||
is AmountType.Token -> convertTokenToFiat(cryptoValue, scaleWithPrecision)
|
||||
AmountType.Reserve -> cryptoValue
|
||||
}
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ data class SendState(
|
|||
fun mainCurrencyCanBeSwitched(): Boolean {
|
||||
return when (amountState.typeOfAmount) {
|
||||
AmountType.Coin -> coinIsConvertible()
|
||||
AmountType.Token -> tokenIsConvertible()
|
||||
is AmountType.Token -> tokenIsConvertible()
|
||||
AmountType.Reserve -> false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ class FeeUiHelper {
|
|||
companion object {
|
||||
fun feeToId(fee: FeeType): Int {
|
||||
return when (fee) {
|
||||
FeeType.SINGLE -> 0
|
||||
FeeType.SINGLE -> View.NO_ID
|
||||
FeeType.LOW -> R.id.chipLow
|
||||
FeeType.NORMAL -> R.id.chipNormal
|
||||
FeeType.PRIORITY -> R.id.chipPriority
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.send.ui.stateSubscribers
|
|||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.bold
|
||||
import com.tangem.tap.common.extensions.*
|
||||
|
|
@ -10,7 +11,6 @@ import com.tangem.tap.common.redux.getMessageString
|
|||
import com.tangem.tap.common.text.DecimalDigitsInputFilter
|
||||
import com.tangem.tap.common.toggleWidget.ProgressState
|
||||
import com.tangem.tap.domain.MultiMessageError
|
||||
import com.tangem.tap.domain.TapWorkarounds
|
||||
import com.tangem.tap.domain.assembleErrors
|
||||
import com.tangem.tap.features.send.BaseStoreFragment
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
|
||||
|
|
@ -104,7 +104,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
val til = fg.tilAddressOrPayId
|
||||
val parsedError = parseError(til.context, state.error)
|
||||
|
||||
val hintResId = if (TapWorkarounds.isPayIdEnabled()) {
|
||||
val hintResId = if (state.sendingToPayIdEnabled) {
|
||||
R.string.send_destination_hint_address_payid
|
||||
}else {
|
||||
R.string.send_destination_hint_address
|
||||
|
|
@ -201,7 +201,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
}
|
||||
|
||||
val chipId = FeeUiHelper.feeToId(state.selectedFeeType)
|
||||
if (fg.chipGroup.checkedChipId != chipId && chipId != 0) fg.chipGroup.check(chipId)
|
||||
if (fg.chipGroup.checkedChipId != chipId && chipId != View.NO_ID) fg.chipGroup.check(chipId)
|
||||
}
|
||||
|
||||
private fun handleReceiptState(fg: BaseStoreFragment, state: ReceiptState) {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ package com.tangem.tap.features.wallet.redux
|
|||
import android.content.Context
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -91,4 +93,15 @@ sealed class WalletAction : Action {
|
|||
sealed class TopUpAction : WalletAction() {
|
||||
data class TopUp(val context: Context, val toolbarColor: Int) : TopUpAction()
|
||||
}
|
||||
|
||||
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
|
||||
|
||||
sealed class TwinsAction : WalletAction() {
|
||||
object ShowOnboarding : TwinsAction()
|
||||
object SetOnboardingShown : TwinsAction()
|
||||
data class SetTwinCard(
|
||||
val secondCardId: String, val number: TwinCardNumber,
|
||||
val isCreatingTwinCardsAllowed: Boolean
|
||||
) : TwinsAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,10 @@ import androidx.browser.customtabs.CustomTabsIntent
|
|||
import androidx.core.content.ContextCompat
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.CardType
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.CardType
|
||||
import com.tangem.common.extensions.getType
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
|
|
@ -21,6 +21,10 @@ import com.tangem.tap.domain.PayIdManager
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.TopUpHelper
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
|
|
@ -60,15 +64,25 @@ class WalletMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createWallet(
|
||||
store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.state.globalState.tapWalletManager.onCardScanned(result.data)
|
||||
}
|
||||
if (store.state.walletState.twinCardsState != null) {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning(
|
||||
store.state.globalState.scanNoteResponse?.card?.cardId?.let {
|
||||
TwinsHelper.getTwinCardNumber(it)
|
||||
},
|
||||
CreateTwinWallet.CreateWallet
|
||||
))
|
||||
} else {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createWallet(
|
||||
store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.state.globalState.tapWalletManager
|
||||
.onCardScanned(result.data)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,8 +125,15 @@ class WalletMiddleware {
|
|||
val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
|
||||
store.state.globalState.tapWalletManager
|
||||
.onCardScanned(result.data, true)
|
||||
if (store.state.walletState.twinCardsState != null) {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -133,13 +154,13 @@ class WalletMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletAction.CopyAddress -> {
|
||||
store.state.walletState.addressData?.address?.let {
|
||||
store.state.walletState.walletAddresses?.selectedAddress?.address?.let {
|
||||
action.context.copyToClipboard(it)
|
||||
store.dispatch(WalletAction.CopyAddress.Success)
|
||||
}
|
||||
}
|
||||
is WalletAction.ExploreAddress -> {
|
||||
val uri = Uri.parse(store.state.walletState.addressData?.exploreUrl)
|
||||
val uri = Uri.parse(store.state.walletState.walletAddresses?.selectedAddress?.exploreUrl)
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
ContextCompat.startActivity(action.context, intent, null)
|
||||
}
|
||||
|
|
@ -164,6 +185,13 @@ class WalletMiddleware {
|
|||
val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
cardId?.let { preferencesStorage.saveScannedCardId(it) }
|
||||
}
|
||||
is WalletAction.TwinsAction.SetTwinCard -> {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding)
|
||||
}
|
||||
is WalletAction.TwinsAction.SetOnboardingShown -> {
|
||||
preferencesStorage.saveTwinsOnboardingShown()
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
|
|
@ -185,7 +213,7 @@ class WalletMiddleware {
|
|||
|
||||
private fun prepareSendAction(amount: Amount?): Action {
|
||||
return if (amount != null) {
|
||||
if (amount.type == AmountType.Token) {
|
||||
if (amount.type is AmountType.Token) {
|
||||
PrepareSendScreen(store.state.walletState.wallet?.amounts?.get(AmountType.Coin), amount)
|
||||
} else {
|
||||
PrepareSendScreen(amount)
|
||||
|
|
@ -208,6 +236,7 @@ class WalletMiddleware {
|
|||
if (card.getType() != CardType.Release) {
|
||||
return WarningType.DevCard
|
||||
}
|
||||
if (card.isTwinCard()) return null
|
||||
|
||||
return if (signatureCountValidator == null) {
|
||||
if (card.walletSignedHashes ?: 0 > 0) {
|
||||
|
|
@ -229,6 +258,8 @@ class WalletMiddleware {
|
|||
val card = store.state.globalState.scanNoteResponse?.card
|
||||
if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return
|
||||
|
||||
if (card.isTwinCard()) return
|
||||
|
||||
val validator = store.state.globalState.scanNoteResponse?.walletManager
|
||||
as? SignatureCountValidator
|
||||
scope.launch {
|
||||
|
|
@ -255,9 +286,13 @@ private class TopUpMiddleware {
|
|||
fun handle(action: WalletAction.TopUpAction) {
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction.TopUp -> {
|
||||
val config = store.state.globalState.configManager?.config ?: return
|
||||
val defaultAddress = store.state.walletState.walletAddresses!!.list[0].address
|
||||
val url = TopUpHelper.getUrl(
|
||||
store.state.walletState.currencyData.currencySymbol!!,
|
||||
store.state.walletState.addressData!!.address
|
||||
defaultAddress,
|
||||
config.moonPayApiKey,
|
||||
config.moonPayApiSecretKey
|
||||
)
|
||||
val customTabsIntent = CustomTabsIntent.Builder()
|
||||
.setToolbarColor(action.toolbarColor)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import com.tangem.tap.common.extensions.toFormattedString
|
|||
import com.tangem.tap.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
|
|
@ -33,29 +35,29 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
|
||||
when (action) {
|
||||
is WalletAction.ResetState -> newState = WalletState()
|
||||
is WalletAction.EmptyWallet -> newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
|
||||
mainButton = WalletMainButton.CreateWalletButton(true),
|
||||
topUpState = TopUpState(false)
|
||||
)
|
||||
is WalletAction.EmptyWallet -> {
|
||||
val creatingWalletAllowed = !(newState.twinCardsState != null &&
|
||||
newState.twinCardsState?.isCreatingTwinCardsAllowed != true)
|
||||
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
|
||||
mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed),
|
||||
topUpState = TopUpState(false)
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadData.Failure -> {
|
||||
when (action.error) {
|
||||
is TapError.NoInternetConnection -> {
|
||||
val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet
|
||||
val addressData = if (wallet == null) {
|
||||
null
|
||||
} else {
|
||||
AddressData(wallet.address, wallet.shareUrl, wallet.exploreUrl)
|
||||
}
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
error = ErrorType.NoInternetConnection,
|
||||
addressData = addressData,
|
||||
walletAddresses = createAddressList(wallet, newState.walletAddresses),
|
||||
currencyData = BalanceWidgetData(
|
||||
status = BalanceStatus.Unreachable,
|
||||
currency = wallet?.blockchain?.fullName,
|
||||
token = wallet?.token?.symbol?.let {
|
||||
token = wallet?.getFirstToken()?.symbol?.let {
|
||||
TokenData("", tokenSymbol = it)
|
||||
}),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
|
|
@ -86,11 +88,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
BalanceStatus.Loading,
|
||||
wallet.blockchain.fullName,
|
||||
currencySymbol = wallet.blockchain.currency,
|
||||
token = wallet.token?.symbol?.let {
|
||||
token = wallet.getFirstToken()?.symbol?.let {
|
||||
TokenData("", tokenSymbol = it)
|
||||
}
|
||||
),
|
||||
addressData = AddressData(wallet.address, wallet.shareUrl, wallet.exploreUrl),
|
||||
walletAddresses = createAddressList(wallet, newState.walletAddresses),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
topUpState = TopUpState(allowed = action.allowTopUp)
|
||||
)
|
||||
|
|
@ -143,9 +145,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
} else {
|
||||
newState.currencyData.fiatAmount
|
||||
}
|
||||
val tokenFiatAmount = if (currency == newState.wallet?.token?.symbol) {
|
||||
newState.wallet?.amounts?.get(AmountType.Token)?.value
|
||||
?.toFiatString(rate, state.globalState.appCurrency)
|
||||
val token = newState.wallet?.getFirstToken()
|
||||
val tokenFiatAmount = if (currency == token?.symbol) {
|
||||
newState.wallet?.getTokenAmount(token)?.value?.toFiatString(rate, state.globalState.appCurrency)
|
||||
} else {
|
||||
newState.currencyData.token?.fiatAmount
|
||||
}
|
||||
|
|
@ -163,6 +165,12 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
Artwork.SERGIO_CARD_URL
|
||||
} else if (action.card.cardId.startsWith(Artwork.MARTA_CARD_ID)) {
|
||||
Artwork.MARTA_CARD_URL
|
||||
} else if (newState.twinCardsState?.cardNumber != null) {
|
||||
when (newState.twinCardsState?.cardNumber) {
|
||||
TwinCardNumber.First -> Artwork.TWIN_CARD_1
|
||||
TwinCardNumber.Second -> Artwork.TWIN_CARD_2
|
||||
null -> Artwork.DEFAULT_IMG_URL
|
||||
}
|
||||
} else {
|
||||
Artwork.DEFAULT_IMG_URL
|
||||
}
|
||||
|
|
@ -171,8 +179,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
is WalletAction.ShowQrCode -> {
|
||||
newState = newState.copy(
|
||||
walletDialog = WalletDialog.QrDialog(
|
||||
newState.addressData?.shareUrl?.toQrCode(),
|
||||
newState.addressData?.shareUrl,
|
||||
newState.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(),
|
||||
newState.walletAddresses?.selectedAddress?.shareUrl,
|
||||
newState.currencyData.currency
|
||||
)
|
||||
)
|
||||
|
|
@ -216,10 +224,64 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
is WalletAction.TopUpAction -> {
|
||||
newState = newState.copy(topUpState = handleTopUpActions(action, newState.topUpState))
|
||||
}
|
||||
is WalletAction.ChangeSelectedAddress -> {
|
||||
val walletAddresses = newState.walletAddresses ?: return newState
|
||||
val address = walletAddresses.list.firstOrNull { it.type == action.type }
|
||||
?: return newState
|
||||
|
||||
newState = newState.copy(walletAddresses = WalletAddresses(address, walletAddresses.list))
|
||||
}
|
||||
is WalletAction.TwinsAction.SetTwinCard -> {
|
||||
newState = newState.copy(
|
||||
twinCardsState = TwinCardsState(
|
||||
secondCardId = action.secondCardId,
|
||||
cardNumber = action.number,
|
||||
showTwinOnboarding = newState.twinCardsState?.showTwinOnboarding
|
||||
?: false,
|
||||
isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletAction.TwinsAction.ShowOnboarding -> {
|
||||
newState = newState.copy(
|
||||
twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = true)
|
||||
?: TwinCardsState(null, null,
|
||||
showTwinOnboarding = true,
|
||||
isCreatingTwinCardsAllowed = false)
|
||||
)
|
||||
}
|
||||
is WalletAction.TwinsAction.SetOnboardingShown -> {
|
||||
newState = newState.copy(
|
||||
twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
||||
fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? {
|
||||
if (wallet == null) return null
|
||||
|
||||
val listOfAddressData = mutableListOf<AddressData>()
|
||||
// put a defaultAddress at the first place
|
||||
wallet.addresses.forEach {
|
||||
val addressData = AddressData(it.value, it.type, wallet.getShareUri(it.value), wallet.getExploreUrl(it.value))
|
||||
if (it.type == wallet.blockchain.defaultAddressType()) {
|
||||
listOfAddressData.add(0, addressData)
|
||||
} else {
|
||||
listOfAddressData.add(addressData)
|
||||
}
|
||||
}
|
||||
|
||||
// restore a selected wallet address
|
||||
var indexOfSelectedWallet = 0
|
||||
walletAddresses?.let {
|
||||
val index = listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address }
|
||||
if (index != -1) indexOfSelectedWallet = index
|
||||
}
|
||||
return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData)
|
||||
}
|
||||
|
||||
private fun handleTopUpActions(action: WalletAction.TopUpAction, state: TopUpState): TopUpState {
|
||||
return when (action) {
|
||||
is WalletAction.TopUpAction.TopUp -> state
|
||||
|
|
@ -230,13 +292,17 @@ private fun onWalletLoaded(
|
|||
wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null
|
||||
): WalletState {
|
||||
val fiatCurrencySymbol = store.state.globalState.appCurrency
|
||||
val token = wallet.amounts[AmountType.Token]
|
||||
val token = wallet.getFirstToken()
|
||||
val tokenData = if (token != null) {
|
||||
val tokenFiatRate = store.state.globalState.conversionRates.getRate(token.currencySymbol)
|
||||
val tokenFiatAmount = tokenFiatRate?.let { token.value?.toFiatString(it, fiatCurrencySymbol) }
|
||||
TokenData(
|
||||
token.value?.toFormattedString(token.decimals) ?: "",
|
||||
token.currencySymbol, tokenFiatAmount)
|
||||
val tokenAmount = wallet.getTokenAmount(token)
|
||||
if (tokenAmount != null) {
|
||||
val tokenFiatRate = store.state.globalState.conversionRates.getRate(tokenAmount.currencySymbol)
|
||||
val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
|
||||
TokenData(tokenAmount.value?.toFormattedString(tokenAmount.decimals) ?: "",
|
||||
tokenAmount.currencySymbol, tokenFiatAmount)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ package com.tangem.tap.features.wallet.redux
|
|||
|
||||
import android.graphics.Bitmap
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import org.rekotlin.StateType
|
||||
|
|
@ -16,17 +19,24 @@ data class WalletState(
|
|||
val wallet: Wallet? = null,
|
||||
val pendingTransactions: List<PendingTransaction> = emptyList(),
|
||||
val hashesCountVerified: Boolean? = null,
|
||||
val addressData: AddressData? = null,
|
||||
val walletAddresses: WalletAddresses? = null,
|
||||
val currencyData: BalanceWidgetData = BalanceWidgetData(),
|
||||
val payIdData: PayIdData = PayIdData(),
|
||||
val walletDialog: WalletDialog? = null,
|
||||
val updatingWallet: Boolean = false,
|
||||
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
|
||||
val topUpState: TopUpState = TopUpState()
|
||||
val topUpState: TopUpState = TopUpState(),
|
||||
val twinCardsState: TwinCardsState? = null,
|
||||
) : StateType {
|
||||
val showDetails: Boolean =
|
||||
currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
|
||||
currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain
|
||||
|
||||
val showSegwitAddress: Boolean
|
||||
get() {
|
||||
val listOfAddresses = walletAddresses?.list ?: return false
|
||||
return wallet?.blockchain == Blockchain.Bitcoin && listOfAddresses.size > 1
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WalletDialog {
|
||||
|
|
@ -37,6 +47,7 @@ sealed class WalletDialog {
|
|||
data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog()
|
||||
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog()
|
||||
data class WarningDialog(val type: WarningType) : WalletDialog()
|
||||
data class TwinsOnboardingFragment(val secondCardId: String): WalletDialog()
|
||||
}
|
||||
|
||||
enum class WarningType { CardSignedHashesBefore, DevCard }
|
||||
|
|
@ -60,8 +71,14 @@ sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
|
|||
class CreateWalletButton(enabled: Boolean) : WalletMainButton(enabled)
|
||||
}
|
||||
|
||||
data class WalletAddresses(
|
||||
val selectedAddress: AddressData,
|
||||
val list: List<AddressData>
|
||||
)
|
||||
|
||||
data class AddressData(
|
||||
val address: String,
|
||||
val type: AddressType,
|
||||
val shareUrl: String,
|
||||
val exploreUrl: String
|
||||
)
|
||||
|
|
@ -76,6 +93,8 @@ data class Artwork(
|
|||
const val MARTA_CARD_URL = "https://app.tangem.com/cards/card_tg083.png"
|
||||
const val SERGIO_CARD_ID = "BC01"
|
||||
const val MARTA_CARD_ID = "BC02"
|
||||
const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png"
|
||||
const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,4 +102,11 @@ data class TopUpState(
|
|||
val allowed: Boolean = true,
|
||||
val url: String? = null,
|
||||
val redirectUrl: String? = null
|
||||
)
|
||||
|
||||
data class TwinCardsState(
|
||||
val secondCardId: String?,
|
||||
val cardNumber: TwinCardNumber?,
|
||||
val showTwinOnboarding: Boolean,
|
||||
val isCreatingTwinCardsAllowed: Boolean
|
||||
)
|
||||
|
|
@ -40,6 +40,7 @@ data class TokenData(
|
|||
class BalanceWidget(
|
||||
val fragment: Fragment,
|
||||
val data: BalanceWidgetData,
|
||||
val isTwinCard: Boolean,
|
||||
) {
|
||||
|
||||
fun setup() {
|
||||
|
|
@ -100,8 +101,13 @@ class BalanceWidget(
|
|||
BalanceStatus.EmptyCard -> {
|
||||
fragment.l_balance.hide()
|
||||
fragment.l_balance_error.show()
|
||||
fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_card)
|
||||
fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_card_subtitle)
|
||||
if (isTwinCard) {
|
||||
fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_twin_card)
|
||||
fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_twin_card_subtitle)
|
||||
} else {
|
||||
fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_card)
|
||||
fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_card_subtitle)
|
||||
}
|
||||
}
|
||||
BalanceStatus.NoAccount -> {
|
||||
fragment.l_balance.hide()
|
||||
|
|
|
|||
|
|
@ -2,11 +2,8 @@ package com.tangem.tap.features.wallet.ui
|
|||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.*
|
||||
import android.view.Menu.NONE
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
|
|
@ -14,10 +11,14 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
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.common.address.AddressType
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
|
||||
|
|
@ -29,6 +30,7 @@ import com.tangem.wallet.R
|
|||
import kotlinx.android.synthetic.main.card_balance.*
|
||||
import kotlinx.android.synthetic.main.fragment_wallet.*
|
||||
import kotlinx.android.synthetic.main.layout_address.*
|
||||
import kotlinx.android.synthetic.main.layout_send_fee.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_short_buttons.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -90,6 +92,20 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
override fun newState(state: WalletState) {
|
||||
if (activity == null) return
|
||||
|
||||
state.twinCardsState?.cardNumber?.let { cardNumber ->
|
||||
tv_twin_card_number.show()
|
||||
iv_twin_card.show()
|
||||
val number = when (cardNumber) {
|
||||
TwinCardNumber.First -> "1"
|
||||
TwinCardNumber.Second -> "2"
|
||||
}
|
||||
tv_twin_card_number.text = getString(R.string.wallet_twins_chip_format, number)
|
||||
}
|
||||
if (state.twinCardsState?.cardNumber == null) {
|
||||
tv_twin_card_number.hide()
|
||||
iv_twin_card.hide()
|
||||
}
|
||||
|
||||
if (!state.showDetails) {
|
||||
toolbar.menu.removeItem(R.id.details_menu)
|
||||
} else if (toolbar.menu.findItem(R.id.details_menu) == null) {
|
||||
|
|
@ -116,7 +132,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
handleDialogs(state.walletDialog)
|
||||
|
||||
l_balance.show()
|
||||
BalanceWidget(this, state.currencyData).setup()
|
||||
BalanceWidget(this, state.currencyData, state.twinCardsState != null).setup()
|
||||
|
||||
if (state.state == ProgressState.Done) {
|
||||
srl_wallet.isRefreshing = false
|
||||
|
|
@ -187,7 +203,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
|
||||
val buttonTitle = when (state.mainButton) {
|
||||
is WalletMainButton.SendButton -> R.string.wallet_button_send
|
||||
is WalletMainButton.CreateWalletButton -> R.string.wallet_button_create_wallet
|
||||
is WalletMainButton.CreateWalletButton -> {
|
||||
if (state.twinCardsState == null) {
|
||||
R.string.wallet_button_create_wallet
|
||||
} else {
|
||||
R.string.wallet_button_create_twin_wallet
|
||||
}
|
||||
}
|
||||
}
|
||||
btnConfirm.text = getString(buttonTitle)
|
||||
btnConfirm.isEnabled = state.mainButton.enabled
|
||||
|
|
@ -206,9 +228,30 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
}
|
||||
|
||||
private fun setupAddressCard(state: WalletState) {
|
||||
if (state.addressData != null) {
|
||||
if (state.walletAddresses != null) {
|
||||
l_address?.show()
|
||||
tv_address.text = state.addressData.address
|
||||
val tvAddressPaddingTop = tv_address.resources.getDimension(R.dimen.dimen16).toInt()
|
||||
if (state.showSegwitAddress) {
|
||||
(l_address as? ViewGroup)?.beginDelayedTransition()
|
||||
tv_address.setPadding(tv_address.paddingStart, tvAddressPaddingTop / 2,
|
||||
tv_address.paddingEnd, tv_address.paddingBottom)
|
||||
chip_group_segwit.show()
|
||||
val checkedId = SegwitUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
|
||||
if (checkedId != View.NO_ID) chip_group_segwit.check(checkedId)
|
||||
|
||||
chip_group_segwit.setOnCheckedChangeListener { group, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
|
||||
SegwitUiHelper.idToType(checkedId)?.let {
|
||||
store.dispatch(WalletAction.ChangeSelectedAddress(it))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tv_address.setPadding(tv_address.paddingStart, tvAddressPaddingTop,
|
||||
tv_address.paddingEnd, tv_address.paddingBottom)
|
||||
chip_group_segwit.hide()
|
||||
}
|
||||
tv_address.text = state.walletAddresses.selectedAddress.address
|
||||
tv_explore?.setOnClickListener {
|
||||
store.dispatch(WalletAction.ExploreAddress(requireContext()))
|
||||
}
|
||||
|
|
@ -267,9 +310,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.details_menu -> {
|
||||
store.state.globalState.scanNoteResponse?.card?.let { card ->
|
||||
store.state.globalState.scanNoteResponse?.let { scanNoteResponse ->
|
||||
store.dispatch(DetailsAction.PrepareScreen(
|
||||
card, store.state.walletState.wallet,
|
||||
scanNoteResponse.card, scanNoteResponse,
|
||||
store.state.walletState.wallet,
|
||||
store.state.globalState.configManager?.config?.isCreatingTwinCardsAllowed,
|
||||
store.state.globalState.appCurrency
|
||||
))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Details))
|
||||
|
|
@ -285,4 +330,24 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
if (store.state.walletState.showDetails) inflater.inflate(R.menu.wallet, menu)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SegwitUiHelper {
|
||||
companion object {
|
||||
fun typeToId(type: AddressType): Int {
|
||||
return when (type) {
|
||||
is BitcoinAddressType.Legacy -> R.id.chip_legacy
|
||||
is BitcoinAddressType.Segwit -> R.id.chip_default
|
||||
else -> View.NO_ID
|
||||
}
|
||||
}
|
||||
|
||||
fun idToType(id: Int): AddressType? {
|
||||
return when (id) {
|
||||
R.id.chip_default -> BitcoinAddressType.Segwit
|
||||
R.id.chip_legacy -> BitcoinAddressType.Legacy
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_twin_cards.*
|
||||
import kotlinx.android.synthetic.main.layout_twin_cards.*
|
||||
|
||||
class TwinsOnboardingFragment : Fragment(R.layout.fragment_twin_cards) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
store.dispatch(WalletAction.TwinsAction.SetOnboardingShown)
|
||||
|
||||
val secondCardId = store.state.walletState.twinCardsState?.secondCardId ?: ""
|
||||
val secondTwinCardId = TwinsHelper.getTwinCardIdForUser(secondCardId)
|
||||
val text = getString(R.string.twins_onboarding_description_format, secondTwinCardId)
|
||||
tv_twin_cards_description_1.text = text
|
||||
|
||||
setOnClickListeners()
|
||||
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_1)
|
||||
.placeholder(R.drawable.card_placeholder)
|
||||
?.error(R.drawable.card_placeholder)
|
||||
?.into(iv_twin_card_1)
|
||||
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_2)
|
||||
.placeholder(R.drawable.card_placeholder)
|
||||
?.error(R.drawable.card_placeholder)
|
||||
?.into(iv_twin_card_2)
|
||||
}
|
||||
|
||||
private fun setOnClickListeners() {
|
||||
btn_continue.setOnClickListener {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.network.coinmarketcap
|
||||
|
||||
import com.tangem.tap.TapConfig
|
||||
import com.tangem.tap.network.createRetrofitInstance
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
|
@ -23,20 +22,20 @@ interface CoinMarketCapApi {
|
|||
companion object {
|
||||
private const val baseUrl = "https://pro-api.coinmarketcap.com/"
|
||||
|
||||
fun create(): CoinMarketCapApi {
|
||||
fun create(apiKey: String): CoinMarketCapApi {
|
||||
return createRetrofitInstance(
|
||||
baseUrl,
|
||||
listOf(createCoinMarketRequestInterceptor()),
|
||||
listOf(createCoinMarketRequestInterceptor(apiKey)),
|
||||
).create(CoinMarketCapApi::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCoinMarketRequestInterceptor(): Interceptor {
|
||||
private fun createCoinMarketRequestInterceptor(apiKey: String): Interceptor {
|
||||
return object : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val requestBuilder = chain.request().newBuilder()
|
||||
requestBuilder.addHeader("X-CMC_PRO_API_KEY", TapConfig.coinMarketCapKey)
|
||||
requestBuilder.addHeader("X-CMC_PRO_API_KEY", apiKey)
|
||||
return chain.proceed(requestBuilder.build())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,17 @@ package com.tangem.tap.network.coinmarketcap
|
|||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.commands.common.network.performRequest
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
class CoinMarketCapService {
|
||||
private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create() }
|
||||
class CoinMarketCapService() {
|
||||
private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create(getApiKey()) }
|
||||
|
||||
private fun getApiKey(): String {
|
||||
return store.state.globalState.configManager?.config?.coinMarketCapKey ?: ""
|
||||
}
|
||||
|
||||
suspend fun getRate(
|
||||
currency: String, fiatCurrency: FiatCurrencyName? = null
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.persistence
|
|||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
|
|
@ -18,6 +19,10 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
init {
|
||||
incrementLaunchCounter()
|
||||
}
|
||||
|
||||
private val fiatCurrenciesAdapter: JsonAdapter<List<FiatCurrency>> by lazy {
|
||||
val moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
|
|
@ -45,11 +50,7 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply()
|
||||
}
|
||||
|
||||
fun isFirstLaunch(): Boolean {
|
||||
val isFirst = !preferences.contains(FIRST_LAUNCH_CHECK_KEY)
|
||||
if (isFirst) preferences.edit().putInt(FIRST_LAUNCH_CHECK_KEY, System.currentTimeMillis().toInt()).apply()
|
||||
return isFirst
|
||||
}
|
||||
fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1)
|
||||
|
||||
fun saveScannedCardId(cardId: String) {
|
||||
val scannedCardsIds: String = restoreScannedCardIds()
|
||||
|
|
@ -65,7 +66,6 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
private fun restoreScannedCardIds(): String =
|
||||
preferences.getString(SCANNED_CARDS_IDS_KEY, "") ?: ""
|
||||
|
||||
|
||||
fun saveDisclaimerAccepted() {
|
||||
preferences.edit().putBoolean(DISCLAIMER_ACCEPTED_KEY, true).apply()
|
||||
}
|
||||
|
|
@ -74,13 +74,27 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
return preferences.getBoolean(DISCLAIMER_ACCEPTED_KEY, false)
|
||||
}
|
||||
|
||||
fun saveTwinsOnboardingShown() {
|
||||
preferences.edit().putBoolean(TWINS_ONBOARDING_SHOWN_KEY, true).apply()
|
||||
}
|
||||
|
||||
fun wasTwinsOnboardingShown(): Boolean {
|
||||
return preferences.getBoolean(TWINS_ONBOARDING_SHOWN_KEY, false)
|
||||
}
|
||||
|
||||
private fun incrementLaunchCounter() {
|
||||
var count = preferences.getInt(APP_LAUNCH_COUNT_KEY, 0)
|
||||
preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFERENCES_NAME = "tapPrefs"
|
||||
private const val APP_CURRENCY_KEY = "appCurrency"
|
||||
private const val FIAT_CURRENCIES_KEY = "fiatCurrencies"
|
||||
private const val FIRST_LAUNCH_CHECK_KEY = "firstLaunchCheck"
|
||||
private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds"
|
||||
private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted"
|
||||
private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown"
|
||||
private const val APP_LAUNCH_COUNT_KEY = "launchCount"
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB |
26
app/src/main/res/drawable/ic_group_1258.xml
Normal file
26
app/src/main/res/drawable/ic_group_1258.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="375dp"
|
||||
android:height="471dp"
|
||||
android:viewportWidth="375"
|
||||
android:viewportHeight="471">
|
||||
<path
|
||||
android:pathData="M-65.241,418.887l452.126,-172.218l26.339,66.399l-452.126,172.218z"
|
||||
android:strokeAlpha="0.35"
|
||||
android:fillColor="#D1D1D6"
|
||||
android:fillAlpha="0.35"/>
|
||||
<path
|
||||
android:pathData="M-91.81,315l474.237,-180.641l39.37,99.249l-474.237,180.641z"
|
||||
android:strokeAlpha="0.2"
|
||||
android:fillColor="#636366"
|
||||
android:fillAlpha="0.2"/>
|
||||
<path
|
||||
android:pathData="M-114,31.889l452.126,-172.218l70.731,178.307l-452.126,172.218z"
|
||||
android:strokeAlpha="0.22"
|
||||
android:fillColor="#1C1C1E"
|
||||
android:fillAlpha="0.22"/>
|
||||
<path
|
||||
android:pathData="M-61.336,216.984l452.126,-172.218l29.793,75.107l-452.126,172.218z"
|
||||
android:strokeAlpha="0.2"
|
||||
android:fillColor="#3A3A3C"
|
||||
android:fillAlpha="0.2"/>
|
||||
</vector>
|
||||
101
app/src/main/res/drawable/ic_payid.xml
Normal file
101
app/src/main/res/drawable/ic_payid.xml
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="97dp"
|
||||
android:height="19dp"
|
||||
android:viewportWidth="97"
|
||||
android:viewportHeight="19">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h96.5903v19h-96.5903z"/>
|
||||
<path
|
||||
android:pathData="M29.8167,8.4838C29.5066,9.0359 29.0376,9.482 28.4707,9.7641C27.8598,10.0888 27.0902,10.2507 26.1619,10.2496H24.2677V14.5925H21.9986V3.2826H26.1629C27.038,3.2826 27.7836,3.4337 28.3997,3.7358C28.9765,4.003 29.4594,4.4378 29.7853,4.9836C30.0983,5.5309 30.2581,6.1524 30.2479,6.7828C30.2488,7.377 30.1006,7.9619 29.8167,8.4838ZM27.4588,7.9899C27.7606,7.7038 27.9117,7.3014 27.912,6.7828C27.912,5.6812 27.2963,5.1302 26.0648,5.1298H24.2677V8.4191H26.0669C26.6924,8.4191 27.1563,8.276 27.4588,7.9899Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M31.2345,7.6391C31.5649,6.9717 32.0757,6.4102 32.709,6.0185C33.3364,5.6386 34.0577,5.4422 34.7911,5.4515C35.4019,5.438 36.0064,5.5772 36.5496,5.8566C37.0231,6.1024 37.4355,6.451 37.7567,6.8768V5.5977H40.0415V14.5738H37.7567V13.2612C37.4438,13.7012 37.0299,14.0597 36.5496,14.3064C36.002,14.5902 35.3921,14.7324 34.7755,14.72C34.0454,14.7274 33.3286,14.5252 32.71,14.1373C32.0765,13.7349 31.5661,13.1659 31.2345,12.4926C30.861,11.7351 30.6748,10.8989 30.6915,10.0544C30.6747,9.2172 30.861,8.3885 31.2345,7.6391ZM37.432,8.6687C37.2309,8.2903 36.9278,7.9759 36.5569,7.7613C36.1977,7.5525 35.7893,7.4433 35.3738,7.4449C34.9569,7.4408 34.5469,7.5512 34.1884,7.7641C33.83,7.9769 33.5367,8.284 33.3407,8.652C33.1106,9.0825 32.9961,9.5653 33.0087,10.0533C32.9974,10.5465 33.1116,11.0345 33.3407,11.4714C33.5423,11.8556 33.8449,12.1776 34.2158,12.4028C34.565,12.6153 34.9661,12.7273 35.3749,12.7265C35.79,12.7281 36.1981,12.6189 36.5569,12.4101C36.9275,12.1952 37.2306,11.8809 37.432,11.5027C37.6582,11.0651 37.7696,10.5771 37.7557,10.0847C37.7688,9.5929 37.6575,9.1059 37.432,8.6687Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M53.7502,14.3074C53.1525,14.0645 52.6338,13.6608 52.2517,13.141C51.8784,12.6157 51.68,11.9862 51.6847,11.3418H54.1157C54.1212,11.549 54.1678,11.7531 54.2528,11.9422C54.3378,12.1313 54.4594,12.3016 54.6106,12.4434C54.9082,12.7128 55.3162,12.8479 55.8344,12.8486C56.3639,12.8486 56.7798,12.7215 57.0824,12.4675C57.2294,12.3474 57.3467,12.1951 57.4253,12.0223C57.5039,11.8495 57.5416,11.661 57.5355,11.4713C57.5409,11.3201 57.5165,11.1693 57.4637,11.0276C57.4108,10.8859 57.3305,10.756 57.2274,10.6453C57.0105,10.4219 56.748,10.2477 56.4578,10.1347C56.0412,9.9752 55.6167,9.8372 55.186,9.7212C54.575,9.5494 53.9766,9.3355 53.3951,9.081C52.9285,8.8646 52.5225,8.5365 52.2131,8.1256C51.8831,7.6985 51.7181,7.1287 51.7181,6.4162C51.7043,5.7954 51.8795,5.1851 52.2204,4.6661C52.5705,4.1571 53.0604,3.7603 53.6311,3.5237C54.2873,3.2483 54.9936,3.1128 55.7051,3.1259C56.8715,3.1259 57.8196,3.4095 58.5494,3.9769C59.2783,4.5439 59.6807,5.3351 59.7565,6.3504H57.2608C57.2529,6.1635 57.2046,5.9806 57.1191,5.8142C57.0337,5.6478 56.9132,5.5019 56.7659,5.3866C56.4578,5.1339 56.0507,5.0054 55.5422,5.0054C55.0994,5.0054 54.7457,5.1189 54.4811,5.3459C54.2159,5.5725 54.0833,5.9017 54.0833,6.3337C54.0794,6.4731 54.104,6.6119 54.1555,6.7415C54.207,6.8712 54.2844,6.9889 54.383,7.0876C54.5969,7.2973 54.8503,7.4623 55.1286,7.5732C55.4262,7.6975 55.8438,7.8405 56.3847,8.0034C56.9991,8.1762 57.6003,8.3926 58.1839,8.6508C58.6562,8.8733 59.0675,9.2069 59.3827,9.623C59.7179,10.0553 59.8853,10.6227 59.885,11.3251C59.8879,11.9196 59.7252,12.5032 59.4151,13.0104C59.0826,13.5461 58.6045,13.976 58.0367,14.2499C57.4311,14.558 56.7127,14.7122 55.8815,14.7125C55.151,14.7234 54.4258,14.5856 53.7502,14.3074Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M64.0253,7.4426V11.7856C64.0253,12.0873 64.098,12.3063 64.2435,12.4424C64.3896,12.5781 64.635,12.6449 64.9807,12.6449H66.0333V14.5569H64.6069C62.695,14.5569 61.7388,13.6279 61.7385,11.7699V7.4426H60.6692V5.5798H61.7385V3.3597H64.0232V5.5798H66.0323V7.4437L64.0253,7.4426Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M70.9747,5.8565C71.4667,5.5809 72.0231,5.4411 72.5869,5.4513V7.8332H71.9877C71.2745,7.8332 70.737,8.0006 70.3754,8.3355C70.013,8.6696 69.8323,9.2544 69.8323,10.0856V14.558H67.5633V5.5819H69.8323V6.9759C70.1126,6.5124 70.5056,6.1273 70.9747,5.8565Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M74.0811,4.1648C73.9516,4.0437 73.8492,3.8967 73.7807,3.7333C73.7121,3.5699 73.6789,3.3938 73.6832,3.2166C73.6792,3.0395 73.7125,2.8635 73.7811,2.7001C73.8496,2.5368 73.9519,2.3897 74.0811,2.2685C74.3549,2.0231 74.7096,1.8873 75.0773,1.8873C75.4449,1.8873 75.7997,2.0231 76.0735,2.2685C76.2028,2.3896 76.305,2.5367 76.3736,2.7001C76.4421,2.8635 76.4754,3.0395 76.4713,3.2166C76.4753,3.3937 76.442,3.5697 76.3734,3.7331C76.3049,3.8965 76.2027,4.0436 76.0735,4.1648C75.7997,4.4102 75.4449,4.5459 75.0773,4.5459C74.7096,4.5459 74.3549,4.4102 74.0811,4.1648ZM76.1956,5.6152V14.5913H73.9265V5.6152H76.1956Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M85.3568,6.4643C86.0157,7.1399 86.345,8.0825 86.3446,9.2921V14.5581H84.0755V9.6001C84.0755,8.8869 83.8973,8.3387 83.5409,7.9555C83.1848,7.5722 82.6989,7.3804 82.0831,7.3801C81.4566,7.3801 80.9623,7.5719 80.6004,7.9555C80.238,8.3387 80.057,8.8869 80.0574,9.6001V14.5591H77.7883V5.5809H80.0574V6.6982C80.3655,6.3053 80.7629,5.9914 81.2164,5.7825C81.7013,5.5583 82.2299,5.4449 82.764,5.4504C83.8343,5.4514 84.6986,5.7894 85.3568,6.4643Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M93.1069,5.8503C93.5848,6.0896 93.9965,6.4429 94.3057,6.8788V5.5986H96.5904V14.6405C96.6038,15.4125 96.4315,16.1763 96.0882,16.8678C95.7505,17.5238 95.2262,18.0652 94.5814,18.4237C93.911,18.808 93.1006,18.9998 92.1504,18.9991C90.8754,18.9991 89.8302,18.7022 89.0146,18.1084C88.6198,17.8293 88.2911,17.4669 88.0516,17.0469C87.8122,16.6268 87.6679,16.1593 87.6289,15.6774H89.8813C90.0024,16.091 90.278,16.4421 90.6509,16.6579C91.0852,16.9115 91.582,17.0377 92.0846,17.0224C92.7435,17.0224 93.2785,16.825 93.6896,16.4303C94.1,16.0356 94.305,15.439 94.3046,14.6405V13.2465C93.9909,13.6877 93.5773,14.0484 93.0975,14.2991C92.5566,14.5865 91.9514,14.7313 91.3391,14.7199C90.6065,14.7266 89.8871,14.5245 89.2652,14.1372C88.6284,13.7367 88.1151,13.1674 87.7824,12.4926C87.4089,11.735 87.2227,10.8988 87.2394,10.0543C87.2231,9.2175 87.4094,8.3892 87.7824,7.6401C88.1128,6.9724 88.6241,6.4109 89.2579,6.0194C89.8854,5.6396 90.6067,5.4432 91.3401,5.4524C91.9526,5.4391 92.5592,5.5757 93.1069,5.8503ZM93.982,8.6697C93.7812,8.2911 93.478,7.9766 93.1069,7.7622C92.7477,7.5534 92.3393,7.4442 91.9238,7.4458C91.5069,7.4418 91.0969,7.5522 90.7384,7.765C90.3799,7.9779 90.0867,8.285 89.8907,8.653C89.6606,9.0834 89.5461,9.5663 89.5587,10.0543C89.5473,10.5475 89.6616,11.0355 89.8907,11.4724C90.0923,11.8566 90.3949,12.1786 90.7658,12.4038C91.115,12.6163 91.5161,12.7283 91.9249,12.7275C92.3403,12.729 92.7487,12.6198 93.108,12.4111C93.4788,12.1965 93.782,11.8821 93.983,11.5037C94.2092,11.066 94.3206,10.5781 94.3067,10.0856C94.3197,9.5938 94.208,9.1067 93.982,8.6697Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M47.8324,10.708H47.8378V10.6212C47.8367,10.6512 47.8356,10.6802 47.8324,10.708Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M50.1111,5.5996V14.6426C50.1242,15.4145 49.9519,16.1783 49.6088,16.8699C49.2713,17.526 48.7465,18.0672 48.101,18.4247C47.4316,18.809 46.6217,19.0008 45.6711,19.0001C45.1433,19.0051 44.617,18.9455 44.1037,18.8226C43.5408,18.6893 43.0091,18.4478 42.5384,18.1115C42.5374,18.1104 42.5363,18.1104 42.5353,18.1094C42.1406,17.8301 41.812,17.4677 41.5726,17.0477C41.3332,16.6277 41.1888,16.1603 41.1496,15.6784H43.402C43.4252,15.7605 43.4552,15.8405 43.4918,15.9175C43.6381,16.2278 43.8751,16.4864 44.1716,16.6589C44.5451,16.8805 44.9686,17.004 45.4027,17.0182C45.4685,17.0213 45.5374,17.0234 45.6063,17.0234C46.2652,17.0234 46.7999,16.8264 47.2103,16.4323C47.6206,16.0376 47.826,15.441 47.8263,14.6426V13.2485C47.573,13.6081 47.2507,13.9137 46.8782,14.1476C46.7957,14.2019 46.708,14.252 46.6192,14.3011C46.5879,14.3188 46.5555,14.3356 46.5232,14.3523H46.5221C46.127,14.5484 45.6982,14.6678 45.2586,14.7042C45.2189,14.7052 45.1772,14.7063 45.1364,14.7063C44.0661,14.7063 43.2018,14.369 42.5436,13.6944C42.0247,13.1598 41.7103,12.4601 41.6007,11.5955C41.5696,11.3535 41.5542,11.1097 41.5548,10.8656V5.5996H43.8228V10.5586C43.8228,11.2708 44.0014,11.82 44.3585,12.2022C44.6057,12.4686 44.9298,12.6511 45.2858,12.7243C45.4601,12.7612 45.638,12.7794 45.8162,12.7786C46.4438,12.7786 46.9381,12.5865 47.299,12.2022C47.6175,11.866 47.7954,11.402 47.8326,10.8103H47.8378V5.5996H50.1111Z"
|
||||
android:fillColor="#000E33"/>
|
||||
<path
|
||||
android:pathData="M10.0944,15.0026C11.369,14.8333 12.5495,14.2395 13.4453,13.3172L18.6633,7.9687C18.3313,7.9145 17.9952,7.8893 17.6588,7.8935C16.9109,7.8953 16.171,8.0465 15.4824,8.3382C14.7938,8.63 14.1704,9.0564 13.649,9.5925L11.7245,11.564C11.713,11.5765 11.7004,11.589 11.6879,11.6026C11.0831,12.223 10.2581,12.5798 9.3918,12.5957C8.5256,12.6115 7.688,12.285 7.061,11.6872L9.0711,13.6482C9.4784,14.0456 9.8234,14.5022 10.0944,15.0026Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="6.23136"
|
||||
android:startX="19.5904"
|
||||
android:endY="12.3056"
|
||||
android:endX="10.6475"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFC906F9"/>
|
||||
<item android:offset="0.4483" android:color="#FFA803FC"/>
|
||||
<item android:offset="0.8625" android:color="#FF8700FF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M5.3465,13.4445L10.695,18.6624C10.8987,17.4088 10.6875,16.123 10.0935,15.0004C9.8231,14.4999 9.478,14.0436 9.0702,13.647L7.0601,11.686C6.7446,11.3857 6.492,11.0258 6.3167,10.6271C6.1414,10.2284 6.047,9.7989 6.0389,9.3635C6.0308,8.9281 6.1093,8.4953 6.2697,8.0905C6.4301,7.6856 6.6693,7.3165 6.9734,7.0048L5.0144,9.0149C4.6106,9.4301 4.145,9.7802 3.634,10.0528C3.8012,11.3456 4.4055,12.5423 5.3465,13.4445Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="10.1089"
|
||||
android:startX="8.29132"
|
||||
android:endY="18.3078"
|
||||
android:endX="6.11687"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF0044FF"/>
|
||||
<item android:offset="0.4896" android:color="#FF0087FF"/>
|
||||
<item android:offset="1" android:color="#FF00CAFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M0,10.6417C0.332,10.6961 0.6682,10.7209 1.0045,10.7158C1.9213,10.7097 2.8232,10.483 3.6339,10.0548C4.145,9.7825 4.6108,9.4323 5.0143,9.0169L6.9764,7.0068C7.278,6.6968 7.6377,6.4494 8.035,6.2787C8.4322,6.108 8.8593,6.0172 9.2917,6.0117C9.724,6.0062 10.1533,6.0859 10.5548,6.2465C10.9563,6.407 11.3222,6.6451 11.6316,6.9472L9.6475,5.0123C9.2244,4.6011 8.8689,4.1257 8.5939,3.6036C7.3104,3.7688 6.1208,4.364 5.219,5.2921L0,10.6417Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="6.24054"
|
||||
android:startX="1.39411"
|
||||
android:endY="7.59302"
|
||||
android:endX="5.84485"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFB0FF33"/>
|
||||
<item android:offset="0.4948" android:color="#FF55DF20"/>
|
||||
<item android:offset="1" android:color="#FF0AC222"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M11.7245,11.5658L13.649,9.5943C14.0528,9.1791 14.5185,8.829 15.0295,8.5563C14.8655,7.29 14.2821,6.115 13.3723,5.219L8.0239,0C7.9696,0.332 7.9445,0.6681 7.9487,1.0045C7.9549,1.9106 8.1764,2.8022 8.5951,3.6057C8.87,4.1277 9.2256,4.6031 9.6487,5.0143L11.6317,6.9493C12.2506,7.5522 12.6073,8.3746 12.6247,9.2385C12.6421,10.1024 12.3187,10.9384 11.7246,11.5658H11.7245Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="6.51626"
|
||||
android:startX="6.78645"
|
||||
android:endY="4.72844"
|
||||
android:endX="14.3139"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFFF8100"/>
|
||||
<item android:offset="0.5" android:color="#FFFF5D00"/>
|
||||
<item android:offset="1" android:color="#FFFF3600"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="375dp"
|
||||
android:height="471dp"
|
||||
android:viewportWidth="375"
|
||||
android:viewportHeight="471">
|
||||
<path
|
||||
android:pathData="M-65.241,418.887l452.126,-172.218l26.339,66.399l-452.126,172.218z"
|
||||
android:fillColor="@color/warning"
|
||||
android:fillAlpha="0.25"/>
|
||||
<path
|
||||
android:pathData="M-91.81,315l474.237,-180.641l39.37,99.249l-474.237,180.641z"
|
||||
android:strokeAlpha="0.2"
|
||||
android:fillColor="@color/warning"
|
||||
android:fillAlpha="0.35"/>
|
||||
<path
|
||||
android:pathData="M-114,31.889l452.126,-172.218l70.731,178.307l-452.126,172.218z"
|
||||
android:strokeAlpha="0.22"
|
||||
android:fillColor="@color/warning"
|
||||
android:fillAlpha="0.55"/>
|
||||
<path
|
||||
android:pathData="M-61.336,216.984l452.126,-172.218l29.793,75.107l-452.126,172.218z"
|
||||
android:strokeAlpha="0.2"
|
||||
android:fillColor="@color/warning"
|
||||
android:fillAlpha="0.45"/>
|
||||
</vector>
|
||||
13
app/src/main/res/drawable/rectangle_twin_background.xml
Normal file
13
app/src/main/res/drawable/rectangle_twin_background.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle"
|
||||
>
|
||||
<size
|
||||
android:width="484dp"
|
||||
android:height="192dp" />
|
||||
<gradient
|
||||
android:endColor="@color/darkGray6"
|
||||
android:startColor="@color/darkGray6" />
|
||||
|
||||
</shape>
|
||||
10
app/src/main/res/drawable/shape_chip.xml
Normal file
10
app/src/main/res/drawable/shape_chip.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/twins_dark" />
|
||||
<corners
|
||||
android:bottomLeftRadius="30dp"
|
||||
android:bottomRightRadius="30dp"
|
||||
android:topLeftRadius="30dp"
|
||||
android:topRightRadius="30dp" />
|
||||
</shape>
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
android:id="@+id/tv_card_id_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="14dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/details_row_title_cid"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
android:id="@+id/tv_card_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="14dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
@ -70,7 +70,8 @@
|
|||
android:id="@+id/tv_issuer_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="14dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/details_row_title_issuer"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
|
|
@ -81,7 +82,8 @@
|
|||
android:id="@+id/tv_issuer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="14dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
@ -92,6 +94,7 @@
|
|||
android:id="@+id/tv_signed_hashes_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/details_row_title_signed_hashes"
|
||||
android:textColor="@color/darkGray6"
|
||||
|
|
|
|||
160
app/src/main/res/layout/fragment_details_twin_cards.xml
Normal file
160
app/src/main/res/layout/fragment_details_twin_cards.xml
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:fitsSystemWindows="true"
|
||||
app:liftOnScroll="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
|
||||
app:title="Re-create wallet" />
|
||||
</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_marginTop="16dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_step_number"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="start"
|
||||
android:text="@string/details_twins_recreate_step_format"
|
||||
android:textColor="@color/colorSecondary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="@+id/v_step_1"
|
||||
app:layout_constraintStart_toStartOf="@id/v_step_1"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/v_step_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="3dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:background="@color/colorSecondary"
|
||||
app:layout_constraintEnd_toStartOf="@+id/v_step_2"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_step_number" />
|
||||
|
||||
<View
|
||||
android:id="@+id/v_step_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="3dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:background="@color/colorSecondary"
|
||||
app:layout_constraintEnd_toStartOf="@+id/v_step_3"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/v_step_1"
|
||||
app:layout_constraintTop_toTopOf="@id/v_step_1" />
|
||||
|
||||
<View
|
||||
android:id="@+id/v_step_3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="3dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:background="@color/colorSecondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/v_step_2"
|
||||
app:layout_constraintTop_toTopOf="@id/v_step_1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_twin_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:text="@string/details_twins_recreate_title_format"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/v_step_1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_twin_cards_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:text="@string/details_twins_recreate_subtitle"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_twin_title" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_card_1"
|
||||
android:layout_width="130dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginStart="47dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:src="@drawable/card_placeholder"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_twin_cards_description" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_card_2"
|
||||
android:layout_width="130dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:src="@drawable/card_placeholder"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/iv_card_1" />
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintGuide_percent="0.37" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_tap"
|
||||
style="@style/TapBlackButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:icon="@drawable/ic_scan"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
tools:text="@string/details_twins_recreate_button_format" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
103
app/src/main/res/layout/fragment_details_twin_cards_warning.xml
Normal file
103
app/src/main/res/layout/fragment_details_twin_cards_warning.xml
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/coordinator_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cl_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="33dp">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/l_twin_cards"
|
||||
android:alpha="0.55"
|
||||
android:background="@color/warning"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/l_twin_cards"
|
||||
layout="@layout/layout_twin_cards_orange"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="-40dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_twin_title"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_twin_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:drawablePadding="4dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:text="@string/common_warning"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:drawableStartCompat="@drawable/ic_warning"
|
||||
app:drawableTint="@color/darkGray6"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_twin_cards_description"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_twin_cards_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:text="@string/details_twins_recreate_warning"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_start"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_cancel"
|
||||
style="@style/TapBlackButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/common_back"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintGuide_percent="0.37" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_start"
|
||||
style="@style/TapButtonWithIcon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/common_start"
|
||||
app:icon="@drawable/ic_send"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -28,7 +28,6 @@
|
|||
android:id="@+id/cl_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
|
|
@ -36,7 +35,7 @@
|
|||
android:id="@+id/sv_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="never"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_accept">
|
||||
|
|
@ -67,7 +66,6 @@
|
|||
android:id="@+id/btn_accept"
|
||||
style="@style/TapButtonWithIcon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
|
|
|
|||
97
app/src/main/res/layout/fragment_twin_cards.xml
Normal file
97
app/src/main/res/layout/fragment_twin_cards.xml
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cl_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="33dp">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/l_twin_cards"
|
||||
android:alpha="0.22"
|
||||
android:background="@color/darkGray6"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/l_twin_cards"
|
||||
layout="@layout/layout_twin_cards"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="-40dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:gravity="top"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_twin_title"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_twin_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24sp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/twins_onboarding_title"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_twin_subtitle"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_twin_subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/twins_onboarding_subtitle"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_twin_cards_description_1"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_twin_cards_description_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:text="@string/twins_onboarding_description_format"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_continue"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintGuide_percent="0.37" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_continue"
|
||||
style="@style/TapBlackButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/common_continue"
|
||||
app:icon="@drawable/ic_send"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
@ -61,6 +62,40 @@
|
|||
android:src="@drawable/card_placeholder"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_twin_card"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="30dp"
|
||||
android:src="@drawable/shape_chip"
|
||||
android:elevation="3dp"
|
||||
android:layout_marginStart="25dp"
|
||||
app:layout_constraintStart_toStartOf="@id/iv_card"
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_card"
|
||||
app:layout_constraintBottom_toBottomOf="@id/iv_card"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_twin_card_number"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toStartOf="@id/iv_twin_card"
|
||||
app:layout_constraintEnd_toEndOf="@id/iv_twin_card"
|
||||
app:layout_constraintTop_toTopOf="@id/iv_twin_card"
|
||||
app:layout_constraintBottom_toBottomOf="@id/iv_twin_card"
|
||||
tools:text="Card 2 of 2"
|
||||
android:elevation="3dp"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/buttonGray"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
app:barrierDirection="bottom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="iv_card,iv_twin_card"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_pending_transaction"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -68,7 +103,7 @@
|
|||
android:layout_marginTop="12dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:overScrollMode="never"
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_card" />
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_card_balance"
|
||||
|
|
|
|||
|
|
@ -23,21 +23,52 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/chip_group_segwit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:selectionRequired="true"
|
||||
app:singleLine="true"
|
||||
app:singleSelection="true"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chip_default"
|
||||
style="@style/TapChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/wallet_address_chip_default" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chip_legacy"
|
||||
style="@style/TapChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/wallet_address_chip_legacy" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
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_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/chip_group_segwit"
|
||||
tools:text="139mrsJgyWnJ**** **** y9BV" />
|
||||
|
||||
<TextView
|
||||
|
|
@ -66,10 +97,10 @@
|
|||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/shape_ellipse"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:elevation="3dp"
|
||||
android:focusable="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/btn_show_qr"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toBottomOf="@id/chip_group_segwit">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_copy"
|
||||
|
|
@ -90,10 +121,10 @@
|
|||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/shape_ellipse"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:elevation="3dp"
|
||||
android:focusable="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toBottomOf="@id/chip_group_segwit">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_qr"
|
||||
|
|
@ -117,12 +148,13 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/iv_payid_icon"
|
||||
android:layout_width="68dp"
|
||||
android:layout_height="22dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:src="@drawable/ic_payid"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/v_payid_divider" />
|
||||
app:layout_constraintTop_toBottomOf="@id/v_payid_divider"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_create_payid"
|
||||
|
|
@ -142,18 +174,16 @@
|
|||
android:id="@+id/tv_payid_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:padding="16dp"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="textEnd"
|
||||
android:textColor="@color/darkGray1"
|
||||
android:textSize="13sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_payid_icon"
|
||||
android:textAlignment="textEnd"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/v_payid_divider"
|
||||
tools:text="roman$payid.tangem.com"
|
||||
android:visibility="gone"/>
|
||||
tools:text="romafdffdfdfn$payid.tangem.com" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_payid"
|
||||
|
|
|
|||
|
|
@ -48,21 +48,21 @@
|
|||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipLow"
|
||||
style="@style/ChipNetworkFee"
|
||||
style="@style/TapChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_fee_picker_low" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipNormal"
|
||||
style="@style/ChipNetworkFee"
|
||||
style="@style/TapChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_fee_picker_normal" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipPriority"
|
||||
style="@style/ChipNetworkFee"
|
||||
style="@style/TapChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_fee_picker_priority" />
|
||||
|
|
|
|||
38
app/src/main/res/layout/layout_twin_cards.xml
Normal file
38
app/src/main/res/layout/layout_twin_cards.xml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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_marginStart="-40dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginTop="60dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_group_1258"
|
||||
app:layout_constraintBottom_toBottomOf="@id/iv_twin_card_2"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_twin_card_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="170dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:rotation="-19.5"
|
||||
android:src="@drawable/card_placeholder"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_twin_card_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="170dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:rotation="-19.5"
|
||||
android:src="@drawable/card_placeholder"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_twin_card_1" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
38
app/src/main/res/layout/layout_twin_cards_orange.xml
Normal file
38
app/src/main/res/layout/layout_twin_cards_orange.xml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
|
||||
android:layout_marginStart="-40dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginTop="60dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_twin_cards_background_warning"
|
||||
app:layout_constraintBottom_toBottomOf="@id/iv_twin_card_2"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_twin_card_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="170dp"
|
||||
android:layout_marginTop="50dp"
|
||||
android:rotation="-19.5"
|
||||
android:src="@drawable/card_placeholder"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_twin_card_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="170dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:rotation="-19.5"
|
||||
android:src="@drawable/card_placeholder"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_twin_card_1" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -40,18 +40,18 @@
|
|||
<string name="wallet_pending_tx_receiving">Erhalten\u0020</string>
|
||||
<string name="wallet_pending_tx_receiving_address_format">von %s </string>
|
||||
<string name="wallet_address_button_explore">Adresse erkunden</string>
|
||||
<string name="wallet_address_button_create_payid">PayID erstellen</string>
|
||||
<string name="wallet_address_button_create_payid">PayString erstellen</string>
|
||||
<string name="wallet_qr_title_format">%s Wallet</string>
|
||||
<string name="wallet_create_payid">PayID erstellen</string>
|
||||
<string name="wallet_create_payid">PayString erstellen</string>
|
||||
<string name="wallet_create_payid_card_format">Karte: %s</string>
|
||||
<string name="wallet_create_payid_hint">PayID Name</string>
|
||||
<string name="wallet_create_payid_hint">PayString Name</string>
|
||||
<string name="wallet_create_payid_domain">$payid.tangem.com</string>
|
||||
<string name="wallet_create_payid_button_title">Erstellen</string>
|
||||
<string name="wallet_create_payid_info">PayID sind Ihre einzigartigen Identifikationsdaten wie Telefonnummer, E-Mail-Adresse oder ABN.</string>
|
||||
<string name="wallet_create_payid_success_message">Ihre PayID wurde erfolgreich erstellt und zum Clipboard kopiert</string>
|
||||
<string name="wallet_create_payid_error_message">Fehlerantwort beim Erstellen der ZahlungsID</string>
|
||||
<string name="wallet_create_payid_error_already_created">Diese PayID existiert bereits. Versuchen Sie mit einer anderen erneut.</string>
|
||||
<string name="send_destination_hint_address_payid">Adresse oder PayID</string>
|
||||
<string name="wallet_create_payid_info">PayString sind Ihre einzigartigen Identifikationsdaten wie Telefonnummer, E-Mail-Adresse oder ABN.</string>
|
||||
<string name="wallet_create_payid_success_message">Ihre PayString wurde erfolgreich erstellt und zum Clipboard kopiert</string>
|
||||
<string name="wallet_create_payid_error_message">Fehlerantwort beim Erstellen der PayString</string>
|
||||
<string name="wallet_create_payid_error_already_created">Diese PayString existiert bereits. Versuchen Sie mit einer anderen erneut.</string>
|
||||
<string name="send_destination_hint_address_payid">Adresse oder PayString</string>
|
||||
<string name="send_destination_hint_address">Adresse</string>
|
||||
<string name="send_title">Absenden</string>
|
||||
<string name="send_network_fee_title">Netzgebühr</string>
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
<string name="initial_message_create_wallet_body">Tippen Sie um ein Wallet zu erstellen</string>
|
||||
<string name="initial_message_change_access_code_body">Tippen Sie um den Zugangscode zu ändern</string>
|
||||
<string name="initial_message_change_passcode_body">Tippen Sie um den Passcode zu ändern</string>
|
||||
<string name="payid_service_error_loading">Ungültige ZahlungsID</string>
|
||||
<string name="payid_service_error_loading">Ungültige PayString</string>
|
||||
|
||||
|
||||
<string name="disclaimer_text">
|
||||
|
|
@ -167,10 +167,10 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS
|
|||
<string name="send_error_fee_request_failed">Erhalt der Gebühr fehlgeschlagen</string>
|
||||
|
||||
<string name="send_error_unknown">Unbekannter Fehler</string>
|
||||
<string name="send_error_payid_verification_failed">Verifikation der PayID ist fehlgeschlagen</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayID wird von der Blockchain nicht unterstützt</string>
|
||||
<string name="send_error_payid_not_registered">PayID ist nicht registriert</string>
|
||||
<string name="send_error_payid_request_failed">PayID-Anfrage ist fehlgeschlagen</string>
|
||||
<string name="send_error_payid_verification_failed">Verifikation der PayString ist fehlgeschlagen</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString wird von der Blockchain nicht unterstützt</string>
|
||||
<string name="send_error_payid_not_registered">PayString ist nicht registriert</string>
|
||||
<string name="send_error_payid_request_failed">PayString-Anfrage ist fehlgeschlagen</string>
|
||||
|
||||
<string name="send_error_address_same_as_wallet">Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein</string>
|
||||
<string name="send_error_insufficient_balance">Nicht genug Geld</string>
|
||||
|
|
@ -182,7 +182,7 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS
|
|||
<string name="wallet_notification_address_copied">Die Adresse wurde erfolgreich kopiert</string>
|
||||
|
||||
|
||||
<string name="wallet_create_payid_empty">Zuerst geben Sie die gewünschte PayID ein</string>
|
||||
<string name="wallet_create_payid_empty">Zuerst geben Sie die gewünschte PayString ein</string>
|
||||
|
||||
<string name="initial_message_scan_header">Legen Sie die Karte zum Scannen an</string>
|
||||
<string name="initial_message_tap_header">Legen Sie die Karte an</string>
|
||||
|
|
|
|||
|
|
@ -41,18 +41,18 @@
|
|||
<string name="wallet_pending_tx_receiving">Réception \u0020</string>
|
||||
<string name="wallet_pending_tx_receiving_address_format">de %s </string>
|
||||
<string name="wallet_address_button_explore">Explorer l\'adresse </string>
|
||||
<string name="wallet_address_button_create_payid">Créer PayID</string>
|
||||
<string name="wallet_address_button_create_payid">Créer PayString</string>
|
||||
<string name="wallet_qr_title_format">Portefeuille %s</string>
|
||||
<string name="wallet_create_payid">Créer PayID</string>
|
||||
<string name="wallet_create_payid">Créer PayString</string>
|
||||
<string name="wallet_create_payid_card_format">Carte: %s</string>
|
||||
<string name="wallet_create_payid_hint">Dénomination PayID</string>
|
||||
<string name="wallet_create_payid_hint">Dénomination PayString</string>
|
||||
<string name="wallet_create_payid_domain">$payid.tangem.com</string>
|
||||
<string name="wallet_create_payid_button_title">Créer </string>
|
||||
<string name="wallet_create_payid_info">Votre PayID est une information qui vous est propre, comme un numéro de téléphone, une adresse du courrier électronique ou un ABN.</string>
|
||||
<string name="wallet_create_payid_success_message">PayID a été créé avec succès et copié dans le presse-papiers </string>
|
||||
<string name="wallet_create_payid_error_message">Mauvaise réponse lors de la création de PayID </string>
|
||||
<string name="wallet_create_payid_error_already_created">Ce PayID existe déjà. Essayez une autre variante.</string>
|
||||
<string name="send_destination_hint_address_payid">Adresse ou PayID</string>
|
||||
<string name="wallet_create_payid_info">Votre PayString est une information qui vous est propre, comme un numéro de téléphone, une adresse du courrier électronique ou un ABN.</string>
|
||||
<string name="wallet_create_payid_success_message">PayString a été créé avec succès et copié dans le presse-papiers </string>
|
||||
<string name="wallet_create_payid_error_message">Mauvaise réponse lors de la création de PayString </string>
|
||||
<string name="wallet_create_payid_error_already_created">Ce PayString existe déjà. Essayez une autre variante.</string>
|
||||
<string name="send_destination_hint_address_payid">Adresse ou PayString</string>
|
||||
<string name="send_destination_hint_address">Adresse</string>
|
||||
<string name="send_title">Envoyer </string>
|
||||
<string name="send_network_fee_title">Commissions du réseau </string>
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
<string name="initial_message_create_wallet_body">Touchez, pour créer un portefeuille </string>
|
||||
<string name="initial_message_change_access_code_body">Touchez, pour modifier le code d’accès </string>
|
||||
<string name="initial_message_change_passcode_body">Touchez, pour modifier le mot de passe </string>
|
||||
<string name="payid_service_error_loading">PayID incorrect </string>
|
||||
<string name="payid_service_error_loading">PayString incorrect </string>
|
||||
|
||||
<string name="disclaimer_text">
|
||||
|
||||
|
|
@ -165,10 +165,10 @@ Avertissement
|
|||
<string name="send_error_fee_request_failed">Échec de réception des commissions</string>
|
||||
|
||||
<string name="send_error_unknown">Erreur inconnue</string>
|
||||
<string name="send_error_payid_verification_failed">La vérification de PayID a échoué</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayID non pris en charge par la blockchain</string>
|
||||
<string name="send_error_payid_not_registered">PayID non enregistré</string>
|
||||
<string name="send_error_payid_request_failed">La demande de PayID a échoué</string>
|
||||
<string name="send_error_payid_verification_failed">La vérification de PayString a échoué</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString non pris en charge par la blockchain</string>
|
||||
<string name="send_error_payid_not_registered">PayString non enregistré</string>
|
||||
<string name="send_error_payid_request_failed">La demande de PayString a échoué</string>
|
||||
<string name="send_error_address_same_as_wallet">L’adresse est la même que celle de votre portefeuille</string>
|
||||
<string name="send_error_insufficient_balance">Solde insuffisant</string>
|
||||
<string name="send_error_blockchain_internal">Erreur interne de la blockchain</string>
|
||||
|
|
@ -176,7 +176,7 @@ Avertissement
|
|||
<string name="send_error_dust_amount_format">Le montant minimal est de %ы</string>
|
||||
<string name="send_error_dust_change">Le reste est trop petit</string>
|
||||
<string name="wallet_notification_address_copied">L’adresse a été copiée avec succès</string>
|
||||
<string name="wallet_create_payid_empty">Saisissez d’abord le PayID requis</string>
|
||||
<string name="wallet_create_payid_empty">Saisissez d’abord le PayString requis</string>
|
||||
<string name="initial_message_scan_header">Posez pour scanner</string>
|
||||
<string name="initial_message_tap_header">Posez la carte</string>
|
||||
<string name="details_notification_erase_wallet_not_possible">Votre solde sur ce portefeuille n\'est pas nul, ou vous avez des transactions non confirmées. Impossible de supprimer la fonction du portefeuille</string>
|
||||
|
|
|
|||
|
|
@ -39,18 +39,18 @@
|
|||
<string name="wallet_pending_tx_receiving">Ricevi\u0020</string>
|
||||
<string name="wallet_pending_tx_receiving_address_format"> da %s </string>
|
||||
<string name="wallet_address_button_explore">Cerca indirizzo</string>
|
||||
<string name="wallet_address_button_create_payid">Crea PayID</string>
|
||||
<string name="wallet_address_button_create_payid">Crea PayString</string>
|
||||
<string name="wallet_qr_title_format">portafoglio %s</string>
|
||||
<string name="wallet_create_payid">Crea PayID</string>
|
||||
<string name="wallet_create_payid">Crea PayString</string>
|
||||
<string name="wallet_create_payid_card_format">Scheda: %s</string>
|
||||
<string name="wallet_create_payid_hint">Nome PayID</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>
|
||||
<string name="wallet_create_payid_info">Il tuo PayID – informazioni per te uniche, come il tuo numero di telefono, indirizzo email o ABN.</string>
|
||||
<string name="wallet_create_payid_success_message">PayID creato con successo e copiato negli appunti</string>
|
||||
<string name="wallet_create_payid_error_message">Errore di risposta durante la creazione del PayID</string>
|
||||
<string name="wallet_create_payid_error_already_created">Questo PayID esiste già. Scegline un altro.</string>
|
||||
<string name="send_destination_hint_address_payid">Indirizzo o PayID</string>
|
||||
<string name="wallet_create_payid_info">Il tuo PayString – informazioni per te uniche, come il tuo numero di telefono, indirizzo email o ABN.</string>
|
||||
<string name="wallet_create_payid_success_message">PayString creato con successo e copiato negli appunti</string>
|
||||
<string name="wallet_create_payid_error_message">Errore di risposta durante la creazione del PayString</string>
|
||||
<string name="wallet_create_payid_error_already_created">Questo PayString esiste già. Scegline un altro.</string>
|
||||
<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>
|
||||
|
|
@ -102,7 +102,7 @@
|
|||
<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>
|
||||
<string name="initial_message_change_passcode_body">Avvicina per modificare la password</string>
|
||||
<string name="payid_service_error_loading">PayID non valido</string>
|
||||
<string name="payid_service_error_loading">PayString non valido</string>
|
||||
|
||||
<string name="disclaimer_text">
|
||||
|
||||
|
|
@ -166,10 +166,10 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020.
|
|||
<string name="wallet_notification_no_internet">Nessuna connessione a Internet</string>
|
||||
|
||||
<string name="send_error_unknown">Errore sconosciuto</string>
|
||||
<string name="send_error_payid_verification_failed">Verifica del PayID fallita</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayID non supportato dalla blockchain</string>
|
||||
<string name="send_error_payid_not_registered">PayID non registrato</string>
|
||||
<string name="send_error_payid_request_failed">Richiesta PayID fallita</string>
|
||||
<string name="send_error_payid_verification_failed">Verifica del PayString fallita</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString non supportato dalla blockchain</string>
|
||||
<string name="send_error_payid_not_registered">PayString non registrato</string>
|
||||
<string name="send_error_payid_request_failed">Richiesta PayString fallita</string>
|
||||
|
||||
<string name="send_error_address_same_as_wallet">L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio</string>
|
||||
<string name="send_error_insufficient_balance">Saldo insufficiente</string>
|
||||
|
|
@ -181,7 +181,7 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020.
|
|||
<string name="wallet_notification_address_copied">L\'indirizzo è stato copiato con successo</string>
|
||||
|
||||
|
||||
<string name="wallet_create_payid_empty">Inserire prima il PayID desiderato</string>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
<color name="buttonGray">#F4F5F6</color>
|
||||
<color name="textGray">#DE000000</color>
|
||||
|
||||
<color name="twins_dark">#14181D</color>
|
||||
|
||||
|
||||
<color name="lightGray1">#F8F8FB</color>
|
||||
<color name="lightGray4">#C7C7CC</color>
|
||||
|
|
@ -39,5 +41,7 @@
|
|||
<color name="blue1">#0029FF</color>
|
||||
<color name="blue2">#1F50FF</color>
|
||||
<color name="blue3">#CBE4FF</color>
|
||||
<color name="blue_pale">#E0E6FA</color>
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
@ -33,5 +33,6 @@
|
|||
<dimen name="btn_rounded_size">44dp</dimen>
|
||||
|
||||
<dimen name="text_size_amount_to_send">32sp</dimen>
|
||||
<dimen name="dimen16">16dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -49,20 +49,20 @@
|
|||
<string name="wallet_pending_tx_sending">Sending\u0020</string>
|
||||
|
||||
<string name="wallet_address_button_explore">Explore address</string>
|
||||
<string name="wallet_address_button_create_payid">Create PayID</string>
|
||||
<string name="wallet_address_button_create_payid">Create PayString</string>
|
||||
<string name="wallet_qr_title_format">%s wallet</string>
|
||||
|
||||
<string name="wallet_create_payid">Create PayID</string>
|
||||
<string name="wallet_create_payid">Create PayString</string>
|
||||
<string name="wallet_create_payid_card_format">Card: %s</string>
|
||||
<string name="wallet_create_payid_hint">PayID name</string>
|
||||
<string name="wallet_create_payid_hint">PayString name</string>
|
||||
<string name="wallet_create_payid_domain">$payid.tangem.com</string>
|
||||
<string name="wallet_create_payid_button_title">Create</string>
|
||||
<string name="wallet_create_payid_info">Your PayID is information unique to you, like your phone number, email or ABN.</string>
|
||||
<string name="wallet_create_payid_success_message">PayID created successfully and copied to clipboard</string>
|
||||
<string name="wallet_create_payid_error_message">Error response while creating PayID</string>
|
||||
<string name="wallet_create_payid_error_already_created">This PayID already exists. Try a different one.</string>
|
||||
<string name="wallet_create_payid_info">Your PayString is information unique to you, like your phone number, email or ABN.</string>
|
||||
<string name="wallet_create_payid_success_message">PayString created successfully and copied to clipboard</string>
|
||||
<string name="wallet_create_payid_error_message">Error response while creating PayString</string>
|
||||
<string name="wallet_create_payid_error_already_created">This PayString already exists. Try a different one.</string>
|
||||
|
||||
<string name="send_destination_hint_address_payid">Address or PayID</string>
|
||||
<string name="send_destination_hint_address_payid">Address or PayString</string>
|
||||
<string name="send_destination_hint_address">Address</string>
|
||||
<string name="send_title">Send</string>
|
||||
<string name="send_network_fee_title">Network fee</string>
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
<string name="initial_message_change_access_code_body">Tap to change the access code</string>
|
||||
<string name="initial_message_change_passcode_body">Tap to change the passcode</string>
|
||||
|
||||
<string name="payid_service_error_loading">Invalid PayID</string>
|
||||
<string name="payid_service_error_loading">Invalid PayString</string>
|
||||
|
||||
<string name="disclaimer_text">Legal Disclaimer
|
||||
\n
|
||||
|
|
@ -177,10 +177,10 @@
|
|||
<string name="wallet_notification_no_internet">No internet connection</string>
|
||||
|
||||
<string name="send_error_unknown">Unknown error</string>
|
||||
<string name="send_error_payid_verification_failed">PayID verification failed</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayID unsupported by blockchain</string>
|
||||
<string name="send_error_payid_not_registered">PayID not registered</string>
|
||||
<string name="send_error_payid_request_failed">PayID request failed</string>
|
||||
<string name="send_error_payid_verification_failed">PayString verification failed</string>
|
||||
<string name="send_error_payid_unsupported_by_blockchain">PayString unsupported by blockchain</string>
|
||||
<string name="send_error_payid_not_registered">PayString not registered</string>
|
||||
<string name="send_error_payid_request_failed">PayString request failed</string>
|
||||
|
||||
<string name="send_error_address_same_as_wallet">Address is the same as wallet address</string>
|
||||
<string name="send_error_insufficient_balance">Insufficient balance</string>
|
||||
|
|
@ -192,20 +192,16 @@
|
|||
<string name="wallet_notification_address_copied">Address was copied to clipboard</string>
|
||||
|
||||
|
||||
<string name="wallet_create_payid_empty">Enter desired PayID first</string>
|
||||
<string name="wallet_create_payid_empty">Enter desired PayString first</string>
|
||||
|
||||
<string name="initial_message_scan_header">Tap to scan</string>
|
||||
<string name="initial_message_tap_header">Tap the card</string>
|
||||
|
||||
<string name="details_notification_erase_wallet_not_possible">You balance on this wallet is not zero, or you have unconfirmed transactions</string>
|
||||
<string name="details_notification_erase_wallet_not_possible">Your balance on this wallet is not zero, or you have unconfirmed transactions</string>
|
||||
<string name="details_notification_security_option_already_active">This is the currently active option</string>
|
||||
|
||||
|
||||
<string name="xtz_withdrawal_message_warning">To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ</string>
|
||||
<string name="xtz_withdrawal_message_reduce">Reduce by %s XTZ</string>
|
||||
<string name="xtz_withdrawal_message_ignore">No, send all</string>
|
||||
|
||||
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
@ -1,3 +1,36 @@
|
|||
<resources>
|
||||
<string name="wallet_button_topup" translatable="false">Top Up</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>
|
||||
|
||||
<string name="wallet_button_create_twin_wallet" translatable="false">Create twin wallet</string>
|
||||
<string name="wallet_error_empty_twin_card" translatable="false">Create twin wallet</string>
|
||||
<string name="wallet_error_empty_twin_card_subtitle" translatable="false"> Generate wallet keys on both cards to start using your Twins</string>
|
||||
|
||||
<string name="twins_onboarding_title" translatable="false">Tangem Twin</string>
|
||||
<string name="twins_onboarding_subtitle" translatable="false">One wallet. Two cards.</string>
|
||||
<string name="twins_onboarding_description_format" translatable="false">This one that you are holding in your hands
|
||||
and the other one with number %s.\n\nBoth cards can be used to extract funds from
|
||||
this wallet.
|
||||
</string>
|
||||
|
||||
<string name="wallet_twins_chip_format" translatable="false">Card %s of 2</string>
|
||||
|
||||
<string name="details_row_title_twins_recreate" translatable="false">Re-create twin wallet</string>
|
||||
<string name="details_twins_recreate_warning" translatable="false">This action is irreversible. You will not have access to the old wallet.\nThe wallet re-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_toolbar" translatable="false">Re-create wallet</string>
|
||||
<string name="details_twins_recreate_step_format" translatable="false">Step %s</string>
|
||||
<string name="details_twins_recreate_title_format" translatable="false">Tap the #%s twin card</string>
|
||||
<string name="details_twins_recreate_title_preparing" translatable="false">Preparing card</string>
|
||||
<string name="details_twins_recreate_title_creating_wallet" translatable="false">Creating wallet</string>
|
||||
<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="notification_twins_recreate_success" translatable="false">The twin address was successfully created</string>
|
||||
|
||||
<string name="common_start" translatable="false">Start</string>
|
||||
<string name="common_back" translatable="false">Back</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
<item name="android:backgroundTint">@color/selector_btn_black</item>
|
||||
</style>
|
||||
|
||||
<style name="ChipNetworkFee" parent="@style/Widget.MaterialComponents.Chip.Choice">
|
||||
<style name="TapChip" parent="@style/Widget.MaterialComponents.Chip.Choice">
|
||||
<item name="chipBackgroundColor">@color/selector_chip_background</item>
|
||||
<item name="chipStrokeColor">@color/selector_chip_stroke</item>
|
||||
<item name="chipStrokeWidth">1dp</item>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ext.versions = [
|
||||
kotlin : '1.4.0',
|
||||
build_gradle: '4.1.0',
|
||||
kotlin : '1.4.10',
|
||||
build_gradle: '4.1.1',
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue