Updated on 2026-08-14
This commit is contained in:
commit
6493949b5f
133 changed files with 4409 additions and 1122 deletions
3
.idea/codeStyles/Project.xml
generated
3
.idea/codeStyles/Project.xml
generated
|
|
@ -13,6 +13,9 @@
|
|||
<package name="io.ktor" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="LINE_BREAK_AFTER_MULTILINE_WHEN_ENTRY" value="false" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="5" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="3" />
|
||||
<option name="ALLOW_TRAILING_COMMA" value="true" />
|
||||
<option name="BLANK_LINES_BEFORE_DECLARATION_WITH_COMMENT_OR_ANNOTATION_ON_SEPARATE_LINE" value="0" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ dependencies {
|
|||
|
||||
/** Features */
|
||||
implementation(project(":features:onboarding"))
|
||||
implementation(project(":features:learn2earn:api"))
|
||||
implementation(project(":features:learn2earn:impl"))
|
||||
implementation(project(":features:referral:presentation"))
|
||||
implementation(project(":features:referral:domain"))
|
||||
implementation(project(":features:referral:data"))
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
android:largeHeap="@bool/largeHeap"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:extractNativeLibs="true"
|
||||
android:supportsRtl="true"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:allowBackup, android:fullBackupContent">
|
||||
|
|
@ -138,6 +139,10 @@
|
|||
android:name="com.tangem.tap.features.sprinklr.ui.SprinklrActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name="com.tangem.feature.learn2earn.presentation.webView.Learn2earnWebViewActivity"
|
||||
android:theme="@style/Theme.MaterialComponents.Light.NoActionBar" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
|
|
|
|||
|
|
@ -474,6 +474,17 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "telos",
|
||||
"symbol": "TLOS",
|
||||
"name": "Telos EVM",
|
||||
"networks":
|
||||
[
|
||||
{
|
||||
"networkId": "telos/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ravencoin",
|
||||
"symbol": "RVN",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
|||
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
|
||||
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.features.intentHandler.IntentProcessor
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.BuyCurrencyIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.SellCurrencyIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.shop.redux.ShopAction
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
|
|
@ -45,6 +50,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.ref.WeakReference
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
|
@ -95,6 +101,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var walletConnectInteractor: WalletConnectInteractor
|
||||
|
||||
// TODO: fixme: inject through DI
|
||||
private val intentProcessor: IntentProcessor = IntentProcessor()
|
||||
|
||||
private var snackbar: Snackbar? = null
|
||||
private val dialogManager = DialogManager()
|
||||
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
||||
|
|
@ -117,6 +126,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
lockUserWalletsTimer = LockUserWalletsTimer(owner = this)
|
||||
|
||||
initUserWalletsListManager()
|
||||
initIntentHandlers()
|
||||
|
||||
store.dispatch(
|
||||
ShopAction.CheckIfGooglePayAvailable(
|
||||
|
|
@ -133,6 +143,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
)
|
||||
}
|
||||
|
||||
private fun initIntentHandlers() {
|
||||
val hasSavedWalletsProvider = { store.state.globalState.userWalletsListManager?.hasUserWallets == true }
|
||||
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider))
|
||||
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
|
||||
intentProcessor.addHandler(BuyCurrencyIntentHandler())
|
||||
intentProcessor.addHandler(SellCurrencyIntentHandler())
|
||||
}
|
||||
|
||||
private fun initUserWalletsListManager() {
|
||||
val manager = if (preferencesStorage.shouldSaveUserWallets) {
|
||||
UserWalletsListManager.provideBiometricImplementation(
|
||||
|
|
@ -164,12 +182,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
super.onResume()
|
||||
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
|
||||
|
||||
navigateToInitialScreenIfNeeded(intent)
|
||||
navigateToInitialScreenIfNeededOnResume(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
intentHandler.handleIntent(intent, userWalletsListManager.hasUserWallets)
|
||||
scope.launch {
|
||||
intentProcessor.handleIntent(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
@ -185,6 +205,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
override fun onDestroy() {
|
||||
store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this)))
|
||||
intentProcessor.removeAll()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
|
@ -235,29 +256,39 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
lockUserWalletsTimer?.restart()
|
||||
}
|
||||
|
||||
private fun navigateToInitialScreenIfNeeded(intent: Intent?) {
|
||||
private fun navigateToInitialScreenIfNeededOnResume(intentWhichStartedActivity: Intent?) {
|
||||
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
|
||||
val isNotScannedBefore = store.state.globalState.scanResponse == null
|
||||
val isOnboardingServiceNotActive = store.state.globalState.onboardingState.onboardingStarted
|
||||
val isShopNotOpened = store.state.shopState.total != null
|
||||
when {
|
||||
!backStackIsEmpty && isNotScannedBefore && isOnboardingServiceNotActive && isShopNotOpened -> {
|
||||
navigateToInitialScreen(intent)
|
||||
navigateToInitialScreenOnResume(intentWhichStartedActivity)
|
||||
}
|
||||
backStackIsEmpty -> {
|
||||
navigateToInitialScreen(intent)
|
||||
navigateToInitialScreenOnResume(intentWhichStartedActivity)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToInitialScreen(intent: Intent?) {
|
||||
private fun navigateToInitialScreenOnResume(intentWhichStartedActivity: Intent?) {
|
||||
if (store.state.globalState.userWalletsListManager?.hasUserWallets == true) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome))
|
||||
store.dispatchOnMain(WelcomeAction.HandleIntentIfNeeded(intent))
|
||||
store.dispatchOnMain(WelcomeAction.SetInitialIntent(intentWhichStartedActivity))
|
||||
scope.launch {
|
||||
val handler = BackgroundScanIntentHandler(hasSavedUserWalletsProvider = { true })
|
||||
val isBackgroundScanNotHandled = handler.handleIntent(intentWhichStartedActivity)
|
||||
val hasNotIncompletedBackup = !backupService.hasIncompletedBackup
|
||||
if (isBackgroundScanNotHandled && hasNotIncompletedBackup) {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
intentHandler.handleIntent(intent, hasSavedUserWallets = false)
|
||||
scope.launch {
|
||||
intentProcessor.handleIntent(intentWhichStartedActivity)
|
||||
}
|
||||
}
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,67 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.app.*
|
||||
import android.content.*
|
||||
import android.content.pm.*
|
||||
import coil.*
|
||||
import com.tangem.*
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.network.*
|
||||
import com.tangem.core.analytics.*
|
||||
import com.tangem.core.featuretoggle.manager.*
|
||||
import com.tangem.data.source.preferences.*
|
||||
import com.tangem.datasource.api.common.*
|
||||
import com.tangem.datasource.asset.*
|
||||
import com.tangem.datasource.config.*
|
||||
import com.tangem.datasource.config.models.*
|
||||
import com.tangem.datasource.connection.*
|
||||
import com.tangem.domain.*
|
||||
import com.tangem.domain.common.*
|
||||
import com.tangem.features.wallet.featuretoggles.*
|
||||
import com.tangem.tap.common.*
|
||||
import com.tangem.tap.common.analytics.*
|
||||
import com.tangem.tap.common.analytics.api.*
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.*
|
||||
import com.tangem.tap.common.analytics.handlers.appsFlyer.*
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.*
|
||||
import com.tangem.tap.common.analytics.topup.*
|
||||
import com.tangem.tap.common.chat.*
|
||||
import com.tangem.tap.common.feedback.*
|
||||
import com.tangem.tap.common.images.*
|
||||
import com.tangem.tap.common.log.*
|
||||
import com.tangem.tap.common.redux.*
|
||||
import com.tangem.tap.common.redux.global.*
|
||||
import com.tangem.tap.common.shop.*
|
||||
import com.tangem.tap.domain.configurable.warningMessage.*
|
||||
import com.tangem.tap.domain.tokens.*
|
||||
import com.tangem.tap.domain.totalBalance.*
|
||||
import com.tangem.tap.domain.totalBalance.di.*
|
||||
import com.tangem.tap.domain.walletCurrencies.*
|
||||
import com.tangem.tap.domain.walletCurrencies.di.*
|
||||
import com.tangem.tap.domain.walletStores.*
|
||||
import com.tangem.tap.domain.walletStores.di.*
|
||||
import com.tangem.tap.domain.walletStores.repository.*
|
||||
import com.tangem.tap.domain.walletStores.repository.di.*
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
import com.tangem.Log
|
||||
import com.tangem.LogFormat
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.domain.DomainLayer
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.topup.TopUpController
|
||||
import com.tangem.tap.common.chat.ChatManager
|
||||
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
|
||||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
import com.tangem.tap.common.log.TangemLogCollector
|
||||
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.common.shop.TangemShopService
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator
|
||||
import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
|
||||
import com.tangem.tap.domain.walletCurrencies.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.domain.walletStores.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.*
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.*
|
||||
import com.tangem.tap.proxy.*
|
||||
import com.tangem.tap.proxy.redux.*
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.android.*
|
||||
import kotlinx.coroutines.*
|
||||
import okhttp3.logging.*
|
||||
import org.rekotlin.*
|
||||
import timber.log.*
|
||||
import javax.inject.*
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository
|
||||
|
||||
lateinit var store: Store<AppState>
|
||||
|
|
@ -102,7 +110,6 @@ val walletCurrenciesManager by lazy {
|
|||
val totalFiatBalanceCalculator by lazy {
|
||||
TotalFiatBalanceCalculator.provideDefaultImplementation()
|
||||
}
|
||||
val intentHandler by lazy { IntentHandler() }
|
||||
|
||||
@HiltAndroidApp
|
||||
class TapApplication : Application(), ImageLoaderFactory {
|
||||
|
|
@ -137,6 +144,9 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
@Inject
|
||||
lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var learn2earnInteractor: Learn2earnInteractor
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
|
@ -190,8 +200,11 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
appStateHolder.userTokensRepository = userTokensRepository
|
||||
appStateHolder.walletStoresManager = walletStoresManager
|
||||
|
||||
scope.launch {
|
||||
// TODO: Try to performance and user experience.
|
||||
// [REDACTED_JIRA]
|
||||
runBlocking {
|
||||
featureTogglesManager.init()
|
||||
learn2earnInteractor.init()
|
||||
}
|
||||
|
||||
initTopUpController()
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.removePrefixOrNull
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class IntentHandler {
|
||||
|
||||
private val nfcActions = arrayOf(
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED,
|
||||
NfcAdapter.ACTION_TECH_DISCOVERED,
|
||||
NfcAdapter.ACTION_TAG_DISCOVERED,
|
||||
)
|
||||
|
||||
fun handleIntent(intent: Intent?, hasSavedUserWallets: Boolean) {
|
||||
handleBackgroundScan(intent, hasSavedUserWallets)
|
||||
handleWalletConnectLink(intent)
|
||||
handleBuyCurrencyCallback(intent)
|
||||
handleSellCurrencyCallback(intent)
|
||||
}
|
||||
|
||||
fun handleWalletConnectLink(intent: Intent?) {
|
||||
val wcUri = when (intent?.scheme) {
|
||||
WalletConnectManager.WC_SCHEME -> {
|
||||
intent.data?.toString()
|
||||
}
|
||||
TANGEM_SCHEME -> {
|
||||
intent.data?.toString()?.removePrefixOrNull(TANGEM_WC_PREFIX)
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
if (wcUri != null) {
|
||||
store.dispatch(WalletConnectAction.HandleDeepLink(wcUri))
|
||||
}
|
||||
}
|
||||
|
||||
fun handleBackgroundScan(intent: Intent?, hasSavedUserWallets: Boolean): Boolean {
|
||||
if (intent == null || intent.action !in nfcActions) return false
|
||||
|
||||
val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
||||
}
|
||||
if (tag == null) return false
|
||||
|
||||
intent.action = null
|
||||
if (hasSavedUserWallets) {
|
||||
// TODO: Remove delay after [REDACTED_JIRA]
|
||||
scope.launch {
|
||||
delay(timeMillis = 200)
|
||||
store.dispatch(WelcomeAction.ProceedWithCard)
|
||||
}
|
||||
} else {
|
||||
store.dispatch(HomeAction.ReadCard())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleBuyCurrencyCallback(intent: Intent?) {
|
||||
val data = intent?.data ?: return
|
||||
|
||||
val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL)
|
||||
if (data.host == successUri.host && data.authority == successUri.authority) {
|
||||
val currency = store.state.walletState.selectedCurrency ?: return
|
||||
val currencyType = AnalyticsParam.CurrencyType.Currency(currency)
|
||||
Analytics.send(Token.Bought(currencyType))
|
||||
}
|
||||
}
|
||||
|
||||
fun handleSellCurrencyCallback(intent: Intent?) {
|
||||
try {
|
||||
val transactionID =
|
||||
intent?.data?.getQueryParameter(TRANSACTION_ID_PARAM) ?: return
|
||||
val currency =
|
||||
intent.data?.getQueryParameter(CURRENCY_CODE_PARAM) ?: return
|
||||
val amount =
|
||||
intent.data?.getQueryParameter(CURRENCY_AMOUNT_PARAM) ?: return
|
||||
val destinationAddress =
|
||||
intent.data?.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM)
|
||||
?: return
|
||||
|
||||
Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
|
||||
|
||||
store.dispatch(
|
||||
WalletAction.TradeCryptoAction.SendCrypto(
|
||||
currencyId = currency,
|
||||
amount = amount,
|
||||
destinationAddress = destinationAddress,
|
||||
transactionId = transactionID,
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
Timber.d("Not MoonPay URL")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TRANSACTION_ID_PARAM = "transactionId"
|
||||
private const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
|
||||
private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
|
||||
private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
|
||||
private const val TANGEM_SCHEME = "tangem"
|
||||
private const val TANGEM_WC_PREFIX = "tangem://wc?uri="
|
||||
}
|
||||
}
|
||||
|
|
@ -31,12 +31,14 @@ sealed class Basic(
|
|||
currency: AnalyticsParam.CardCurrency,
|
||||
batch: String,
|
||||
signInType: SignInType,
|
||||
walletsCount: String,
|
||||
) : Basic(
|
||||
event = "Signed in",
|
||||
params = mapOf(
|
||||
AnalyticsParam.CURRENCY to currency.value,
|
||||
AnalyticsParam.BATCH to batch,
|
||||
"Sign in type" to signInType.name,
|
||||
"Wallets Count" to walletsCount,
|
||||
),
|
||||
) {
|
||||
enum class SignInType {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
|||
Blockchain.TerraV1 -> R.drawable.ic_terra_no_color
|
||||
Blockchain.TerraV2 -> R.drawable.ic_terra2_no_color
|
||||
Blockchain.Cronos -> R.drawable.ic_cronos_no_color
|
||||
Blockchain.Telos, Blockchain.TelosTestnet -> R.drawable.ic_telos_no_color
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ interface UserWalletsListManager {
|
|||
* */
|
||||
val hasUserWallets: Boolean
|
||||
|
||||
/**
|
||||
* Count of saved user wallets
|
||||
*/
|
||||
val walletsCount: Int
|
||||
|
||||
/**
|
||||
* Set [UserWallet] with provided [UserWalletId] as selected
|
||||
*
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ internal class BiometricUserWalletsListManager(
|
|||
override val hasUserWallets: Boolean
|
||||
get() = keysRepository.hasSavedEncryptionKeys()
|
||||
|
||||
override val walletsCount: Int
|
||||
get() = state.value.userWallets.size
|
||||
|
||||
override suspend fun unlock(): CompletionResult<UserWallet> {
|
||||
return unlockWithBiometryInternal()
|
||||
.mapFailure { error ->
|
||||
|
|
|
|||
|
|
@ -7,13 +7,7 @@ import com.tangem.tap.domain.model.UserWallet
|
|||
import com.tangem.tap.domain.userWalletList.UserWalletsListError
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.flow.updateAndGet
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
||||
|
|
@ -36,6 +30,12 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
|||
override val hasUserWallets: Boolean
|
||||
get() = state.value.userWallet != null
|
||||
|
||||
/**
|
||||
* only 1 wallet stored in runtime implementation
|
||||
*/
|
||||
override val walletsCount: Int
|
||||
get() = 1
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
state.value.userWallet
|
||||
?.takeIf { it.walletId == userWalletId }
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.tangem.domain.common.util.cardTypesResolver
|
|||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -33,7 +32,6 @@ import com.tangem.tap.features.wallet.redux.WalletState
|
|||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -110,19 +108,23 @@ class WalletConnectMiddleware {
|
|||
)
|
||||
}
|
||||
is WalletConnectAction.OpeningSessionTimeout -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
|
||||
Timber.e("OpeningSessionTimeout for topic ${action.session.topic}")
|
||||
// do not show dialog for now, it shows always to user if cannot establish connection
|
||||
// store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
|
||||
}
|
||||
is WalletConnectAction.FailureEstablishingSession -> {
|
||||
if (action.error != null) {
|
||||
store.dispatch(
|
||||
GlobalAction.ShowDialog(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_warning,
|
||||
messageId = action.error.messageResource,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Timber.e("FailureEstablishingSession for topic ${action.session?.topic}")
|
||||
// disable alerts in release to avoid annoying users
|
||||
// if (action.error != null) {
|
||||
// store.dispatch(
|
||||
// GlobalAction.ShowDialog(
|
||||
// AppDialog.SimpleOkDialogRes(
|
||||
// headerId = R.string.common_warning,
|
||||
// messageId = action.error.messageResource,
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// }
|
||||
if (action.session != null) {
|
||||
walletConnectManager.disconnect(action.session)
|
||||
}
|
||||
|
|
@ -140,11 +142,14 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletConnectAction.RefuseOpeningSession -> {
|
||||
store.dispatch(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.OpeningSessionRejected,
|
||||
),
|
||||
)
|
||||
Timber.e("RefuseOpeningSession")
|
||||
|
||||
// do not show for now to avoid anoying users with alert
|
||||
// store.dispatch(
|
||||
// GlobalAction.ShowDialog(
|
||||
// WalletConnectDialog.OpeningSessionRejected,
|
||||
// ),
|
||||
// )
|
||||
}
|
||||
is WalletConnectAction.ScanCard -> {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
|
|
@ -306,16 +311,18 @@ class WalletConnectMiddleware {
|
|||
)
|
||||
}
|
||||
is WalletConnectError.ExternalApprovalError -> {
|
||||
val message = action.error.message
|
||||
if (!message.isNullOrEmpty()) {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
AppDialog.SimpleOkWarningDialog(
|
||||
message = message,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Timber.e(action.error, "ExternalApprovalError ${action.error.message}")
|
||||
// do not show dialog on this event
|
||||
// val message = action.error.message
|
||||
// if (!message.isNullOrEmpty()) {
|
||||
// store.dispatchOnMain(
|
||||
// GlobalAction.ShowDialog(
|
||||
// AppDialog.SimpleOkWarningDialog(
|
||||
// message = message,
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// }
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,36 +12,47 @@ import androidx.compose.ui.platform.ComposeView
|
|||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.learn2earn.presentation.Learn2earnViewModel
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.home.redux.Stories
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensAction
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
|
||||
|
||||
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
|
||||
|
||||
private val learn2earnViewModel by activityViewModels<Learn2earnViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
store.dispatch(HomeAction.OnCreate)
|
||||
store.dispatch(HomeAction.Init)
|
||||
if (learn2earnViewModel.uiState.storyScreenState.isVisible) {
|
||||
store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo))
|
||||
// re init homeState after inserting learn2earn story
|
||||
homeState = mutableStateOf(store.state.homeState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
return ComposeView(inflater.context).apply {
|
||||
setContent {
|
||||
BackHandler {
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
AppCompatTheme {
|
||||
TangemTheme {
|
||||
BackHandler {
|
||||
requireActivity().finish()
|
||||
}
|
||||
ScreenContent()
|
||||
}
|
||||
}
|
||||
|
|
@ -79,7 +90,8 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
|
|||
@Composable
|
||||
private fun ScreenContent() {
|
||||
StoriesScreen(
|
||||
homeState,
|
||||
homeState = homeState,
|
||||
onLearn2earnClick = learn2earnViewModel.uiState.storyScreenState.onClick,
|
||||
onScanButtonClick = {
|
||||
Analytics.send(IntroductionProcess.ButtonScanCard())
|
||||
store.dispatch(HomeAction.ReadCard())
|
||||
|
|
|
|||
|
|
@ -5,29 +5,11 @@ package com.tangem.tap.features.home.compose
|
|||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
|
|
@ -43,38 +25,40 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.tangem.tap.features.home.compose.content.FirstStoriesContent
|
||||
import com.tangem.tap.features.home.compose.content.StoriesCurrencies
|
||||
import com.tangem.tap.features.home.compose.content.StoriesRevolutionaryWallet
|
||||
import com.tangem.tap.features.home.compose.content.StoriesUltraSecureBackup
|
||||
import com.tangem.tap.features.home.compose.content.StoriesWalletForEveryone
|
||||
import com.tangem.tap.features.home.compose.content.StoriesWeb3
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.learn2earn.presentation.ui.Learn2earnStoriesScreen
|
||||
import com.tangem.tap.features.home.compose.content.*
|
||||
import com.tangem.tap.features.home.compose.views.HomeButtons
|
||||
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.home.redux.Stories
|
||||
import com.tangem.wallet.R
|
||||
import kotlin.math.max
|
||||
|
||||
private const val STEPS = 6
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
@Composable
|
||||
fun StoriesScreen(
|
||||
homeState: MutableState<HomeState>,
|
||||
onLearn2earnClick: () -> Unit,
|
||||
onScanButtonClick: () -> Unit,
|
||||
onShopButtonClick: () -> Unit,
|
||||
onSearchTokensClick: () -> Unit,
|
||||
) {
|
||||
val currentStep = remember { mutableStateOf(1) }
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val state = homeState.value
|
||||
|
||||
val isDarkBackground = currentStep.value !in 3..5
|
||||
var currentStory by remember { mutableStateOf(state.firstStory) }
|
||||
val currentStep = { state.stepOf(currentStory) }
|
||||
|
||||
val goToPreviousScreen = {
|
||||
currentStep.value = max(1, currentStep.value - 1)
|
||||
currentStory = state.stories[max(0, currentStep() - 1)]
|
||||
}
|
||||
val goToNextScreen = {
|
||||
currentStep.value = if (currentStep.value < STEPS) currentStep.value + 1 else 1
|
||||
currentStory = if (currentStep() < state.stories.lastIndex) {
|
||||
state.stories[currentStep() + 1]
|
||||
} else {
|
||||
state.firstStory
|
||||
}
|
||||
}
|
||||
|
||||
val isPressed = remember { mutableStateOf(false) }
|
||||
|
|
@ -82,10 +66,10 @@ fun StoriesScreen(
|
|||
|
||||
val hideContent = remember { mutableStateOf(true) }
|
||||
|
||||
LaunchedEffect(key1 = isDarkBackground) {
|
||||
LaunchedEffect(key1 = currentStory.isDarkBackground) {
|
||||
systemUiController.setSystemBarsColor(
|
||||
color = Color.Transparent,
|
||||
darkIcons = !isDarkBackground,
|
||||
darkIcons = !currentStory.isDarkBackground,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +118,7 @@ fun StoriesScreen(
|
|||
},
|
||||
)
|
||||
}
|
||||
if (!isDarkBackground) {
|
||||
if (!currentStory.isDarkBackground) {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
painter = painterResource(id = R.drawable.ic_overlay),
|
||||
|
|
@ -154,9 +138,9 @@ fun StoriesScreen(
|
|||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
StoriesProgressBar(
|
||||
steps = STEPS,
|
||||
currentStep = currentStep.value,
|
||||
stepDuration = currentStep.duration(),
|
||||
steps = state.stories.lastIndex,
|
||||
currentStep = currentStep(),
|
||||
stepDuration = currentStory.duration,
|
||||
paused = isPaused,
|
||||
onStepFinish = goToNextScreen,
|
||||
)
|
||||
|
|
@ -169,15 +153,16 @@ fun StoriesScreen(
|
|||
.height(17.dp)
|
||||
.alpha(if (hideContent.value) 0f else 1f)
|
||||
.align(Alignment.Start),
|
||||
colorFilter = if (isDarkBackground) null else ColorFilter.tint(Color.Black),
|
||||
colorFilter = if (currentStory.isDarkBackground) null else ColorFilter.tint(Color.Black),
|
||||
)
|
||||
when (currentStep.value) {
|
||||
1 -> FirstStoriesContent(isPaused, currentStep.duration()) { hideContent.value = it }
|
||||
2 -> StoriesRevolutionaryWallet(currentStep.duration())
|
||||
3 -> StoriesUltraSecureBackup(isPaused, currentStep.duration())
|
||||
4 -> StoriesCurrencies(isPaused, currentStep.duration())
|
||||
5 -> StoriesWeb3(isPaused, currentStep.duration())
|
||||
6 -> StoriesWalletForEveryone(currentStep.duration())
|
||||
when (currentStory) {
|
||||
Stories.OneInchPromo -> Learn2earnStoriesScreen(onLearn2earnClick)
|
||||
Stories.TangemIntro -> FirstStoriesContent(isPaused, currentStory.duration) { hideContent.value = it }
|
||||
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet(currentStory.duration)
|
||||
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(isPaused, currentStory.duration)
|
||||
Stories.Currencies -> StoriesCurrencies(isPaused, currentStory.duration)
|
||||
Stories.Web3 -> StoriesWeb3(isPaused, currentStory.duration)
|
||||
Stories.WalletForEveryone -> StoriesWalletForEveryone(currentStory.duration)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
|
|
@ -186,7 +171,7 @@ fun StoriesScreen(
|
|||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
if (currentStep.value == 4) {
|
||||
if (currentStory == Stories.Currencies) {
|
||||
Button(
|
||||
onClick = onSearchTokensClick,
|
||||
modifier = Modifier
|
||||
|
|
@ -210,29 +195,31 @@ fun StoriesScreen(
|
|||
)
|
||||
}
|
||||
}
|
||||
HomeButtons(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, top = 0.dp, end = 16.dp, bottom = 37.dp)
|
||||
.fillMaxWidth(),
|
||||
isDarkBackground = isDarkBackground,
|
||||
btnScanStateInProgress = homeState.value.btnScanStateInProgress,
|
||||
onScanButtonClick = onScanButtonClick,
|
||||
onShopButtonClick = onShopButtonClick,
|
||||
)
|
||||
|
||||
if (currentStory != Stories.OneInchPromo) {
|
||||
HomeButtons(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.size16,
|
||||
end = TangemTheme.dimens.size16,
|
||||
bottom = TangemTheme.dimens.size36,
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
isDarkBackground = currentStory.isDarkBackground,
|
||||
btnScanStateInProgress = homeState.value.btnScanStateInProgress,
|
||||
onScanButtonClick = onScanButtonClick,
|
||||
onShopButtonClick = onShopButtonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun MutableState<Int>.duration(): Int = when (this.value) {
|
||||
1 -> 8000
|
||||
else -> 6000
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun StoriesScreenPreview() {
|
||||
StoriesScreen(
|
||||
onLearn2earnClick = {},
|
||||
onScanButtonClick = {},
|
||||
onShopButtonClick = {},
|
||||
onSearchTokensClick = {},
|
||||
|
|
|
|||
|
|
@ -4,12 +4,7 @@ import androidx.compose.animation.core.Animatable
|
|||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -52,7 +47,7 @@ fun StoriesProgressBar(
|
|||
// .height()
|
||||
.padding(start = 9.dp, end = 9.dp, top = 16.dp),
|
||||
) {
|
||||
for (index in 1..steps) {
|
||||
for (index in 0..steps) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(2.dp)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ sealed class HomeAction : Action {
|
|||
object OnCreate : HomeAction()
|
||||
object Init : HomeAction()
|
||||
|
||||
data class InsertStory(val position: Int, val story: Stories) : HomeAction()
|
||||
|
||||
data class ReadCard(
|
||||
val analyticsEvent: AnalyticsEvent? = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Introduction),
|
||||
) : HomeAction()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@ private fun internalReduce(action: Action, state: AppState): HomeState {
|
|||
|
||||
var state = state.homeState
|
||||
when (action) {
|
||||
is HomeAction.InsertStory -> {
|
||||
state = state.copy(
|
||||
stories = state.stories.toMutableList().apply {
|
||||
add(action.position, action.story)
|
||||
},
|
||||
)
|
||||
}
|
||||
is HomeAction.ScanInProgress -> {
|
||||
state = state.copy(scanInProgress = action.scanInProgress)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,38 @@ import org.rekotlin.StateType
|
|||
data class HomeState(
|
||||
val scanInProgress: Boolean = false,
|
||||
val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED),
|
||||
val stories: List<Stories> = initDefaultStories(),
|
||||
) : StateType {
|
||||
|
||||
val firstStory: Stories
|
||||
get() = stories[0]
|
||||
|
||||
val btnScanStateInProgress: Boolean
|
||||
get() = btnScanState.progressState == ProgressState.Loading
|
||||
|
||||
fun stepOf(story: Stories): Int = stories.indexOf(story)
|
||||
|
||||
companion object {
|
||||
fun initDefaultStories(): List<Stories> = listOf(
|
||||
Stories.TangemIntro,
|
||||
Stories.RevolutionaryWallet,
|
||||
Stories.UltraSecureBackup,
|
||||
Stories.Currencies,
|
||||
Stories.Web3,
|
||||
Stories.WalletForEveryone,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Stories(
|
||||
val isDarkBackground: Boolean,
|
||||
val duration: Int,
|
||||
) {
|
||||
object OneInchPromo : Stories(true, duration = 8000)
|
||||
object TangemIntro : Stories(true, duration = 8000)
|
||||
object RevolutionaryWallet : Stories(true, duration = 6000)
|
||||
object UltraSecureBackup : Stories(false, duration = 6000)
|
||||
object Currencies : Stories(false, duration = 6000)
|
||||
object Web3 : Stories(false, duration = 6000)
|
||||
object WalletForEveryone : Stories(true, duration = 6000)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.tap.features.intentHandler
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface IntentHandler {
|
||||
suspend fun handleIntent(intent: Intent?): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.tap.features.intentHandler
|
||||
|
||||
import android.content.Intent
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO: fixme: close it with the combined interfaces IntentHandler and IntentHandlerHolder
|
||||
class IntentProcessor {
|
||||
|
||||
private val intentHandlers = CopyOnWriteArrayList<IntentHandler>()
|
||||
|
||||
fun addHandler(handler: IntentHandler) {
|
||||
intentHandlers.add(handler)
|
||||
}
|
||||
|
||||
fun removeIntentHandler(handler: IntentHandler) {
|
||||
intentHandlers.remove(handler)
|
||||
}
|
||||
|
||||
fun removeAll() {
|
||||
intentHandlers.clear()
|
||||
}
|
||||
|
||||
suspend fun handleIntent(intent: Intent?) {
|
||||
intentHandlers.forEach {
|
||||
it.handleIntent(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BackgroundScanIntentHandler(
|
||||
private val hasSavedUserWalletsProvider: () -> Boolean,
|
||||
) : IntentHandler {
|
||||
|
||||
private val nfcActions = arrayOf(
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED,
|
||||
NfcAdapter.ACTION_TECH_DISCOVERED,
|
||||
NfcAdapter.ACTION_TAG_DISCOVERED,
|
||||
)
|
||||
|
||||
override suspend fun handleIntent(intent: Intent?): Boolean {
|
||||
if (intent == null || intent.action !in nfcActions) return false
|
||||
|
||||
val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
||||
}
|
||||
if (tag == null) return false
|
||||
|
||||
intent.action = null
|
||||
if (hasSavedUserWalletsProvider.invoke()) {
|
||||
// TODO: Remove delay after [REDACTED_JIRA]
|
||||
scope.launch {
|
||||
delay(timeMillis = 200)
|
||||
store.dispatch(WelcomeAction.ProceedWithCard)
|
||||
}
|
||||
} else {
|
||||
store.dispatch(HomeAction.ReadCard())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BuyCurrencyIntentHandler : IntentHandler {
|
||||
|
||||
override suspend fun handleIntent(intent: Intent?): Boolean {
|
||||
val data = intent?.data ?: return false
|
||||
val currency = store.state.walletState.selectedCurrency ?: return false
|
||||
|
||||
val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL)
|
||||
return if (data.host == successUri.host && data.authority == successUri.authority) {
|
||||
val currencyType = AnalyticsParam.CurrencyType.Currency(currency)
|
||||
Analytics.send(Token.Bought(currencyType))
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SellCurrencyIntentHandler : IntentHandler {
|
||||
|
||||
override suspend fun handleIntent(intent: Intent?): Boolean {
|
||||
return try {
|
||||
val intentData = intent?.data ?: return false
|
||||
val transactionID = intentData.getQueryParameter(TRANSACTION_ID_PARAM) ?: return false
|
||||
val currency = intentData.getQueryParameter(CURRENCY_CODE_PARAM) ?: return false
|
||||
val amount = intentData.getQueryParameter(CURRENCY_AMOUNT_PARAM) ?: return false
|
||||
val destinationAddress = intentData.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM) ?: return false
|
||||
|
||||
Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
|
||||
store.dispatch(
|
||||
WalletAction.TradeCryptoAction.SendCrypto(
|
||||
currencyId = currency,
|
||||
amount = amount,
|
||||
destinationAddress = destinationAddress,
|
||||
transactionId = transactionID,
|
||||
),
|
||||
)
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
Timber.d("Not MoonPay URL")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TRANSACTION_ID_PARAM = "transactionId"
|
||||
private const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
|
||||
private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
|
||||
private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.tap.common.extensions.removePrefixOrNull
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WalletConnectLinkIntentHandler : IntentHandler {
|
||||
|
||||
override suspend fun handleIntent(intent: Intent?): Boolean {
|
||||
val intentData = intent?.data ?: return false
|
||||
val scheme = intent.scheme ?: return false
|
||||
|
||||
val wcUri = when (scheme) {
|
||||
WalletConnectManager.WC_SCHEME -> intentData.toString()
|
||||
TANGEM_SCHEME -> intentData.toString().removePrefixOrNull(TANGEM_WC_PREFIX)
|
||||
else -> null
|
||||
}
|
||||
|
||||
return if (wcUri == null) {
|
||||
false
|
||||
} else {
|
||||
store.dispatch(WalletConnectAction.HandleDeepLink(wcUri))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TANGEM_SCHEME = "tangem"
|
||||
private const val TANGEM_WC_PREFIX = "tangem://wc?uri="
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,9 @@ import android.view.View
|
|||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
|
|
@ -22,12 +24,16 @@ import coil.size.Scale
|
|||
import com.badoo.mvicore.modelWatcher
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.OneTouchClickListener
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.feature.learn2earn.presentation.Learn2earnViewModel
|
||||
import com.tangem.feature.learn2earn.presentation.ui.Learn2earnMainPageScreen
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.analytics.events.Portfolio
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -73,6 +79,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<W
|
|||
|
||||
private var walletView: WalletView = MultiWalletView()
|
||||
|
||||
private val learn2earnViewModel by activityViewModels<Learn2earnViewModel>()
|
||||
private val viewModel by viewModels<WalletViewModel>()
|
||||
|
||||
private val totalBalanceWatcher = modelWatcher {
|
||||
|
|
@ -102,6 +109,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<W
|
|||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
learn2earnViewModel.onMainScreenCreated()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
@ -199,6 +207,27 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<W
|
|||
binding.toolbar.setNavigationIcon(
|
||||
if (state.canSaveUserWallets) R.drawable.ic_wallet_24 else R.drawable.ic_tap_card_24,
|
||||
)
|
||||
|
||||
showLearn2earnView()
|
||||
}
|
||||
|
||||
private fun showLearn2earnView() {
|
||||
val isShowing = learn2earnViewModel.uiState.mainScreenState.isVisible
|
||||
if (!isShowing) return
|
||||
|
||||
binding.composeLearnToEarnContainer.show(true) { binding.llWarnings.beginDelayedTransition() }
|
||||
binding.composeLearnToEarnContainer.apply {
|
||||
setViewCompositionStrategy(
|
||||
strategy = ViewCompositionStrategy.DisposeOnLifecycleDestroyed(
|
||||
lifecycle = this@WalletFragment.lifecycle,
|
||||
),
|
||||
)
|
||||
setContent {
|
||||
TangemTheme {
|
||||
Learn2earnMainPageScreen(learn2earnViewModel.uiState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupPullToRefreshLayout(state: WalletState) {
|
||||
|
|
|
|||
|
|
@ -17,16 +17,8 @@ import com.tangem.tap.features.wallet.ui.analytics.WalletAnalyticsEventsMapper
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -71,6 +63,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = signInType,
|
||||
walletsCount = store.state.globalState.userWalletsListManager?.walletsCount.toString(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ internal fun WalletDataModel.getAvailableActions(
|
|||
|
||||
internal fun WalletDataModel.shouldShowMultipleAddress(): Boolean {
|
||||
val listOfAddresses = walletAddresses?.list.orEmpty()
|
||||
return listOfAddresses.size > 1 && currency.blockchain != Blockchain.BitcoinCash
|
||||
return listOfAddresses.size > 1
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.assembleWarnings(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ internal sealed interface WelcomeAction : Action {
|
|||
data class Error(val error: TangemError) : WelcomeAction
|
||||
}
|
||||
|
||||
data class HandleIntentIfNeeded(val intent: Intent?) : WelcomeAction
|
||||
data class SetInitialIntent(val intent: Intent?) : WelcomeAction
|
||||
|
||||
object CloseError : WelcomeAction
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnResult
|
||||
|
|
@ -19,6 +18,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.domain.model.builders.UserWalletBuilder
|
||||
import com.tangem.tap.domain.scanCard.ScanCardProcessor
|
||||
import com.tangem.tap.domain.userWalletList.unlockIfLockable
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.features.signin.redux.SignInAction
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -39,24 +39,10 @@ internal class WelcomeMiddleware {
|
|||
|
||||
private fun handleAction(action: WelcomeAction, state: WelcomeState) {
|
||||
when (action) {
|
||||
is WelcomeAction.ProceedWithBiometrics -> {
|
||||
proceedWithBiometrics(state)
|
||||
}
|
||||
is WelcomeAction.ProceedWithCard -> {
|
||||
proceedWithCard(state)
|
||||
}
|
||||
is WelcomeAction.HandleIntentIfNeeded -> {
|
||||
handleInitialIntent(action.intent)
|
||||
}
|
||||
is WelcomeAction.ClearUserWallets -> {
|
||||
disableUserWalletsSaving()
|
||||
}
|
||||
is WelcomeAction.ProceedWithBiometrics.Error,
|
||||
is WelcomeAction.ProceedWithCard.Error,
|
||||
is WelcomeAction.ProceedWithBiometrics.Success,
|
||||
is WelcomeAction.ProceedWithCard.Success,
|
||||
is WelcomeAction.CloseError,
|
||||
-> Unit
|
||||
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics(state)
|
||||
is WelcomeAction.ProceedWithCard -> proceedWithCard(state)
|
||||
is WelcomeAction.ClearUserWallets -> disableUserWalletsSaving()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +71,9 @@ internal class WelcomeMiddleware {
|
|||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(userWallet = selectedUserWallet)
|
||||
|
||||
intentHandler.handleWalletConnectLink(state.intent)
|
||||
state.intent?.let {
|
||||
WalletConnectLinkIntentHandler().handleIntent(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,20 +92,13 @@ internal class WelcomeMiddleware {
|
|||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success)
|
||||
store.onUserWalletSelected(userWallet = userWallet)
|
||||
|
||||
intentHandler.handleWalletConnectLink(state.intent)
|
||||
state.intent?.let {
|
||||
WalletConnectLinkIntentHandler().handleIntent(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInitialIntent(intent: Intent?) {
|
||||
val isBackgroundScanNotHandled = !intentHandler.handleBackgroundScan(intent, hasSavedUserWallets = true)
|
||||
val hasNotIncompletedBackup = !backupService.hasIncompletedBackup
|
||||
|
||||
if (isBackgroundScanNotHandled && hasNotIncompletedBackup) {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun scanCardInternal(crossinline onCardScanned: suspend (ScanResponse) -> Unit) {
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ internal object WelcomeReducer {
|
|||
|
||||
private fun internalReduce(action: WelcomeAction, state: WelcomeState): WelcomeState {
|
||||
return when (action) {
|
||||
is WelcomeAction.HandleIntentIfNeeded -> state.copy(intent = action.intent)
|
||||
is WelcomeAction.SetInitialIntent -> state.copy(intent = action.intent)
|
||||
is WelcomeAction.ProceedWithBiometrics -> state.copy(isUnlockWithBiometricsInProgress = true)
|
||||
is WelcomeAction.ProceedWithCard -> state.copy(isUnlockWithCardInProgress = true)
|
||||
is WelcomeAction.ProceedWithBiometrics.Error -> state.copy(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.proxy.di
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
|
|
@ -48,4 +50,20 @@ class ProxyModule {
|
|||
appStateHolder = appStateHolder,
|
||||
)
|
||||
}
|
||||
|
||||
// regions FeatureConsumers
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideLear2earnDependencies(appStateHolder: AppStateHolder): Learn2earnDependencyProvider {
|
||||
return object : Learn2earnDependencyProvider {
|
||||
override fun getUserCountryCodeProvider(): () -> String = {
|
||||
appStateHolder.mainStore?.state?.globalState?.userCountryCode ?: Locale.current.language
|
||||
}
|
||||
|
||||
override fun getWebViewAuthCredentialsProvider(): () -> String? = {
|
||||
appStateHolder.mainStore?.state?.globalState?.configManager?.config?.tangemComAuthorization
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion FeatureConsumers
|
||||
}
|
||||
15
app/src/main/res/drawable/ic_telos_no_color.xml
Normal file
15
app/src/main/res/drawable/ic_telos_no_color.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<path
|
||||
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"
|
||||
android:fillColor="#EBEBEB"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M14.862,5.607C14.909,5.486 14.825,5.354 14.695,5.346L12.507,5.2C12.44,5.196 12.375,5.227 12.336,5.283L11.756,6.118C11.72,6.17 11.66,6.201 11.597,6.201L7.745,6.178C7.556,6.177 7.373,6.245 7.231,6.37L6.163,7.308C5.925,7.517 5.839,7.849 5.945,8.147L6.454,9.575C6.457,9.583 6.455,9.591 6.451,9.597C6.165,9.977 5.998,10.434 5.973,10.909L5.964,11.071C5.91,12.08 6.029,13.091 6.316,14.061C6.552,14.859 6.823,15.775 6.989,16.336C7.076,16.632 7.379,16.807 7.679,16.735C8.325,16.58 9.435,16.313 10.309,16.102C11.078,15.917 11.809,15.602 12.472,15.17L12.886,14.901C13.285,14.641 13.597,14.269 13.784,13.831C13.787,13.824 13.793,13.818 13.801,13.817L15.292,13.544C15.603,13.487 15.848,13.246 15.91,12.936L16.188,11.541C16.225,11.356 16.193,11.163 16.097,11L14.288,7.909C14.17,7.706 14.15,7.461 14.234,7.242L14.862,5.607Z"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#B0B0B0"/>
|
||||
</vector>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:text="@string/wallet_button_buy"
|
||||
android:text="@string/common_buy"
|
||||
android:textColor="@color/darkGray3"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:text="@string/wallet_button_sell"
|
||||
android:text="@string/common_sell"
|
||||
android:textColor="@color/darkGray3"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
|
||||
app:title="@string/send_title" />
|
||||
app:title="@string/common_send" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
style="@style/TapPrimaryIconButton"
|
||||
android:layout_width="match_parent"
|
||||
android:fontFamily="@font/saira_semi_condensed_regular"
|
||||
android:text="@string/send_title"
|
||||
android:text="@string/common_send"
|
||||
app:icon="@drawable/ic_arrow_right" />
|
||||
|
||||
<ProgressBar
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@
|
|||
android:id="@+id/cl_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="92dp">
|
||||
|
||||
<ImageView
|
||||
|
|
@ -113,6 +113,12 @@
|
|||
android:paddingTop="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_warning_messages">
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/compose_learn_to_earn_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_wallet_rescan_warning"
|
||||
layout="@layout/layout_wallet_rescan_warning"
|
||||
|
|
@ -219,8 +225,8 @@
|
|||
android:id="@+id/btn_add_token"
|
||||
style="@style/TapPrimaryButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/main_manage_tokens"
|
||||
android:visibility="gone"
|
||||
|
|
|
|||
|
|
@ -25,14 +25,14 @@
|
|||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_buy"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
android:text="@string/wallet_button_buy"
|
||||
android:text="@string/common_buy"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_arrow_up" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_sell"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
android:text="@string/wallet_button_sell"
|
||||
android:text="@string/common_sell"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_arrow_down" />
|
||||
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
style="@style/TapPrimaryIconButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/wallet_button_send"
|
||||
android:text="@string/common_send"
|
||||
app:icon="@drawable/ic_send" />
|
||||
|
||||
</merge>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.datasource.api.promotion
|
||||
|
||||
import com.tangem.datasource.api.promotion.models.*
|
||||
import retrofit2.http.*
|
||||
|
||||
/**
|
||||
*
|
||||
* Promotion API
|
||||
* @see <a href = "https://www.notion.so/tangem/Promotion-Program-API-0907159c3fdb4975aac761be632f44da">Documentation<a/>
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface PromotionApi {
|
||||
|
||||
@Headers("Cache-Control: max-age=3600")
|
||||
@GET("promotion")
|
||||
suspend fun getPromotionInfo(@Query("programName") name: String): PromotionInfoResponse
|
||||
|
||||
@POST("promotion/code/validate")
|
||||
suspend fun validateCode(@Body request: CodeValidateRequestBody): CodeValidateResponse
|
||||
|
||||
@POST("promotion/code/award")
|
||||
suspend fun requestAwardByCode(@Body request: CodeAwardRequestBody): CodeAwardResponse
|
||||
|
||||
@POST("promotion/validate")
|
||||
suspend fun validate(@Body request: ValidateRequestBody): ValidateResponse
|
||||
|
||||
@POST("promotion/award")
|
||||
suspend fun requestAward(@Body request: AwardRequestBody): AwardResponse
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class AbstractPromotionResponse {
|
||||
|
||||
abstract val error: Error?
|
||||
|
||||
fun isError(): Boolean = error != null
|
||||
|
||||
data class Error(
|
||||
@Json(name = "code") val code: Int,
|
||||
@Json(name = "message") val message: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class AwardRequestBody(
|
||||
@Json(name = "walletId") val walletId: String,
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "programName") val programName: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class AwardResponse(
|
||||
@Json(name = "status") val status: Boolean?,
|
||||
@Json(name = "error") override val error: Error? = null,
|
||||
) : AbstractPromotionResponse()
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class CodeAwardRequestBody(
|
||||
@Json(name = "walletId") val walletId: String,
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "code") val code: String?,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class CodeAwardResponse(
|
||||
@Json(name = "status") val status: Boolean?,
|
||||
@Json(name = "error") override val error: Error? = null,
|
||||
) : AbstractPromotionResponse()
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class CodeValidateRequestBody(
|
||||
@Json(name = "walletId") val walletId: String,
|
||||
@Json(name = "code") val code: String?,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class CodeValidateResponse(
|
||||
@Json(name = "valid") val valid: Boolean?,
|
||||
@Json(name = "error") override val error: Error? = null,
|
||||
) : AbstractPromotionResponse()
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class PromotionInfoResponse(
|
||||
@Json(name = "newCard") val newCard: Data?,
|
||||
@Json(name = "oldCard") val oldCard: Data?,
|
||||
@Json(name = "awardPaymentToken") val awardPaymentToken: TokenData?,
|
||||
@Json(name = "error") override val error: Error? = null,
|
||||
) : AbstractPromotionResponse() {
|
||||
|
||||
data class Data(
|
||||
@Json(name = "status") val status: Status,
|
||||
@Json(name = "award") val award: Double,
|
||||
)
|
||||
|
||||
enum class Status(val value: String) {
|
||||
@Json(name = "pending")
|
||||
PENDING("pending"),
|
||||
|
||||
@Json(name = "active")
|
||||
ACTIVE("active"),
|
||||
|
||||
@Json(name = "finished")
|
||||
FINISHED("finished"),
|
||||
}
|
||||
|
||||
data class TokenData(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "symbol") val symbol: String,
|
||||
@Json(name = "active") val active: Boolean,
|
||||
@Json(name = "networkId") val networkId: String,
|
||||
@Json(name = "contractAddress") val contractAddress: String,
|
||||
@Json(name = "decimalCount") val decimalCount: Int,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class ValidateRequestBody(
|
||||
@Json(name = "walletId") val walletId: String,
|
||||
@Json(name = "programName") val programName: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.promotion.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class ValidateResponse(
|
||||
@Json(name = "valid") val valid: Boolean?,
|
||||
@Json(name = "error") override val error: Error? = null,
|
||||
) : AbstractPromotionResponse()
|
||||
|
|
@ -103,6 +103,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
zendesk = configValues.zendesk,
|
||||
swapReferrerAccount = configValues.swapReferrerAccount,
|
||||
walletConnectProjectId = configValues.walletConnectProjectId,
|
||||
tangemComAuthorization = configValues.tangemComAuthorization,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,4 +18,5 @@ data class Config(
|
|||
val zendesk: ZendeskConfig? = null,
|
||||
val swapReferrerAccount: SwapReferrerAccount? = null,
|
||||
val walletConnectProjectId: String = "",
|
||||
val tangemComAuthorization: String? = null,
|
||||
)
|
||||
|
|
@ -37,6 +37,7 @@ class ConfigValueModel(
|
|||
val swapReferrerAccount: SwapReferrerAccount?,
|
||||
val kaspaSecondaryApiUrl: String,
|
||||
val walletConnectProjectId: String,
|
||||
val tangemComAuthorization: String?,
|
||||
)
|
||||
|
||||
data class AppsFlyer(
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@ package com.tangem.datasource.di
|
|||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.paymentology.PaymentologyApi
|
||||
import com.tangem.datasource.api.promotion.PromotionApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.utils.RequestHeader.*
|
||||
import com.tangem.datasource.utils.addHeaders
|
||||
import com.tangem.datasource.utils.allowLogging
|
||||
import com.tangem.lib.auth.AuthProvider
|
||||
import com.tangem.lib.auth.BuildConfig
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -13,6 +16,7 @@ import dagger.hilt.components.SingletonComponent
|
|||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -54,10 +58,37 @@ class NetworkModule {
|
|||
.create(PaymentologyApi::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@PromotionOneInch
|
||||
fun providePromotionOneInchApi(authProvider: AuthProvider, @NetworkMoshi moshi: Moshi): PromotionApi {
|
||||
val okClient = OkHttpClient.Builder()
|
||||
.addHeaders(
|
||||
AuthenticationHeader(authProvider),
|
||||
)
|
||||
.allowLogging()
|
||||
.callTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||
.connectTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
return createBasePromotionRetrofit(okClient, moshi)
|
||||
}
|
||||
|
||||
private fun createBasePromotionRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): PromotionApi {
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.baseUrl(if (BuildConfig.DEBUG) DEV_TANGEM_TECH_BASE_URL else PROD_TANGEM_TECH_BASE_URL)
|
||||
.client(okHttpClient)
|
||||
.build()
|
||||
.create(PromotionApi::class.java)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/"
|
||||
const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/"
|
||||
|
||||
private const val PAYMENTOLOGY_BASE_URL: String = "https://paymentologygate.oa.r.appspot.com/"
|
||||
const val PAYMENTOLOGY_BASE_URL: String = "https://paymentologygate.oa.r.appspot.com/"
|
||||
const val API_ONE_INCH_TIMEOUT_MS = 5000L
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@MustBeDocumented
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class PromotionOneInch
|
||||
|
|
@ -13,7 +13,7 @@ internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeade
|
|||
val request = chain.request().newBuilder().apply {
|
||||
requestHeaders
|
||||
.flatMap(RequestHeader::values)
|
||||
.forEach { addHeader(it.first, it.second) }
|
||||
.forEach { addHeader(it.first, it.second.invoke()) }
|
||||
}.build()
|
||||
|
||||
chain.proceed(request)
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ import com.tangem.lib.auth.AuthProvider
|
|||
*
|
||||
* @param pairs header name and header value pairs
|
||||
*/
|
||||
sealed class RequestHeader(vararg pairs: Pair<String, String>) {
|
||||
sealed class RequestHeader(vararg pairs: Pair<String, () -> String>) {
|
||||
|
||||
/** Header list */
|
||||
val values: List<Pair<String, String>> = pairs.toList()
|
||||
val values: List<Pair<String, () -> String>> = pairs.toList()
|
||||
|
||||
object CacheControlHeader : RequestHeader("Cache-Control" to "max-age=600")
|
||||
object CacheControlHeader : RequestHeader("Cache-Control" to { "max-age=600" })
|
||||
|
||||
class AuthenticationHeader(authProvider: AuthProvider) : RequestHeader(
|
||||
"card_public_key" to authProvider.getCardPublicKey(),
|
||||
"card_id" to authProvider.getCardId(),
|
||||
"card_id" to { authProvider.getCardId() },
|
||||
"card_public_key" to { authProvider.getCardPublicKey() },
|
||||
)
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
{
|
||||
"name": "NEW_CARD_SCANNING_ENABLED",
|
||||
"version": "4.8.0"
|
||||
"version": "4.9.0"
|
||||
},
|
||||
{
|
||||
"name": "REDESIGNED_WALLET_SCREEN_ENABLED",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<string name="common_done">Erledigt</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_save_changes">Änderungen speichern</string>
|
||||
<string name="common_send">Absenden</string>
|
||||
<string name="common_success">Erfolg</string>
|
||||
<string name="details_manage_security_access_code">Zugangscode</string>
|
||||
<string name="details_manage_security_access_code_description">Sie müssen den richtigen Zugangscode eingeben, bevor Sie die Karte scannen.</string>
|
||||
|
|
@ -44,7 +45,6 @@
|
|||
<string name="send_fee_picker_priority">Priorität</string>
|
||||
<string name="send_max_amount_label">Höchstbetrag</string>
|
||||
<string name="send_network_fee_title">Netzgebühr</string>
|
||||
<string name="send_title">Absenden</string>
|
||||
<string name="send_total_label">Gesamt</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s und %2$s werden gesendet</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (inkl. Gebühr: %2$s)</string>
|
||||
|
|
@ -56,7 +56,6 @@
|
|||
<string name="wallet_balance_loading">Die Bilanz wird aufgeladen…</string>
|
||||
<string name="wallet_balance_tx_in_progress">Die Transaktion läuft…</string>
|
||||
<string name="wallet_balance_verified">Verifizierte Bilanz</string>
|
||||
<string name="wallet_button_send">Absenden</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_error_no_account">Das Konto ist nicht erstellt</string>
|
||||
<string name="wallet_error_unsupported_blockchain">Diese Karte wird nicht unterstützt</string>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<string name="common_done">Exécuté</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_save_changes">Sauvegarder les modifications</string>
|
||||
<string name="common_send">Envoyer</string>
|
||||
<string name="common_success">Avec succès</string>
|
||||
<string name="details_manage_security_access_code">Code d\'accès</string>
|
||||
<string name="details_manage_security_access_code_description">Vous devrez entrer le mot de passe correct avant de scanner la carte</string>
|
||||
|
|
@ -44,7 +45,6 @@
|
|||
<string name="send_fee_picker_priority">Priorité</string>
|
||||
<string name="send_max_amount_label">Somme maximale</string>
|
||||
<string name="send_network_fee_title">Commissions du réseau</string>
|
||||
<string name="send_title">Envoyer</string>
|
||||
<string name="send_total_label">Total</string>
|
||||
<string name="send_total_subtitle_asset_format">Sera envoyé %1$s et %2$s</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (incl. les commissions : %2$s)</string>
|
||||
|
|
@ -56,7 +56,6 @@
|
|||
<string name="wallet_balance_loading">Solde est en cours de téléchargement…</string>
|
||||
<string name="wallet_balance_tx_in_progress">Transaction en cours…</string>
|
||||
<string name="wallet_balance_verified">Solde confirmé</string>
|
||||
<string name="wallet_button_send">Envoyer</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_error_no_account">Compte n\'est pas créé</string>
|
||||
<string name="wallet_error_unsupported_blockchain">Cette carte n\'est pas prise en charge</string>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<string name="common_done">Fatto</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_save_changes">Mantieni le modifiche</string>
|
||||
<string name="common_send">Invia</string>
|
||||
<string name="common_success">Con successo</string>
|
||||
<string name="details_manage_security_access_code">Codice di accesso</string>
|
||||
<string name="details_manage_security_access_code_description">Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto</string>
|
||||
|
|
@ -44,7 +45,6 @@
|
|||
<string name="send_fee_picker_priority">Prioritario</string>
|
||||
<string name="send_max_amount_label">Importo totale</string>
|
||||
<string name="send_network_fee_title">Costi della rete</string>
|
||||
<string name="send_title">Invia</string>
|
||||
<string name="send_total_label">Totale</string>
|
||||
<string name="send_total_subtitle_asset_format">Sarà inviato %1$s e %2$s</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (inc. commissione: %2$s)</string>
|
||||
|
|
@ -56,7 +56,6 @@
|
|||
<string name="wallet_balance_loading">Il saldo sta per essere caricato…</string>
|
||||
<string name="wallet_balance_tx_in_progress">Transazione in corso…</string>
|
||||
<string name="wallet_balance_verified">Saldo verificato</string>
|
||||
<string name="wallet_button_send">Invia</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_error_no_account">Conto non creato</string>
|
||||
<string name="wallet_error_unsupported_blockchain">Questa carta non è supportata</string>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<string name="alert_signed_hashes_message">Эта карта не является платежным средством. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец удерживает подписанную транзакцию от публикации, что является поводом для беспокойства.\nНе принимайте эту карту в качестве физического платежа от кого-то, кому вы не доверяете.\nВо всех остальных отношениях эта карта совершенно безопасна.\nTangem — единственный аппаратный кошелек, предлагающий защиту методом подсчета подписей.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">У вас возникли трудности со сканированием карты?</string>
|
||||
<string name="alert_unsupported_card">Эта карта не предназначена для работы с этим приложением</string>
|
||||
<string name="app_settings_enable_biometrics_description">Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App</string>
|
||||
<string name="app_settings_enable_biometrics_description">Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem</string>
|
||||
<string name="app_settings_enable_biometrics_title">Включите биометрическую аутентификацию</string>
|
||||
<string name="app_settings_off_saved_access_code_alert_message">Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком.</string>
|
||||
<string name="app_settings_off_saved_wallet_alert_message">При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения.</string>
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
<string name="card_settings_access_code_recovery_title">Восстановление кода доступа</string>
|
||||
<string name="card_settings_change_access_code">Смена кода доступа</string>
|
||||
<string name="card_settings_change_access_code_footer">Код доступа будет изменен только на данной карте</string>
|
||||
<string name="card_settings_reset_card_to_factory">Сброс к заводским настройкам</string>
|
||||
<string name="card_settings_reset_card_to_factory">Заводские настройки</string>
|
||||
<string name="card_settings_security_mode">Тип безопасности</string>
|
||||
<string name="card_settings_title">Настройки карты</string>
|
||||
<string name="chat_bot_name">Tangem Bot</string>
|
||||
|
|
@ -52,27 +52,41 @@
|
|||
<string name="common_balance">Баланс: %s</string>
|
||||
<string name="common_biometric_authentication">биометрическую аутентификацию</string>
|
||||
<string name="common_biometrics">биометрией</string>
|
||||
<string name="common_buy">Купить</string>
|
||||
<string name="common_camera_denied_alert_message">Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности.</string>
|
||||
<string name="common_cancel">Отмена</string>
|
||||
<string name="common_close">Закрыть</string>
|
||||
<string name="common_copy">Копировать</string>
|
||||
<string name="common_copy_address">Скопировать адрес</string>
|
||||
<string name="common_create">Создать</string>
|
||||
<string name="common_delete">Удалить</string>
|
||||
<string name="common_disabled">Отключено</string>
|
||||
<string name="common_dislike">Не нравится</string>
|
||||
<string name="common_done">Готово</string>
|
||||
<string name="common_enable">Включить</string>
|
||||
<string name="common_enabled">Включено</string>
|
||||
<string name="common_exchange">Обменять</string>
|
||||
<string name="common_explore_transaction_history">Посмотреть историю транзакций</string>
|
||||
<string name="common_explorer">Обозреватель</string>
|
||||
<string name="common_like">Нравится</string>
|
||||
<string name="common_main_network">Основная сеть</string>
|
||||
<string name="common_no">Нет</string>
|
||||
<string name="common_ok">Ок</string>
|
||||
<string name="common_no_data">Нет данных</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_origin_card">Основная карта</string>
|
||||
<string name="common_receive">Получить</string>
|
||||
<string name="common_reject">Отклонить</string>
|
||||
<string name="common_reload">Перезагрузить</string>
|
||||
<string name="common_save_changes">Сохранить изменения</string>
|
||||
<string name="common_search">Искать</string>
|
||||
<string name="common_seed_phrase">Секретная фраза</string>
|
||||
<string name="common_sell">Продать</string>
|
||||
<string name="common_send">Отправить</string>
|
||||
<string name="common_server_unavailable">Сервер недоступен, повторите попытку позднее</string>
|
||||
<string name="common_share">Поделиться</string>
|
||||
<string name="common_sign">Подписать</string>
|
||||
<string name="common_sign_and_send">Подписать и отправить</string>
|
||||
<string name="common_stake">Стейкинг</string>
|
||||
<string name="common_start">Начать</string>
|
||||
<string name="common_submit">Отправить</string>
|
||||
<string name="common_success">Успешно</string>
|
||||
|
|
@ -81,6 +95,7 @@
|
|||
<string name="common_transactions">Транзакции</string>
|
||||
<string name="common_transfer">Перевод</string>
|
||||
<string name="common_understand">Я понял</string>
|
||||
<string name="common_unreachable">Недоступно</string>
|
||||
<string name="common_yes">Да</string>
|
||||
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
|
||||
<string name="currency_subtitle_expanded">Доступные сети</string>
|
||||
|
|
@ -145,11 +160,22 @@
|
|||
<string name="initial_message_tap_header">Приложите карту</string>
|
||||
<string name="internal_error_wallet_manager_not_found">Внутренняя ошибка: не удается найти менеджер кошельков</string>
|
||||
<string name="key_invalidated_warning_description">Вы обновили данные биометрии, отсканируйте свою карту для входа</string>
|
||||
<string name="main_get_bonus_subtitle">Вы завершили обучение и можете получить свои токены 1inch</string>
|
||||
<string name="main_get_bonus_title">Получите бонус</string>
|
||||
<plurals name="main_learn_subtitle">
|
||||
<item quantity="one">Пройдите обучение и получите %d токен 1inch на свой кошелек</item>
|
||||
<item quantity="few">Пройдите обучение и получите %d токена 1inch на свой кошелек</item>
|
||||
<item quantity="many">Пройдите обучение и получите %d токена 1inch на свой кошелек</item>
|
||||
<item quantity="other">Пройдите обучение и получите %d токенов 1inch на свой кошелек</item>
|
||||
</plurals>
|
||||
<string name="main_learn_title">Бонус за обучение</string>
|
||||
<string name="main_manage_tokens">Управление токенами</string>
|
||||
<string name="main_no_backup_warning_subtitle">Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру</string>
|
||||
<string name="main_no_backup_warning_title">Бэкап кошелька не был произведен</string>
|
||||
<string name="main_page_balance">Баланс</string>
|
||||
<string name="main_processing_full_amount">В сумме учтены не все монеты</string>
|
||||
<string name="main_promotion_credited">Токены 1inch будут зачислены на адрес вашего кошелька в течение 2 дней</string>
|
||||
<string name="main_promotion_no_purchase">По вашему промокоду не было покупки кошелька, а значит вы не можете получить бонус. Купите кошелек Tangem, отсканируйте его в приложении и получите бонус.</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту</string>
|
||||
<string name="main_scan_card_warning_view_title">Отсканируйте карту</string>
|
||||
<string name="onboarding_access_code_feature_1_description">Вам необходимо установить единый код доступа для защиты всех ваших карт</string>
|
||||
|
|
@ -173,11 +199,8 @@
|
|||
<string name="onboarding_button_claim">Запросить</string>
|
||||
<string name="onboarding_button_continue_wallet">Перейти к моему кошельку</string>
|
||||
<string name="onboarding_button_finalize_backup">Завершение бэкапа</string>
|
||||
<string name="onboarding_button_kyc_start">Верифицировать (Utorg)</string>
|
||||
<string name="onboarding_button_kyc_waiting">Обновить</string>
|
||||
<string name="onboarding_button_pin">Установить Код доступа</string>
|
||||
<string name="onboarding_button_receive_crypto">Получить криптовалюту</string>
|
||||
<string name="onboarding_button_register_wallet">Зарегистрироваться</string>
|
||||
<string name="onboarding_button_scan_origin_card">Сканировать основную карту</string>
|
||||
<string name="onboarding_button_skip_backup">Пропустить</string>
|
||||
<string name="onboarding_button_what_does_it_mean">Как это работает?</string>
|
||||
|
|
@ -192,19 +215,9 @@
|
|||
<string name="onboarding_exit_alert_message">В этом случае вам будет необходимо начать процесс заново.</string>
|
||||
<string name="onboarding_exit_alert_title">Вы хотите выйти из процесса активации?</string>
|
||||
<string name="onboarding_getting_started">Подготовка</string>
|
||||
<string name="onboarding_linking_error_card_with_wallets">Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа?</string>
|
||||
<string name="onboarding_navbar_kyc_progress">Подтвердите свою личность</string>
|
||||
<string name="onboarding_navbar_kyc_start">Верификация клиента</string>
|
||||
<string name="onboarding_navbar_pin">Код доступа</string>
|
||||
<string name="onboarding_navbar_register_wallet">Подключиться</string>
|
||||
<string name="onboarding_navbar_title_creating_backup">Резервная копия</string>
|
||||
<string name="onboarding_saltpay_button_backup_origin">Приложите SaltPay карту</string>
|
||||
<string name="onboarding_saltpay_subtitle_no_backup_cards">Для начала процесса бэкапа вам необходимо добавить Tangem карту</string>
|
||||
<string name="onboarding_saltpay_subtitle_one_backup_card">Завершите процесс бэкапа создав код доступа</string>
|
||||
<string name="onboarding_saltpay_title_backup_card">Приложите Tangem карту</string>
|
||||
<string name="onboarding_saltpay_title_no_backup_card">Бэкап карта не добавлена</string>
|
||||
<string name="onboarding_saltpay_title_one_backup_card">Бэкап карта создана</string>
|
||||
<string name="onboarding_saltpay_title_prepare_origin">Приготовьте SaltPay карту</string>
|
||||
<string name="onboarding_seed_button_read_more">Читать про секретную фразу</string>
|
||||
<string name="onboarding_seed_generate_message">Запишите эти 12 слов в порядке, указанном ниже, и сохраните их в надежном месте.</string>
|
||||
<string name="onboarding_seed_generate_title">Ваша секретная фраза</string>
|
||||
|
|
@ -220,13 +233,8 @@
|
|||
<string name="onboarding_seed_user_validation_title">Итак, проверим</string>
|
||||
<string name="onboarding_subtitle_claim">Для начала работы просто запросите начисление wxDAI на свой кошелек</string>
|
||||
<string name="onboarding_subtitle_claim_progress">Это займет несколько секунд</string>
|
||||
<string name="onboarding_subtitle_kyc_retry">Более подробная информация отправлена на ваш адрес электронной почты.</string>
|
||||
<string name="onboarding_subtitle_kyc_start">Для начала работы с картой вам необходимо завершить процесс подтверждения личности</string>
|
||||
<string name="onboarding_subtitle_kyc_waiting">Пожалуйста дождитесь завершения процесса подтверждения личности. Вы будете уведомлены через e-mail. Обычно это занимает не более часа. Вы можете закрыть приложение и вернуться позже.</string>
|
||||
<string name="onboarding_subtitle_no_backup_cards">Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты.</string>
|
||||
<string name="onboarding_subtitle_one_backup_card">Вы можете добавить еще одну карту или завершить процесс резервного копирования</string>
|
||||
<string name="onboarding_subtitle_pin">Установите 4-х значный код.\nОн будет использован для платежей.</string>
|
||||
<string name="onboarding_subtitle_register_wallet">Подключите вашу карту к децентрализованной платежной системе</string>
|
||||
<string name="onboarding_subtitle_scan_backup_card_format">Подготовьте резервную карту с номером %s</string>
|
||||
<string name="onboarding_subtitle_scan_origin_card">Подготовьте основную карту</string>
|
||||
<string name="onboarding_subtitle_scan_primary_card_format">Подготовьте основную карту с номером %s</string>
|
||||
|
|
@ -237,13 +245,9 @@
|
|||
<string name="onboarding_title_backup_card_format">Резервная карта #%d</string>
|
||||
<string name="onboarding_title_claim">Запросить %s</string>
|
||||
<string name="onboarding_title_claim_progress">Запрашивается</string>
|
||||
<string name="onboarding_title_kyc_retry">Что-то пошло не так</string>
|
||||
<string name="onboarding_title_kyc_start">Подтвердите свою личность</string>
|
||||
<string name="onboarding_title_kyc_waiting">Подтверждение личности в процессе</string>
|
||||
<string name="onboarding_title_no_backup_cards">Нет резервных карт</string>
|
||||
<string name="onboarding_title_one_backup_card">Добавлена одна резервная карта</string>
|
||||
<string name="onboarding_title_pin">Код доступа</string>
|
||||
<string name="onboarding_title_register_wallet">Подключите свою карту</string>
|
||||
<string name="onboarding_title_scan_origin_card">Подготовьте свою карту</string>
|
||||
<string name="onboarding_title_two_backup_cards">Добавлены две резервные карты</string>
|
||||
<string name="onboarding_top_up_body">Пополните кошелек на любую сумму, чтобы начать пользоваться картой</string>
|
||||
|
|
@ -264,6 +268,9 @@
|
|||
<string name="organize_tokens_group">Группировка</string>
|
||||
<string name="organize_tokens_sort_by_balance">По балансу</string>
|
||||
<string name="organize_tokens_title">Сортировка токенов</string>
|
||||
<string name="organize_tokens_ungroup">Разгруппировать</string>
|
||||
<string name="receive_bottom_sheet_title">%1$s %2$s адрес в сети %3$s</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) в сети %3$s</string>
|
||||
<string name="referral_button_participate">Участвовать</string>
|
||||
<string name="referral_error_failed_to_load_info">Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="referral_error_failed_to_load_info_with_reason">Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже.</string>
|
||||
|
|
@ -295,12 +302,6 @@
|
|||
<string name="reset_card_without_backup_to_factory_message">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек.</string>
|
||||
<string name="russian_bank_card_warning_subtitle">У вас есть карта банка другой страны или платежной системы UnionPay?</string>
|
||||
<string name="russian_bank_card_warning_title">Карты банков РФ в данный момент не принимаются</string>
|
||||
<string name="saltpay_error_empty_backup_message">Приложите карту с логотипом Visa</string>
|
||||
<string name="saltpay_error_empty_backup_title">Внимание</string>
|
||||
<string name="saltpay_error_no_gas_message">Пожалуйста обратитесь в службу поддержки</string>
|
||||
<string name="saltpay_error_no_gas_title">Недостаточно средств для активации</string>
|
||||
<string name="saltpay_error_pin_weak_message">Данный Код доступа может быть легко взломан</string>
|
||||
<string name="saltpay_error_pin_weak_title">Ввод одинаковых цифр является не безопасным</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Войдите в приложение и следите за своим балансом без сканирования карты</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Доступ в приложение</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Использовать биометрию</string>
|
||||
|
|
@ -329,7 +330,6 @@
|
|||
<string name="send_fee_picker_priority">Приоритетная</string>
|
||||
<string name="send_max_amount_label">Максимальная сумма</string>
|
||||
<string name="send_network_fee_title">Сетевая комиссия</string>
|
||||
<string name="send_title">Отправить</string>
|
||||
<string name="send_title_currency_format">Отправка %s</string>
|
||||
<string name="send_total_label">Всего</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s и %2$s будет отправлено</string>
|
||||
|
|
@ -346,6 +346,7 @@
|
|||
<string name="solana_rent_warning">Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату.</string>
|
||||
<string name="story_awe_description">Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.</string>
|
||||
<string name="story_awe_title">Революционный аппаратный кошелек</string>
|
||||
<string name="story_backup_description">До **трех карт** с одним кошельком</string>
|
||||
<string name="story_backup_description_1">До</string>
|
||||
<string name="story_backup_description_2_bold">трех карт</string>
|
||||
<string name="story_backup_description_3">с одним кошельком</string>
|
||||
|
|
@ -354,6 +355,9 @@
|
|||
<string name="story_currencies_title">Тысячи криптовалют</string>
|
||||
<string name="story_finish_description">Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону.</string>
|
||||
<string name="story_finish_title">Кошелек для каждого</string>
|
||||
<string name="story_learn_description">Пройдите обучение и получите возможность купить кошелек Tangem со скидкой и токены 1inch в качестве бонуса</string>
|
||||
<string name="story_learn_learn">Пройти обучение</string>
|
||||
<string name="story_learn_title">Получите свой бонус</string>
|
||||
<string name="story_meet_borrow">Занимайте</string>
|
||||
<string name="story_meet_buy">Покупайте</string>
|
||||
<string name="story_meet_exchange">Обменивайте</string>
|
||||
|
|
@ -406,6 +410,7 @@
|
|||
<string name="token_details_hide_token">Скрыть токен</string>
|
||||
<string name="token_details_send_blocked_fee_format">%1$s — это токен в сети %2$s. Чтобы отправить транзакцию %3$s, необходимо пополнить баланс %4$s (%5$s) для оплаты комиссии сети.</string>
|
||||
<string name="token_details_send_blocked_tx_format">Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства</string>
|
||||
<string name="token_details_token_type_subtitle">%1$s токен в сети %%image%% %2$s</string>
|
||||
<string name="token_details_unable_hide_alert_message">Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.</string>
|
||||
<string name="token_details_unable_hide_alert_title">Невозможно скрыть %s</string>
|
||||
<string name="token_item_no_rate">Нет цены</string>
|
||||
|
|
@ -420,6 +425,7 @@
|
|||
<string name="transaction_history_error_failed_to_load">Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.</string>
|
||||
<string name="transaction_history_not_supported_description">История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе.</string>
|
||||
<string name="transaction_history_transaction_from_address">от: %s</string>
|
||||
<string name="transaction_history_transaction_to_address">на: %s</string>
|
||||
<string name="transaction_history_tx_in_progress">В процессе…</string>
|
||||
<string name="twin_error_same_card">Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d</string>
|
||||
<string name="twins_onboarding_description_format">Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька.</string>
|
||||
|
|
@ -449,21 +455,26 @@
|
|||
<string name="wallet_balance_tx_in_progress">Транзакция подтверждается…</string>
|
||||
<string name="wallet_balance_verified">Подтвержденный баланс</string>
|
||||
<string name="wallet_button_actions">Действия</string>
|
||||
<string name="wallet_button_buy">Купить</string>
|
||||
<string name="wallet_button_sell">Продать</string>
|
||||
<string name="wallet_button_send">Отправить</string>
|
||||
<string name="wallet_choose_trade_action">Вы хотите купить или продать криптовалюту?</string>
|
||||
<string name="wallet_connect_alert_sign_message">Запрос на подпись сообщения.\n\n%s</string>
|
||||
<string name="wallet_connect_bnb_sign_message">Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s</string>
|
||||
<string name="wallet_connect_bnb_trade_order_message">Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s</string>
|
||||
<string name="wallet_connect_bnb_transaction_message">Детали транзакции:\nОт: %1$s\nК: %2$s\nСумма: %3$s</string>
|
||||
<string name="wallet_connect_bnb_transaction_signed">Транзакция с BNB успешно подписана и отправлена в Dapp.</string>
|
||||
<string name="wallet_connect_clipboard_alert">Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код</string>
|
||||
<string name="wallet_connect_create_tx_message">Запрос на создание транзакции для %1$s\n%2$s\n\nСумма: %3$s\nКомиссия: %4$s\nВсего: %5$s\nБаланс: %6$s</string>
|
||||
<string name="wallet_connect_create_tx_not_enough_funds">Невозможно отправить транзакцию. Недостаточно средств.</string>
|
||||
<string name="wallet_connect_error_failed_to_connect">Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже.</string>
|
||||
<string name="wallet_connect_error_missing_blockchains">Не все токены добавлены в ваш список. Пожалуйста, добавьте их в начале, а потом попробуйте снова. Недостающие токены: \n</string>
|
||||
<string name="wallet_connect_error_sing_failed">Не удалось подписать сообщение.\nПожалуйста, попробуйте еще раз</string>
|
||||
<string name="wallet_connect_error_timeout">Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже.</string>
|
||||
<string name="wallet_connect_error_unsupported_blockchains">Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации.</string>
|
||||
<string name="wallet_connect_error_with_framework_message">Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки.</string>
|
||||
<string name="wallet_connect_error_wrong_card_selected">Неверная карта выбрана в приложении Tangem</string>
|
||||
<string name="wallet_connect_failed_to_build_tx">Не удалось создать транзакцию из данных Dapp. Код: %s</string>
|
||||
<string name="wallet_connect_generic_error_with_code">Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки.</string>
|
||||
<string name="wallet_connect_message_signed">Сообщение было успешно подписано и отправлено в Dapp</string>
|
||||
<string name="wallet_connect_network_not_found_format">Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.</string>
|
||||
<string name="wallet_connect_paste_from_clipboard">Вставить из буфера обмена</string>
|
||||
<string name="wallet_connect_request_session_start">Запрос на открытие сессии для\n%1$s\n\nСЕТЬ: %2$s\n\nURL: %3$s</string>
|
||||
|
|
@ -472,8 +483,12 @@
|
|||
<string name="wallet_connect_scanner_error_not_valid_card">Эту карту нельзя использовать с WalletConnect.</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">Сеть не поддерживается. Пожалуйста, выберите другую сеть.</string>
|
||||
<string name="wallet_connect_select_network">Выберите сеть</string>
|
||||
<string name="wallet_connect_service_no_chain_id">Dapp не предоставил необходимые данные для открытия сессии WalletConnect</string>
|
||||
<string name="wallet_connect_subtitle">Подключение к Dapps</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_connect_transaction_signed">Транзакция успешно подписана и отправлена в Dapp</string>
|
||||
<string name="wallet_connect_transaction_signed_and_send">Транзакция успешно подписана и отправлена в блокчейн</string>
|
||||
<string name="wallet_connect_tx_not_found">Не удалось найти хэш транзакции</string>
|
||||
<string name="wallet_currency_subtitle">Сеть %s</string>
|
||||
<string name="wallet_error_no_account">Аккаунт не создан</string>
|
||||
<string name="wallet_error_unsupported_blockchain">Эта карта не поддерживается</string>
|
||||
|
|
|
|||
|
|
@ -46,23 +46,29 @@
|
|||
<string name="common_balance">餘額: %s</string>
|
||||
<string name="common_biometric_authentication">生物識別</string>
|
||||
<string name="common_biometrics">生物</string>
|
||||
<string name="common_buy">購買</string>
|
||||
<string name="common_camera_denied_alert_message">您尚未授予相機訪問權限,請更改您的隱私設置</string>
|
||||
<string name="common_cancel">刪除</string>
|
||||
<string name="common_close">關閉</string>
|
||||
<string name="common_copy">複製</string>
|
||||
<string name="common_copy_address">複製地址</string>
|
||||
<string name="common_create">創造</string>
|
||||
<string name="common_delete">刪除</string>
|
||||
<string name="common_disabled">禁用</string>
|
||||
<string name="common_dislike">不喜歡</string>
|
||||
<string name="common_done">完成</string>
|
||||
<string name="common_enable">允許</string>
|
||||
<string name="common_enabled">啟用</string>
|
||||
<string name="common_exchange">交易</string>
|
||||
<string name="common_like">喜歡</string>
|
||||
<string name="common_no">否</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_origin_card">主卡片</string>
|
||||
<string name="common_reject">拒絕</string>
|
||||
<string name="common_save_changes">保存設置</string>
|
||||
<string name="common_search">搜索</string>
|
||||
<string name="common_sell">銷售</string>
|
||||
<string name="common_send">發送</string>
|
||||
<string name="common_server_unavailable">伺服器不可用,請稍後在試</string>
|
||||
<string name="common_share">分享</string>
|
||||
<string name="common_sign">簽署</string>
|
||||
|
|
@ -74,6 +80,7 @@
|
|||
<string name="common_terms_and_conditions">條款和條件</string>
|
||||
<string name="common_transactions">交易</string>
|
||||
<string name="common_understand">我了解</string>
|
||||
<string name="common_unreachable">無法觸達</string>
|
||||
<string name="common_yes">是</string>
|
||||
<string name="contract_address_copied_message">已複製代幣地址</string>
|
||||
<string name="currency_subtitle_expanded">支持的網路</string>
|
||||
|
|
@ -164,11 +171,8 @@
|
|||
<string name="onboarding_button_claim">獲取</string>
|
||||
<string name="onboarding_button_continue_wallet">繼續至我的錢包</string>
|
||||
<string name="onboarding_button_finalize_backup">完成備份過程</string>
|
||||
<string name="onboarding_button_kyc_start">通過Utorg驗證</string>
|
||||
<string name="onboarding_button_kyc_waiting">刷新</string>
|
||||
<string name="onboarding_button_pin">設置PIN碼</string>
|
||||
<string name="onboarding_button_receive_crypto">接收貨幣</string>
|
||||
<string name="onboarding_button_register_wallet">註冊</string>
|
||||
<string name="onboarding_button_scan_origin_card">掃描主卡</string>
|
||||
<string name="onboarding_button_skip_backup">暫時略過</string>
|
||||
<string name="onboarding_button_what_does_it_mean">它是如何運作的?</string>
|
||||
|
|
@ -183,18 +187,9 @@
|
|||
<string name="onboarding_exit_alert_message">這此情況,您必須要重新開始</string>
|
||||
<string name="onboarding_exit_alert_title">您想要離開啟用程序嗎?</string>
|
||||
<string name="onboarding_getting_started">開始</string>
|
||||
<string name="onboarding_navbar_kyc_progress">驗證身分</string>
|
||||
<string name="onboarding_navbar_kyc_start">KYC</string>
|
||||
<string name="onboarding_navbar_pin">PIN 碼</string>
|
||||
<string name="onboarding_navbar_register_wallet">連接</string>
|
||||
<string name="onboarding_navbar_title_creating_backup">創建備份</string>
|
||||
<string name="onboarding_saltpay_button_backup_origin">點擊 SaltPay 卡</string>
|
||||
<string name="onboarding_saltpay_subtitle_no_backup_cards">要開始備份過程,您必須添加 Tangem 卡片作為備份</string>
|
||||
<string name="onboarding_saltpay_subtitle_one_backup_card">通過創建訪問密碼完成備份</string>
|
||||
<string name="onboarding_saltpay_title_backup_card">點擊 Tangem 卡片</string>
|
||||
<string name="onboarding_saltpay_title_no_backup_card">沒有備份卡片</string>
|
||||
<string name="onboarding_saltpay_title_one_backup_card">備份卡片已準備完成</string>
|
||||
<string name="onboarding_saltpay_title_prepare_origin">準備SaltPay卡</string>
|
||||
<string name="onboarding_seed_button_read_more">閱讀更多關於助記詞的訊息</string>
|
||||
<string name="onboarding_seed_generate_message">按照下面給出的順序寫下這 12 個單詞,並將它們存放在安全秘密的地方。</string>
|
||||
<string name="onboarding_seed_generate_title">您的助記詞</string>
|
||||
|
|
@ -210,13 +205,8 @@
|
|||
<string name="onboarding_seed_user_validation_title">那麼,讓我們檢查一下</string>
|
||||
<string name="onboarding_subtitle_claim">要開始,只需將 wxDAI 獲取到您的錢包</string>
|
||||
<string name="onboarding_subtitle_claim_progress">這會花上幾秒</string>
|
||||
<string name="onboarding_subtitle_kyc_retry">請查看Email已獲得更多指示</string>
|
||||
<string name="onboarding_subtitle_kyc_start">要開始使用您的卡,您必須通過 KYC驗證</string>
|
||||
<string name="onboarding_subtitle_kyc_waiting">請等待驗證完成,您將收到電子郵件通知。通常最多需要 1 小時。您可以關閉該應用程序,稍後再回來</string>
|
||||
<string name="onboarding_subtitle_no_backup_cards">要開始備份過程,最多可添加兩張備份卡。</string>
|
||||
<string name="onboarding_subtitle_one_backup_card">您可以再添加一張卡或完成備份過程</string>
|
||||
<string name="onboarding_subtitle_pin">設置一個 4 位密碼。 \n它將用在付款時使用。</string>
|
||||
<string name="onboarding_subtitle_register_wallet">將您的卡片連接到去中心化支付系統</string>
|
||||
<string name="onboarding_subtitle_scan_backup_card_format">準備編號為 %s 的備份卡</string>
|
||||
<string name="onboarding_subtitle_scan_origin_card">準備主卡</string>
|
||||
<string name="onboarding_subtitle_scan_primary_card_format">準備編號為 %s 的主卡</string>
|
||||
|
|
@ -227,13 +217,9 @@
|
|||
<string name="onboarding_title_backup_card_format">備份卡 #%d</string>
|
||||
<string name="onboarding_title_claim">獲取%s</string>
|
||||
<string name="onboarding_title_claim_progress">獲取中</string>
|
||||
<string name="onboarding_title_kyc_retry">有東西出錯了</string>
|
||||
<string name="onboarding_title_kyc_start">確認你的身分</string>
|
||||
<string name="onboarding_title_kyc_waiting">KYC 認證中</string>
|
||||
<string name="onboarding_title_no_backup_cards">沒有備用卡</string>
|
||||
<string name="onboarding_title_one_backup_card">添加了一張備用卡</string>
|
||||
<string name="onboarding_title_pin">PIN 碼</string>
|
||||
<string name="onboarding_title_register_wallet">連接你的錢包</string>
|
||||
<string name="onboarding_title_scan_origin_card">準備好你的卡片</string>
|
||||
<string name="onboarding_title_two_backup_cards">添加了兩張備用卡</string>
|
||||
<string name="onboarding_top_up_body">要開始使用,只需為錢包充值任意金額</string>
|
||||
|
|
@ -279,12 +265,6 @@
|
|||
<string name="reset_card_without_backup_to_factory_message">恢復原廠設置將從所選卡中完全刪除錢包並將其從應用程序中刪除。您將無法恢復當前錢包</string>
|
||||
<string name="russian_bank_card_warning_subtitle">您有其他國家的銀行卡或銀聯卡嗎?</string>
|
||||
<string name="russian_bank_card_warning_title">目前不接受俄羅斯銀行卡</string>
|
||||
<string name="saltpay_error_empty_backup_message">輕觸帶有 visa 標誌的卡片</string>
|
||||
<string name="saltpay_error_empty_backup_title">注意</string>
|
||||
<string name="saltpay_error_no_gas_message">請聯繫客服</string>
|
||||
<string name="saltpay_error_no_gas_title">沒有激活資金</string>
|
||||
<string name="saltpay_error_pin_weak_message">這樣的 PIN 很容易被暴力破解</string>
|
||||
<string name="saltpay_error_pin_weak_title">四個相同的數字不安全</string>
|
||||
<string name="save_user_wallet_agreement_access_description">登錄應用程序並在不掃描卡片的情況下檢查您的資產</string>
|
||||
<string name="save_user_wallet_agreement_access_title">訪問應用程序</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">允許使用生物辨識</string>
|
||||
|
|
@ -313,7 +293,6 @@
|
|||
<string name="send_fee_picker_priority">優先</string>
|
||||
<string name="send_max_amount_label">最大值</string>
|
||||
<string name="send_network_fee_title">網路費</string>
|
||||
<string name="send_title">發送</string>
|
||||
<string name="send_title_currency_format">發送 %s</string>
|
||||
<string name="send_total_label">總計</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s 和 %2$s 將被發送</string>
|
||||
|
|
@ -329,6 +308,7 @@
|
|||
<string name="solana_rent_warning">Solana 網絡每 2 天收取 %1$s 的費用。無法付此費用的帳戶將從網絡中清除。向您的帳戶存入超過 %2$s 即可免費使用</string>
|
||||
<string name="story_awe_description">安全地存儲您的加密貨幣,同時將私鑰保存在您的卡中</string>
|
||||
<string name="story_awe_title">創新式的硬體錢包</string>
|
||||
<string name="story_backup_description">最多 **3張實體卡片** 到一個錢包</string>
|
||||
<string name="story_backup_description_1">最多</string>
|
||||
<string name="story_backup_description_2_bold">3張實體卡片</string>
|
||||
<string name="story_backup_description_3">到一個錢包</string>
|
||||
|
|
@ -419,21 +399,26 @@
|
|||
<string name="wallet_balance_tx_in_progress">交易進行中</string>
|
||||
<string name="wallet_balance_verified">檢視餘額</string>
|
||||
<string name="wallet_button_actions">動作</string>
|
||||
<string name="wallet_button_buy">購買</string>
|
||||
<string name="wallet_button_sell">銷售</string>
|
||||
<string name="wallet_button_send">發送</string>
|
||||
<string name="wallet_choose_trade_action">您想要購買或賣出交易貨幣?</string>
|
||||
<string name="wallet_connect_alert_sign_message">請求籤署消息。%s</string>
|
||||
<string name="wallet_connect_bnb_sign_message">Dapp %1$s,請求\n簽署 BNB 交易。\n%2$s</string>
|
||||
<string name="wallet_connect_bnb_trade_order_message">%1$s 的交易訂單\n價格: %2$s\n接收金額:%3$s\n支付數量: %4$s</string>
|
||||
<string name="wallet_connect_bnb_transaction_message">交易明細:\n從: %1$s\n到: %2$s\n數量: %3$s</string>
|
||||
<string name="wallet_connect_bnb_transaction_signed">BNB 交易已成功簽署並發送至 Dapp</string>
|
||||
<string name="wallet_connect_clipboard_alert">剪貼板包含 WalletConnect 代碼。使用複制的值或掃描二維碼</string>
|
||||
<string name="wallet_connect_create_tx_message">請求為 %1$s 創建交易\n%2$s\n\n數量: %3$s\n費用: %4$s\n全部的: %5$s\n餘額: %6$s</string>
|
||||
<string name="wallet_connect_create_tx_not_enough_funds">無法交易,無足夠資金</string>
|
||||
<string name="wallet_connect_error_failed_to_connect">未能建立 WalletConnect 連接。請稍後再試</string>
|
||||
<string name="wallet_connect_error_missing_blockchains">並非所有代幣都已添加到您的列表中。請先添加它們,然後重試。缺少標記:\n</string>
|
||||
<string name="wallet_connect_error_sing_failed">無法簽署消息。請重試</string>
|
||||
<string name="wallet_connect_error_timeout">無法建立 WalletConnect 連接:超時錯誤。請稍後再試</string>
|
||||
<string name="wallet_connect_error_unsupported_blockchains">會話請求包含不支持 WalletConnect 連接的區塊鏈。不支持的區塊鏈:</string>
|
||||
<string name="wallet_connect_error_unsupported_blockchains">會話請求包含不支持 WalletConnect 連接的區塊鏈。不支持的區塊鏈:\n</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">由於技術問題,無法與此 Dapp 建立連接</string>
|
||||
<string name="wallet_connect_error_with_framework_message">我們遇到了未知錯誤。錯誤信息:%s。如果問題仍然存在-請隨時聯繫我們的客服人員</string>
|
||||
<string name="wallet_connect_error_wrong_card_selected">在 Tangem App 中選擇了錯誤的卡</string>
|
||||
<string name="wallet_connect_failed_to_build_tx">無法從 Dapp 數據創建交易。代碼: %s</string>
|
||||
<string name="wallet_connect_generic_error_with_code">我們遇到了未知錯誤。錯誤代碼:%d。如果問題仍然存在-請隨時聯繫我們的支持人員</string>
|
||||
<string name="wallet_connect_message_signed">消息已成功簽名並發送至Dapp</string>
|
||||
<string name="wallet_connect_network_not_found_format">沒有 %s 網路,請先加入後再試一次</string>
|
||||
<string name="wallet_connect_paste_from_clipboard">從剪貼板貼上</string>
|
||||
<string name="wallet_connect_request_session_start">請求開始會話\n%1$s\n\n網絡: %2$s\n\n網址:%3$s</string>
|
||||
|
|
@ -442,8 +427,12 @@
|
|||
<string name="wallet_connect_scanner_error_not_valid_card">此卡不能用於建立 WalletConnect 連接</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">不支持此網絡。請選擇其他網絡</string>
|
||||
<string name="wallet_connect_select_network">選擇網路</string>
|
||||
<string name="wallet_connect_service_no_chain_id">Dapp 沒有提供必要的數據來建立 WalletConnect 連接</string>
|
||||
<string name="wallet_connect_subtitle">連結到Dapps</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_connect_transaction_signed">交易已成功簽署並發送至 Dapp</string>
|
||||
<string name="wallet_connect_transaction_signed_and_send">交易已成功簽署並發送至區塊鏈</string>
|
||||
<string name="wallet_connect_tx_not_found">未能找到交易哈希</string>
|
||||
<string name="wallet_currency_subtitle">%s 網路</string>
|
||||
<string name="wallet_error_no_account">帳號尚未被創造</string>
|
||||
<string name="wallet_error_unsupported_blockchain">不支持此卡</string>
|
||||
|
|
|
|||
|
|
@ -50,27 +50,41 @@
|
|||
<string name="common_balance">Balance: %s</string>
|
||||
<string name="common_biometric_authentication">biometric authentication</string>
|
||||
<string name="common_biometrics">biometrics</string>
|
||||
<string name="common_buy">Buy</string>
|
||||
<string name="common_camera_denied_alert_message">You have not given access to your camera, please adjust your privacy settings</string>
|
||||
<string name="common_cancel">Cancel</string>
|
||||
<string name="common_close">Close</string>
|
||||
<string name="common_copy">Copy</string>
|
||||
<string name="common_copy_address">Copy address</string>
|
||||
<string name="common_create">Create</string>
|
||||
<string name="common_delete">Delete</string>
|
||||
<string name="common_disabled">Disabled</string>
|
||||
<string name="common_dislike">Dislike</string>
|
||||
<string name="common_done">Done</string>
|
||||
<string name="common_enable">Enable</string>
|
||||
<string name="common_enabled">Enabled</string>
|
||||
<string name="common_exchange">Exchange</string>
|
||||
<string name="common_explore_transaction_history">Explore transaction history</string>
|
||||
<string name="common_explorer">Explorer</string>
|
||||
<string name="common_like">Like</string>
|
||||
<string name="common_main_network">Main network</string>
|
||||
<string name="common_no">No</string>
|
||||
<string name="common_no_data">No data</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_origin_card">Primary Card</string>
|
||||
<string name="common_receive">Receive</string>
|
||||
<string name="common_reject">Reject</string>
|
||||
<string name="common_reload">Reload</string>
|
||||
<string name="common_save_changes">Save changes</string>
|
||||
<string name="common_search">Search</string>
|
||||
<string name="common_seed_phrase">Seed phrase</string>
|
||||
<string name="common_sell">Sell</string>
|
||||
<string name="common_send">Send</string>
|
||||
<string name="common_server_unavailable">The server is not available, please try again later</string>
|
||||
<string name="common_share">Share</string>
|
||||
<string name="common_sign">Sign</string>
|
||||
<string name="common_sign_and_send">Sign and send</string>
|
||||
<string name="common_stake">Stake</string>
|
||||
<string name="common_start">Start</string>
|
||||
<string name="common_submit">Submit</string>
|
||||
<string name="common_success">Success</string>
|
||||
|
|
@ -79,6 +93,7 @@
|
|||
<string name="common_transactions">Transactions</string>
|
||||
<string name="common_transfer">Transfer</string>
|
||||
<string name="common_understand">I understand</string>
|
||||
<string name="common_unreachable">Unreachable</string>
|
||||
<string name="common_yes">Yes</string>
|
||||
<string name="contract_address_copied_message">Contract address copied!</string>
|
||||
<string name="currency_subtitle_expanded">Available networks</string>
|
||||
|
|
@ -143,11 +158,20 @@
|
|||
<string name="initial_message_tap_header">Tap the card</string>
|
||||
<string name="internal_error_wallet_manager_not_found">Internal error: wallet manager not found</string>
|
||||
<string name="key_invalidated_warning_description">You have updated biometrics, scan your card to enter</string>
|
||||
<string name="main_get_bonus_subtitle">You have completed the training and can get your 1inch tokens</string>
|
||||
<string name="main_get_bonus_title">Get a bonus</string>
|
||||
<plurals name="main_learn_subtitle">
|
||||
<item quantity="one">Complete the training and get %d 1inch token on your wallet</item>
|
||||
<item quantity="other">Complete the training and get %d 1inch tokens on your wallet</item>
|
||||
</plurals>
|
||||
<string name="main_learn_title">Learn & Earn</string>
|
||||
<string name="main_manage_tokens">Manage tokens</string>
|
||||
<string name="main_no_backup_warning_subtitle">To protect your assets, we advise you to carry out this procedure</string>
|
||||
<string name="main_no_backup_warning_title">Your wallet has not been backed up</string>
|
||||
<string name="main_page_balance">Total balance</string>
|
||||
<string name="main_processing_full_amount">The amount does not include some of your funds</string>
|
||||
<string name="main_promotion_credited">1inch tokens will be credited to your wallet address within 2 days</string>
|
||||
<string name="main_promotion_no_purchase">There was no purchase of a wallet using your promo code, which means you cannot receive a bonus. Buy Tangem wallet, scan it in the app, and get the bonus.</string>
|
||||
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>
|
||||
<string name="main_scan_card_warning_view_title">Scan your card</string>
|
||||
<string name="onboarding_access_code_feature_1_description">You have to set up a single access code to protect all your wallets</string>
|
||||
|
|
@ -171,11 +195,8 @@
|
|||
<string name="onboarding_button_claim">Claim</string>
|
||||
<string name="onboarding_button_continue_wallet">Continue to my wallet</string>
|
||||
<string name="onboarding_button_finalize_backup">Finalize the backup</string>
|
||||
<string name="onboarding_button_kyc_start">Verify via Utorg</string>
|
||||
<string name="onboarding_button_kyc_waiting">Refresh</string>
|
||||
<string name="onboarding_button_pin">Set PIN code</string>
|
||||
<string name="onboarding_button_receive_crypto">Receive crypto</string>
|
||||
<string name="onboarding_button_register_wallet">Register</string>
|
||||
<string name="onboarding_button_scan_origin_card">Scan primary card</string>
|
||||
<string name="onboarding_button_skip_backup">Skip for later</string>
|
||||
<string name="onboarding_button_what_does_it_mean">How does it work?</string>
|
||||
|
|
@ -191,18 +212,9 @@
|
|||
<string name="onboarding_exit_alert_title">Do you want to exit the activation process?</string>
|
||||
<string name="onboarding_getting_started">Getting started</string>
|
||||
<string name="onboarding_linking_error_card_with_wallets">Another wallet has already been created on the card you\'re trying to add. Do you want to reset it and use the card for a new wallet?</string>
|
||||
<string name="onboarding_navbar_kyc_progress">Verify your identity</string>
|
||||
<string name="onboarding_navbar_kyc_start">KYC</string>
|
||||
<string name="onboarding_navbar_pin">Pin code</string>
|
||||
<string name="onboarding_navbar_register_wallet">Connect</string>
|
||||
<string name="onboarding_navbar_title_creating_backup">Creating a backup</string>
|
||||
<string name="onboarding_saltpay_button_backup_origin">Tap the SaltPay card</string>
|
||||
<string name="onboarding_saltpay_subtitle_no_backup_cards">To start the backup process you have to add the Tangem card as your backup</string>
|
||||
<string name="onboarding_saltpay_subtitle_one_backup_card">Finalize the backup process by creating an access code</string>
|
||||
<string name="onboarding_saltpay_title_backup_card">Tap the Tangem card</string>
|
||||
<string name="onboarding_saltpay_title_no_backup_card">No backup card</string>
|
||||
<string name="onboarding_saltpay_title_one_backup_card">Backup card ready</string>
|
||||
<string name="onboarding_saltpay_title_prepare_origin">Prepare the SaltPay card</string>
|
||||
<string name="onboarding_seed_button_read_more">Read more about seed phrases</string>
|
||||
<string name="onboarding_seed_generate_message">Write these 12 words down in the order given below and store them in a safe and secret place.</string>
|
||||
<string name="onboarding_seed_generate_title">Your secret phrase</string>
|
||||
|
|
@ -218,13 +230,8 @@
|
|||
<string name="onboarding_seed_user_validation_title">So, let’s check</string>
|
||||
<string name="onboarding_subtitle_claim">To get started, simply claim wxDAI to your wallet</string>
|
||||
<string name="onboarding_subtitle_claim_progress">It will take a few seconds</string>
|
||||
<string name="onboarding_subtitle_kyc_retry">Please check your email for further instructions</string>
|
||||
<string name="onboarding_subtitle_kyc_start">To start using your card you have to pass the KYC process</string>
|
||||
<string name="onboarding_subtitle_kyc_waiting">Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later.</string>
|
||||
<string name="onboarding_subtitle_no_backup_cards">To start the backup process add up to two backup cards.</string>
|
||||
<string name="onboarding_subtitle_one_backup_card">You can add one more card or finalize the backup process</string>
|
||||
<string name="onboarding_subtitle_pin">Set up a 4-digit code.\nIt will be used for payments.</string>
|
||||
<string name="onboarding_subtitle_register_wallet">Connect your card to the decentralized payment system</string>
|
||||
<string name="onboarding_subtitle_scan_backup_card_format">Prepare the backup card with number %s</string>
|
||||
<string name="onboarding_subtitle_scan_origin_card">Prepare the primary card</string>
|
||||
<string name="onboarding_subtitle_scan_primary_card_format">Prepare the primary card with number %s</string>
|
||||
|
|
@ -235,13 +242,9 @@
|
|||
<string name="onboarding_title_backup_card_format">Backup card #%d</string>
|
||||
<string name="onboarding_title_claim">Claim %s</string>
|
||||
<string name="onboarding_title_claim_progress">Claiming</string>
|
||||
<string name="onboarding_title_kyc_retry">Something went wrong</string>
|
||||
<string name="onboarding_title_kyc_start">Verify your identity</string>
|
||||
<string name="onboarding_title_kyc_waiting">KYC is in progress</string>
|
||||
<string name="onboarding_title_no_backup_cards">No backup cards</string>
|
||||
<string name="onboarding_title_one_backup_card">One backup card added</string>
|
||||
<string name="onboarding_title_pin">PIN Code</string>
|
||||
<string name="onboarding_title_register_wallet">Connect your card</string>
|
||||
<string name="onboarding_title_scan_origin_card">Prepare your card</string>
|
||||
<string name="onboarding_title_two_backup_cards">Two backup cards added</string>
|
||||
<string name="onboarding_top_up_body">To get started, simply top up the wallet with any amount</string>
|
||||
|
|
@ -262,6 +265,10 @@
|
|||
<string name="organize_tokens_group">Group</string>
|
||||
<string name="organize_tokens_sort_by_balance">By balance</string>
|
||||
<string name="organize_tokens_title">Organize tokens</string>
|
||||
<string name="organize_tokens_ungroup">Ungroup</string>
|
||||
<string name="receive_bottom_sheet_title">%1$s %2$s address on %3$s network</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) on %3$s network</string>
|
||||
<string name="receive_bottom_sheet_warning_message_full">Send only %s to this address. Sending any other currency will result in its irreversible loss.</string>
|
||||
<string name="referral_button_participate">Participate</string>
|
||||
<string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string>
|
||||
<string name="referral_error_failed_to_load_info_with_reason">Failed to load the information about the referral program. Error code: %s. Please try again later.</string>
|
||||
|
|
@ -291,12 +298,6 @@
|
|||
<string name="reset_card_without_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet.</string>
|
||||
<string name="russian_bank_card_warning_subtitle">Do you have a bank card of another country or a UnionPay card?</string>
|
||||
<string name="russian_bank_card_warning_title">Russian bank cards are not accepted at the moment</string>
|
||||
<string name="saltpay_error_empty_backup_message">Tap the card with the visa logo</string>
|
||||
<string name="saltpay_error_empty_backup_title">Attention</string>
|
||||
<string name="saltpay_error_no_gas_message">Please contact support</string>
|
||||
<string name="saltpay_error_no_gas_title">No funds for activation</string>
|
||||
<string name="saltpay_error_pin_weak_message">Such a PIN can be brute-forced easily</string>
|
||||
<string name="saltpay_error_pin_weak_title">Four identical digits isn\'t safe</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Log into the app and check your balance without scanning the card</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Access the app</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Allow to use biometrics</string>
|
||||
|
|
@ -325,7 +326,6 @@
|
|||
<string name="send_fee_picker_priority">Priority</string>
|
||||
<string name="send_max_amount_label">Maximum amount</string>
|
||||
<string name="send_network_fee_title">Network fee</string>
|
||||
<string name="send_title">Send</string>
|
||||
<string name="send_title_currency_format">Sending %s</string>
|
||||
<string name="send_total_label">Total</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s and %2$s will be sent</string>
|
||||
|
|
@ -342,6 +342,7 @@
|
|||
<string name="solana_rent_warning">Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free.</string>
|
||||
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
|
||||
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
|
||||
<string name="story_backup_description">Up to **3 physical cards** to one wallet</string>
|
||||
<string name="story_backup_description_1">Up to</string>
|
||||
<string name="story_backup_description_2_bold">3 physical cards</string>
|
||||
<string name="story_backup_description_3">to one wallet</string>
|
||||
|
|
@ -350,6 +351,9 @@
|
|||
<string name="story_currencies_title">Thousands of Currencies</string>
|
||||
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
|
||||
<string name="story_finish_title">The Wallet for Everyone</string>
|
||||
<string name="story_learn_description">Complete the training, get the opportunity to buy Tangem wallet with a discount and receive 1inch tokens on your wallet</string>
|
||||
<string name="story_learn_learn">Learn</string>
|
||||
<string name="story_learn_title">Learn and get a bonus</string>
|
||||
<string name="story_meet_borrow">Borrow</string>
|
||||
<string name="story_meet_buy">Buy</string>
|
||||
<string name="story_meet_exchange">Exchange</string>
|
||||
|
|
@ -400,6 +404,7 @@
|
|||
<string name="token_details_hide_token">Hide token</string>
|
||||
<string name="token_details_send_blocked_fee_format">%1$s is a token in the %2$s network. To make a %3$s transaction you need to deposit some %4$s (%5$s) to cover the network fee.</string>
|
||||
<string name="token_details_send_blocked_tx_format">Please wait for %s transaction to complete to be able to send funds</string>
|
||||
<string name="token_details_token_type_subtitle">%1$s token in %%image%% %2$s network</string>
|
||||
<string name="token_details_unable_hide_alert_message">The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
|
||||
<string name="token_details_unable_hide_alert_title">Unable to hide %s</string>
|
||||
<string name="token_item_no_rate">No rate</string>
|
||||
|
|
@ -442,21 +447,26 @@
|
|||
<string name="wallet_balance_tx_in_progress">Transaction is in progress…</string>
|
||||
<string name="wallet_balance_verified">Verified Balance</string>
|
||||
<string name="wallet_button_actions">Actions</string>
|
||||
<string name="wallet_button_buy">Buy</string>
|
||||
<string name="wallet_button_sell">Sell</string>
|
||||
<string name="wallet_button_send">Send</string>
|
||||
<string name="wallet_choose_trade_action">Do you want to buy or sell crypto?</string>
|
||||
<string name="wallet_connect_alert_sign_message">Requesting to sign a message.\n\n%s</string>
|
||||
<string name="wallet_connect_bnb_sign_message">Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s</string>
|
||||
<string name="wallet_connect_bnb_trade_order_message">Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s</string>
|
||||
<string name="wallet_connect_bnb_transaction_message">Transaction details:\nFrom: %1$s\nTo: %2$s\nAmount: %3$s</string>
|
||||
<string name="wallet_connect_bnb_transaction_signed">The BNB transaction has been successfully signed and sent to the Dapp</string>
|
||||
<string name="wallet_connect_clipboard_alert">Clipboard contain WalletConnect code. Use copied value or scan QR-code</string>
|
||||
<string name="wallet_connect_create_tx_message">Request to create transaction for %1$s\n%2$s\n\nAmount: %3$s\nFee: %4$s\nTotal: %5$s\nBalance: %6$s</string>
|
||||
<string name="wallet_connect_create_tx_not_enough_funds">Can\'t send transaction. Not enough funds.</string>
|
||||
<string name="wallet_connect_error_failed_to_connect">Failed to establish WalletConnect session. Please, try again later.</string>
|
||||
<string name="wallet_connect_error_missing_blockchains">Not all tokens were added to your list. Please add them first and try again. Missing tokens:\n</string>
|
||||
<string name="wallet_connect_error_sing_failed">Failed to sign message.\nPlease, try again</string>
|
||||
<string name="wallet_connect_error_timeout">Failed to establish WalletConnect session: timeout error. Please, try again later.</string>
|
||||
<string name="wallet_connect_error_unsupported_blockchains">Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">Connection with this Dapp cannot be established due to its technical implementation.</string>
|
||||
<string name="wallet_connect_error_with_framework_message">We\'ve encountered unknown error. Error message: %s. If the problem persists — feel free to contact our support</string>
|
||||
<string name="wallet_connect_error_wrong_card_selected">Wrong card selected in Tangem App</string>
|
||||
<string name="wallet_connect_failed_to_build_tx">Failed to create transaction from Dapp data. Code: %s</string>
|
||||
<string name="wallet_connect_generic_error_with_code">We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support</string>
|
||||
<string name="wallet_connect_message_signed">The message has been successfully signed and sent to the Dapp</string>
|
||||
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
|
||||
<string name="wallet_connect_paste_from_clipboard">Paste from clipboard</string>
|
||||
<string name="wallet_connect_request_session_start">Request to start a session for\n%1$s\n\nNETWORK: %2$s\n\nURL: %3$s</string>
|
||||
|
|
@ -465,8 +475,12 @@
|
|||
<string name="wallet_connect_scanner_error_not_valid_card">This card can\'t be used to establish WalletConnect session</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
|
||||
<string name="wallet_connect_select_network">Select network</string>
|
||||
<string name="wallet_connect_service_no_chain_id">Dapp didn\'t provide essential data to establish WalletConnect session</string>
|
||||
<string name="wallet_connect_subtitle">Connect to Dapps</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_connect_transaction_signed">The transaction has been successfully signed and sent to the Dapp</string>
|
||||
<string name="wallet_connect_transaction_signed_and_send">The transaction has been succesfully signed and sent to the blockchain</string>
|
||||
<string name="wallet_connect_tx_not_found">Failed to find transaction hash</string>
|
||||
<string name="wallet_currency_subtitle">%s network</string>
|
||||
<string name="wallet_error_no_account">Account is not created</string>
|
||||
<string name="wallet_error_unsupported_blockchain">This card is not supported</string>
|
||||
|
|
|
|||
|
|
@ -2,19 +2,17 @@ package com.tangem.core.ui.components
|
|||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
// region TextButton
|
||||
|
|
@ -26,7 +24,7 @@ fun TextButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier,
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
|
|
@ -49,7 +47,7 @@ fun TextButtonIconStart(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
icon = TangemButtonIconPosition.Start(iconResId),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
|
|
@ -63,7 +61,7 @@ fun WarningTextButton(text: String, onClick: () -> Unit, modifier: Modifier = Mo
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
|
|
@ -85,7 +83,7 @@ fun PrimaryButton(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -108,7 +106,7 @@ fun PrimaryButtonIconEnd(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.End(iconResId),
|
||||
icon = TangemButtonIconPosition.End(iconResId),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -131,7 +129,7 @@ fun PrimaryButtonIconStart(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
icon = TangemButtonIconPosition.Start(iconResId),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -152,7 +150,7 @@ fun SecondaryButton(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.None,
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -175,7 +173,7 @@ fun SecondaryButtonIconEnd(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.End(iconResId),
|
||||
icon = TangemButtonIconPosition.End(iconResId),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -198,7 +196,7 @@ fun SecondaryButtonIconStart(
|
|||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
icon = TangemButtonIconPosition.Start(iconResId),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = enabled,
|
||||
|
|
@ -214,7 +212,7 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif
|
|||
modifier = modifier,
|
||||
text = text,
|
||||
textStyle = TangemTheme.typography.subtitle2,
|
||||
icon = TangemButtonIcon.End(iconResId = R.drawable.ic_chevron_24),
|
||||
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.selectorButtonColors,
|
||||
showProgress = false,
|
||||
|
|
@ -224,362 +222,6 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif
|
|||
}
|
||||
// endregion Other
|
||||
|
||||
// region Action
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-305&t=3z98eFnTeyIx5TH5-4)
|
||||
* */
|
||||
@Composable
|
||||
fun RoundedActionButton(
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1208-1395&t=3z98eFnTeyIx5TH5-4)
|
||||
* */
|
||||
@Composable
|
||||
fun ActionButton(
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
size = TangemButtonSize.Action,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as [RoundedActionButton] but colored in primary background color
|
||||
* */
|
||||
@Composable
|
||||
fun BackgroundActionButton(
|
||||
text: String,
|
||||
@DrawableRes iconResId: Int,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
icon = TangemButtonIcon.Start(iconResId),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
showProgress = false,
|
||||
colors = TangemButtonsDefaults.backgroundButtonColors,
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
)
|
||||
}
|
||||
// endregion Action
|
||||
|
||||
// region Defaults
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun TangemButton(
|
||||
text: String,
|
||||
icon: TangemButtonIcon,
|
||||
onClick: () -> Unit,
|
||||
colors: ButtonColors,
|
||||
showProgress: Boolean,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
elevation: ButtonElevation = TangemButtonsDefaults.elevation,
|
||||
textStyle: TextStyle = TangemTheme.typography.button,
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier.heightIn(min = size.toHeightDp()),
|
||||
onClick = { if (!showProgress) onClick() },
|
||||
enabled = enabled,
|
||||
elevation = elevation,
|
||||
shape = size.toShape(),
|
||||
colors = colors,
|
||||
contentPadding = size.toContentPadding(icon = icon),
|
||||
) {
|
||||
ButtonContent(
|
||||
text = text,
|
||||
textStyle = textStyle,
|
||||
buttonIcon = icon,
|
||||
colors = colors,
|
||||
showProgress = showProgress,
|
||||
enabled = enabled,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun ButtonContent(
|
||||
text: String,
|
||||
textStyle: TextStyle,
|
||||
buttonIcon: TangemButtonIcon,
|
||||
colors: ButtonColors,
|
||||
size: TangemButtonSize,
|
||||
enabled: Boolean,
|
||||
showProgress: Boolean,
|
||||
) {
|
||||
val icon = @Composable { iconResId: Int ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
painter = painterResource(id = iconResId),
|
||||
tint = colors.contentColor(enabled = enabled).value,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
if (showProgress) {
|
||||
Box(modifier = Modifier.wrapContentSize()) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(TangemTheme.dimens.size24),
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
strokeWidth = TangemTheme.dimens.size4,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()),
|
||||
) {
|
||||
if (buttonIcon is TangemButtonIcon.Start) {
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
maxLines = 1,
|
||||
)
|
||||
if (buttonIcon is TangemButtonIcon.End) {
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private sealed interface TangemButtonIcon {
|
||||
val iconResId: Int?
|
||||
|
||||
data class Start(override val iconResId: Int) : TangemButtonIcon
|
||||
|
||||
data class End(override val iconResId: Int) : TangemButtonIcon
|
||||
|
||||
object None : TangemButtonIcon {
|
||||
override val iconResId: Int? = null
|
||||
}
|
||||
}
|
||||
|
||||
private enum class TangemButtonSize {
|
||||
Default,
|
||||
Text,
|
||||
Selector,
|
||||
Action,
|
||||
RoundedAction,
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toHeightDp(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.size48
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.size40
|
||||
TangemButtonSize.Selector -> TangemTheme.dimens.size24
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> TangemTheme.dimens.size36
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toShape(): Shape = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toIconPadding(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Selector -> 0.dp
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> TangemTheme.dimens.spacing8
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toContentPadding(icon: TangemButtonIcon): PaddingValues {
|
||||
val horizontalPadding = this.toHorizontalContentPadding(icon = icon)
|
||||
|
||||
return when (this) {
|
||||
TangemButtonSize.Default -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing14,
|
||||
bottom = TangemTheme.dimens.spacing14,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Text -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Selector -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing0_5,
|
||||
bottom = TangemTheme.dimens.spacing0_5,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing8,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIcon): Pair<Dp, Dp> {
|
||||
return when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
|
||||
TangemButtonSize.Text -> when (icon) {
|
||||
is TangemButtonIcon.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
|
||||
is TangemButtonIcon.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
|
||||
is TangemButtonIcon.End -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing14
|
||||
}
|
||||
TangemButtonSize.Selector -> TangemTheme.dimens.spacing0_5 to TangemTheme.dimens.spacing0_5
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> when (icon) {
|
||||
is TangemButtonIcon.None -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing24
|
||||
is TangemButtonIcon.Start -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing24
|
||||
is TangemButtonIcon.End -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object TangemButtonsDefaults {
|
||||
val elevation: ButtonElevation
|
||||
@Composable get() = ButtonDefaults
|
||||
.elevation(
|
||||
defaultElevation = TangemTheme.dimens.elevation0,
|
||||
pressedElevation = TangemTheme.dimens.elevation0,
|
||||
)
|
||||
|
||||
val primaryButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.button.primary,
|
||||
contentColor = TangemTheme.colors.text.primary2,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val secondaryButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.button.secondary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val defaultTextButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.secondary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val warningTextButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.warning,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val selectorButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.tertiary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val backgroundButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private open class TangemButtonColors(
|
||||
private val backgroundColor: Color,
|
||||
private val contentColor: Color,
|
||||
private val disabledBackgroundColor: Color,
|
||||
private val disabledContentColor: Color,
|
||||
) : ButtonColors {
|
||||
@Composable
|
||||
override fun backgroundColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(newValue = if (enabled) backgroundColor else disabledBackgroundColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun contentColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(newValue = if (enabled) contentColor else disabledContentColor)
|
||||
}
|
||||
}
|
||||
// endregion Defaults
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun PrimaryButtonSample() {
|
||||
|
|
@ -730,34 +372,4 @@ private fun TextButtonPreview_DarkTheme() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionButtonSample() {
|
||||
Column(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
|
||||
ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
|
||||
BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
|
||||
RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
|
||||
ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
|
||||
BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun ActionButtonPreview_LightTheme() {
|
||||
TangemTheme {
|
||||
ActionButtonSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun ActionButtonPreview_DarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
ActionButtonSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.core.ui.components.buttons.actions
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
/**
|
||||
* Action button config
|
||||
*
|
||||
* @property text text
|
||||
* @property iconResId icon resource id
|
||||
* @property onClick lambda be invoked when action component is clicked
|
||||
* @property enabled enabled
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class ActionButtonConfig(
|
||||
val text: String,
|
||||
@DrawableRes val iconResId: Int,
|
||||
val onClick: () -> Unit,
|
||||
val enabled: Boolean = true,
|
||||
)
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
package com.tangem.core.ui.components.buttons.actions
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.buttons.common.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Rounded action button
|
||||
*
|
||||
* @param config component config
|
||||
* @param modifier modifier
|
||||
* @param color background color
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-306&mode=design&t=
|
||||
* cSLQtIbR2J1h5M9U-4">Show in Figma</a>
|
||||
*/
|
||||
@Composable
|
||||
fun RoundedActionButton(
|
||||
config: ActionButtonConfig,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.button.secondary,
|
||||
) {
|
||||
Button(
|
||||
config = config,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius24),
|
||||
modifier = modifier,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Action button
|
||||
*
|
||||
* @param config component config
|
||||
* @param modifier modifier
|
||||
* @param color background color
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-306&mode=design&t=
|
||||
* cSLQtIbR2J1h5M9U-4">Show in Figma</a>
|
||||
*/
|
||||
@Composable
|
||||
fun ActionButton(
|
||||
config: ActionButtonConfig,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.button.secondary,
|
||||
) {
|
||||
Button(
|
||||
config = config,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius12),
|
||||
modifier = modifier,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Button(
|
||||
config: ActionButtonConfig,
|
||||
shape: RoundedCornerShape,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.button.secondary,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.heightIn(min = TangemTheme.dimens.size36)
|
||||
.clip(shape)
|
||||
.background(
|
||||
color = if (config.enabled) color else TangemTheme.colors.button.disabled,
|
||||
shape = shape,
|
||||
)
|
||||
.clickable(enabled = config.enabled, onClick = config.onClick)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing24,
|
||||
)
|
||||
.padding(vertical = TangemTheme.dimens.spacing8),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = config.iconResId),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(size = TangemTheme.dimens.size20),
|
||||
tint = if (config.enabled) TangemTheme.colors.icon.primary1 else TangemTheme.colors.icon.informative,
|
||||
)
|
||||
|
||||
SpacerW8()
|
||||
|
||||
Text(
|
||||
text = config.text,
|
||||
color = if (config.enabled) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.disabled,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.button,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = "RoundedActionButton", showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_RoundedActionButton_Light(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) {
|
||||
TangemTheme(isDark = false) {
|
||||
RoundedActionButton(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = "RoundedActionButton", showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_RoundedActionButton_Dark(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) {
|
||||
TangemTheme(isDark = true) {
|
||||
RoundedActionButton(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = "ActionButton", showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ActionButton_Light(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) {
|
||||
TangemTheme(isDark = false) {
|
||||
ActionButton(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = "ActionButton", showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ActionButton_Dark(@PreviewParameter(ActionStateProvider::class) state: ActionButtonConfig) {
|
||||
TangemTheme(isDark = true) {
|
||||
ActionButton(state)
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionStateProvider : CollectionPreviewParameterProvider<ActionButtonConfig>(
|
||||
collection = listOf(
|
||||
ActionButtonConfig(text = "Enabled", iconResId = R.drawable.ic_arrow_up_24, enabled = true, onClick = {}),
|
||||
ActionButtonConfig(text = "Disabled", iconResId = R.drawable.ic_arrow_down_24, enabled = false, onClick = {}),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun TangemButton(
|
||||
text: String,
|
||||
icon: TangemButtonIconPosition,
|
||||
onClick: () -> Unit,
|
||||
colors: ButtonColors,
|
||||
showProgress: Boolean,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
elevation: ButtonElevation = TangemButtonsDefaults.elevation,
|
||||
textStyle: TextStyle = TangemTheme.typography.button,
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier.heightIn(min = size.toHeightDp()),
|
||||
onClick = { if (!showProgress) onClick() },
|
||||
enabled = enabled,
|
||||
elevation = elevation,
|
||||
shape = size.toShape(),
|
||||
colors = colors,
|
||||
contentPadding = size.toContentPadding(icon = icon),
|
||||
) {
|
||||
ButtonContent(
|
||||
text = text,
|
||||
textStyle = textStyle,
|
||||
buttonIcon = icon,
|
||||
colors = colors,
|
||||
showProgress = showProgress,
|
||||
enabled = enabled,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun ButtonContent(
|
||||
text: String,
|
||||
textStyle: TextStyle,
|
||||
buttonIcon: TangemButtonIconPosition,
|
||||
colors: ButtonColors,
|
||||
size: TangemButtonSize,
|
||||
enabled: Boolean,
|
||||
showProgress: Boolean,
|
||||
) {
|
||||
val icon = @Composable { iconResId: Int ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
painter = painterResource(id = iconResId),
|
||||
tint = colors.contentColor(enabled = enabled).value,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
if (showProgress) {
|
||||
Box(modifier = Modifier.wrapContentSize()) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(TangemTheme.dimens.size24),
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
strokeWidth = TangemTheme.dimens.size4,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()),
|
||||
) {
|
||||
if (buttonIcon is TangemButtonIconPosition.Start) {
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
maxLines = 1,
|
||||
)
|
||||
if (buttonIcon is TangemButtonIconPosition.End) {
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
internal class TangemButtonColors(
|
||||
private val backgroundColor: Color,
|
||||
private val contentColor: Color,
|
||||
private val disabledBackgroundColor: Color,
|
||||
private val disabledContentColor: Color,
|
||||
) : ButtonColors {
|
||||
|
||||
@Composable
|
||||
override fun backgroundColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(newValue = if (enabled) backgroundColor else disabledBackgroundColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun contentColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(newValue = if (enabled) contentColor else disabledContentColor)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
internal sealed interface TangemButtonIconPosition {
|
||||
val iconResId: Int?
|
||||
|
||||
data class Start(@DrawableRes override val iconResId: Int) : TangemButtonIconPosition
|
||||
|
||||
data class End(@DrawableRes override val iconResId: Int) : TangemButtonIconPosition
|
||||
|
||||
object None : TangemButtonIconPosition {
|
||||
@DrawableRes
|
||||
override val iconResId: Int? = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
internal enum class TangemButtonSize {
|
||||
Default,
|
||||
Text,
|
||||
Selector,
|
||||
Action,
|
||||
RoundedAction,
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toHeightDp(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.size48
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.size40
|
||||
TangemButtonSize.Selector -> TangemTheme.dimens.size24
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> TangemTheme.dimens.size36
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toShape(): Shape = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toIconPadding(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Selector -> 0.dp
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> TangemTheme.dimens.spacing8
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition): PaddingValues {
|
||||
val horizontalPadding = this.toHorizontalContentPadding(icon = icon)
|
||||
|
||||
return when (this) {
|
||||
TangemButtonSize.Default -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing14,
|
||||
bottom = TangemTheme.dimens.spacing14,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Text -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Selector -> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing0_5,
|
||||
bottom = TangemTheme.dimens.spacing0_5,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> PaddingValues(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing8,
|
||||
start = horizontalPadding.first,
|
||||
end = horizontalPadding.second,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
internal fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIconPosition): Pair<Dp, Dp> {
|
||||
return when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
|
||||
TangemButtonSize.Text -> when (icon) {
|
||||
is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
|
||||
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
|
||||
is TangemButtonIconPosition.End -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing14
|
||||
}
|
||||
TangemButtonSize.Selector -> TangemTheme.dimens.spacing0_5 to TangemTheme.dimens.spacing0_5
|
||||
TangemButtonSize.Action,
|
||||
TangemButtonSize.RoundedAction,
|
||||
-> when (icon) {
|
||||
is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing24
|
||||
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing24
|
||||
is TangemButtonIconPosition.End -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.ButtonElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
internal object TangemButtonsDefaults {
|
||||
|
||||
val elevation: ButtonElevation
|
||||
@Composable get() = ButtonDefaults.elevation(
|
||||
defaultElevation = TangemTheme.dimens.elevation0,
|
||||
pressedElevation = TangemTheme.dimens.elevation0,
|
||||
)
|
||||
|
||||
val primaryButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.button.primary,
|
||||
contentColor = TangemTheme.colors.text.primary2,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val secondaryButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.button.secondary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val defaultTextButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.secondary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val warningTextButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.warning,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val selectorButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.tertiary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val backgroundButtonColors: ButtonColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemButtonColors(
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,14 +2,16 @@ package com.tangem.core.ui.components.notifications
|
|||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -32,19 +34,24 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
*/
|
||||
@Composable
|
||||
fun Notification(state: NotificationState, modifier: Modifier = Modifier) {
|
||||
Surface(
|
||||
onClick = if (state is NotificationState.Action) {
|
||||
state.onClick
|
||||
} else {
|
||||
{}
|
||||
},
|
||||
modifier = modifier,
|
||||
enabled = when (state) {
|
||||
is NotificationState.Simple -> false
|
||||
is NotificationState.Action -> true
|
||||
},
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius18),
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(shape = RoundedCornerShape(size = TangemTheme.dimens.radius18))
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius18),
|
||||
)
|
||||
.clickable(
|
||||
enabled = when (state) {
|
||||
is NotificationState.Simple -> false
|
||||
is NotificationState.Action -> true
|
||||
},
|
||||
onClick = if (state is NotificationState.Action) {
|
||||
state.onClick
|
||||
} else {
|
||||
{}
|
||||
},
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ fun getActiveIconRes(blockchainId: String): Int {
|
|||
"terra", "terra-luna" -> R.drawable.img_terra_22
|
||||
"terra-2", "terra-luna-2" -> R.drawable.img_terra2_22
|
||||
"cronos" -> R.drawable.img_cronos_22
|
||||
"TELOS", "TELOS/test" -> R.drawable.img_telos_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +85,7 @@ fun getActiveIconResByCoinId(coinId: String, networkId: String): Int {
|
|||
"ravencoin" -> R.drawable.img_ravencoin_22
|
||||
"terra" -> R.drawable.img_terra_22
|
||||
"terra-2" -> R.drawable.img_terra2_22
|
||||
"telos" -> R.drawable.img_telos_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.core.ui.extensions
|
||||
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/**
|
||||
|
|
@ -24,6 +26,8 @@ sealed interface TextReference {
|
|||
*/
|
||||
data class Res(@StringRes val id: Int, val formatArgs: WrappedList<Any> = WrappedList(emptyList())) : TextReference
|
||||
|
||||
data class PluralRes(@PluralsRes val id: Int, val count: Int, val formatArgs: WrappedList<Any>) : TextReference
|
||||
|
||||
/**
|
||||
* Text string
|
||||
*
|
||||
|
|
@ -38,6 +42,7 @@ sealed interface TextReference {
|
|||
fun TextReference.resolveReference(): String {
|
||||
return when (this) {
|
||||
is TextReference.Res -> stringResource(id, *formatArgs.toTypedArray())
|
||||
is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray())
|
||||
is TextReference.Str -> value
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ data class TangemDimens internal constructor(
|
|||
val elevation24: Dp = 24.dp,
|
||||
// endregion Elevation
|
||||
// region Radius
|
||||
val radius0: Dp = 0.dp,
|
||||
val radius2: Dp = 2.dp,
|
||||
val radius3: Dp = 3.dp,
|
||||
val radius4: Dp = 4.dp,
|
||||
|
|
@ -29,6 +30,7 @@ data class TangemDimens internal constructor(
|
|||
val radius12: Dp = 12.dp,
|
||||
val radius16: Dp = 16.dp,
|
||||
val radius18: Dp = 18.dp,
|
||||
val radius24: Dp = 24.dp,
|
||||
val radius28: Dp = 28.dp,
|
||||
// endregion Radius
|
||||
// region Size
|
||||
|
|
|
|||
15
core/ui/src/main/res/drawable/img_telos_22.xml
Normal file
15
core/ui/src/main/res/drawable/img_telos_22.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<path
|
||||
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"
|
||||
android:fillColor="#5C40A6"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M14.862,5.607C14.909,5.486 14.825,5.354 14.695,5.346L12.507,5.2C12.44,5.196 12.375,5.227 12.336,5.283L11.756,6.118C11.72,6.17 11.66,6.201 11.597,6.201L7.745,6.178C7.556,6.177 7.373,6.245 7.231,6.37L6.163,7.308C5.925,7.517 5.839,7.849 5.945,8.147L6.454,9.575C6.457,9.583 6.455,9.591 6.451,9.597C6.165,9.977 5.998,10.434 5.973,10.909L5.964,11.071C5.91,12.08 6.029,13.091 6.316,14.061C6.552,14.859 6.823,15.775 6.989,16.336C7.076,16.632 7.379,16.807 7.679,16.735C8.325,16.58 9.435,16.313 10.309,16.102C11.078,15.917 11.809,15.602 12.472,15.17L12.886,14.901C13.285,14.641 13.597,14.269 13.784,13.831C13.787,13.824 13.793,13.818 13.801,13.817L15.292,13.544C15.603,13.487 15.848,13.246 15.91,12.936L16.188,11.541C16.225,11.356 16.193,11.163 16.097,11L14.288,7.909C14.17,7.706 14.15,7.461 14.234,7.242L14.862,5.607Z"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#ffffff"/>
|
||||
</vector>
|
||||
10
core/utils/src/main/java/com/tangem/utils/extensions/Any.kt
Normal file
10
core/utils/src/main/java/com/tangem/utils/extensions/Any.kt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun <T> T.toWeakReference(): WeakReference<T> {
|
||||
return WeakReference(this)
|
||||
}
|
||||
|
|
@ -68,6 +68,10 @@ class UsedCardsPrefStorage internal constructor(
|
|||
return cardInfo.isActivationStarted && !cardInfo.isActivationFinished
|
||||
}
|
||||
|
||||
fun hadFinishedActivation(): Boolean {
|
||||
return restore().any { it.isActivationFinished }
|
||||
}
|
||||
|
||||
private fun findCardInfo(
|
||||
cardId: String,
|
||||
list: MutableList<DataSourceUsedCardInfo>? = null,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
|||
"terra" -> Blockchain.TerraV1
|
||||
"terra-2" -> Blockchain.TerraV2
|
||||
"cronos" -> Blockchain.Cronos
|
||||
"telos" -> Blockchain.Telos
|
||||
"telos/test" -> Blockchain.TelosTestnet
|
||||
"azero" -> Blockchain.AlephZero
|
||||
"azero/test" -> Blockchain.AlephZeroTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -122,6 +126,10 @@ fun Blockchain.toNetworkId(): String {
|
|||
Blockchain.TerraV1 -> "terra"
|
||||
Blockchain.TerraV2 -> "terra-2"
|
||||
Blockchain.Cronos -> "cronos"
|
||||
Blockchain.Telos -> "telos"
|
||||
Blockchain.TelosTestnet -> "telos/test"
|
||||
Blockchain.AlephZero -> "azero"
|
||||
Blockchain.AlephZeroTestnet -> "azero/test"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,6 +170,8 @@ fun Blockchain.toCoinId(): String {
|
|||
Blockchain.TerraV1 -> "terra-luna"
|
||||
Blockchain.TerraV2 -> "terra-luna-2"
|
||||
Blockchain.Cronos -> "crypto-com-chain"
|
||||
Blockchain.Telos, Blockchain.TelosTestnet -> "telos"
|
||||
Blockchain.AlephZero, Blockchain.AlephZeroTestnet -> "azero"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ private class DomainGlobalReducer : ReStoreReducer<DomainGlobalState> {
|
|||
return when (action) {
|
||||
is DomainGlobalAction.SaveScanNoteResponse -> {
|
||||
val card = action.scanResponse.card
|
||||
// TODO: AuthHeaders: try to remove it, because now we can use headers with dynamic values
|
||||
state.networkServices.tangemTechService.addAuthenticationHeader(
|
||||
RequestHeader.AuthenticationHeader(
|
||||
object : AuthProvider {
|
||||
|
|
|
|||
1
features/learn2earn/api/.gitignore
vendored
Normal file
1
features/learn2earn/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
4
features/learn2earn/api/build.gradle.kts
Normal file
4
features/learn2earn/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
id("configuration")
|
||||
}
|
||||
1
features/learn2earn/impl/.gitignore
vendored
Normal file
1
features/learn2earn/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
55
features/learn2earn/impl/build.gradle.kts
Normal file
55
features/learn2earn/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(project(":common"))
|
||||
implementation(project(":core:featuretoggles"))
|
||||
implementation(project(":core:datasource"))
|
||||
implementation(project(":core:utils"))
|
||||
implementation(project(":core:ui"))
|
||||
implementation(project(":core:res"))
|
||||
implementation(project(":data:source:preferences"))
|
||||
implementation(project(":libs:auth"))
|
||||
implementation(project(":libs:crypto"))
|
||||
|
||||
implementation(deps.material)
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.androidx.appCompat)
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.androidx.browser)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.material)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
|
||||
/** Preferences */
|
||||
implementation(deps.krateSharedPref)
|
||||
|
||||
/** Network */
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.retrofit)
|
||||
implementation(deps.retrofit.moshi)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.timber)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
3
features/learn2earn/impl/src/main/AndroidManifest.xml
Normal file
3
features/learn2earn/impl/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.feature.learn2earn.impl">
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package com.tangem.feature.learn2earn.data
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.promotion.PromotionApi
|
||||
import com.tangem.datasource.api.promotion.models.*
|
||||
import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage
|
||||
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
|
||||
import com.tangem.feature.learn2earn.data.models.PromoUserData
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultLearn2earnRepository(
|
||||
private val preferencesStorage: Learn2earnPreferenceStorage,
|
||||
private val api: PromotionApi,
|
||||
private val dispatchers: AppCoroutineDispatcherProvider,
|
||||
moshi: Moshi,
|
||||
) : Learn2earnRepository {
|
||||
|
||||
private val promotionInfoAdapter = moshi.adapter(PromotionInfoResponse::class.java)
|
||||
private val userDataAdapter = moshi.adapter(PromoUserData::class.java)
|
||||
|
||||
private var userData: PromoUserData = restoreUserData()
|
||||
|
||||
override fun getUserData(): PromoUserData {
|
||||
return userData
|
||||
}
|
||||
|
||||
override fun updateUserData(userData: PromoUserData) {
|
||||
this.userData = userData
|
||||
saveUserData(userData)
|
||||
}
|
||||
|
||||
override fun getProgramName(): String {
|
||||
return PROGRAM_NAME
|
||||
}
|
||||
|
||||
override suspend fun getPromotionInfo(): Result<PromotionInfoResponse> {
|
||||
return withContext(dispatchers.io) {
|
||||
val promotionInfo = restorePromotionInfo()
|
||||
val data = promotionInfo?.getData(userData.promoCode)
|
||||
if (data?.status == PromotionInfoResponse.Status.FINISHED) {
|
||||
Result.success(promotionInfo)
|
||||
} else {
|
||||
runCatching { api.getPromotionInfo(getProgramName()) }
|
||||
.fold(
|
||||
onSuccess = { infoResponse ->
|
||||
savePromotionInfo(infoResponse)
|
||||
Result.success(infoResponse)
|
||||
},
|
||||
onFailure = { exception -> Result.failure(exception) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun validate(walletId: String): ValidateResponse {
|
||||
return withContext(dispatchers.io) {
|
||||
api.validate(ValidateRequestBody(walletId, getProgramName()))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun requestAward(walletId: String, address: String): AwardResponse {
|
||||
return withContext(dispatchers.io) {
|
||||
api.requestAward(AwardRequestBody(walletId, address, getProgramName()))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun validateCode(walletId: String, code: String?): CodeValidateResponse {
|
||||
return withContext(dispatchers.io) {
|
||||
api.validateCode(CodeValidateRequestBody(walletId, code))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun requestAwardByCode(walletId: String, address: String, code: String?): CodeAwardResponse {
|
||||
return withContext(dispatchers.io) {
|
||||
api.requestAwardByCode(CodeAwardRequestBody(walletId, address, code))
|
||||
}
|
||||
}
|
||||
|
||||
private fun restorePromotionInfo(): PromotionInfoResponse? {
|
||||
return try {
|
||||
preferencesStorage.promotionInfo?.let { promotionInfoAdapter.fromJson(it) }
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
preferencesStorage.promotionInfo = null
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun savePromotionInfo(infoResponse: PromotionInfoResponse) {
|
||||
try {
|
||||
preferencesStorage.promotionInfo = promotionInfoAdapter.toJson(infoResponse)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreUserData(): PromoUserData {
|
||||
return try {
|
||||
preferencesStorage.userData?.let { userDataAdapter.fromJson(it) }
|
||||
?: createEmptyUserData().apply { saveUserData(this) }
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
createEmptyUserData().apply { saveUserData(this) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveUserData(userData: PromoUserData) {
|
||||
try {
|
||||
preferencesStorage.userData = userDataAdapter.toJson(userData)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEmptyUserData(): PromoUserData = PromoUserData(
|
||||
promoCode = null,
|
||||
isRegisteredInPromotion = false,
|
||||
isAlreadyReceivedAward = false,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val PROGRAM_NAME: String = "1inch"
|
||||
}
|
||||
}
|
||||
|
||||
private fun PromotionInfoResponse.getData(promoCode: String?): PromotionInfoResponse.Data? {
|
||||
return if (promoCode == null) {
|
||||
newCard
|
||||
} else {
|
||||
oldCard
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.feature.learn2earn.data
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage
|
||||
import hu.autsoft.krate.SimpleKrate
|
||||
import hu.autsoft.krate.default.withDefault
|
||||
import hu.autsoft.krate.stringPref
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultPreferenceStorage(
|
||||
context: Context,
|
||||
) : SimpleKrate(context = context, name = "Lear2earnPromotion"), Learn2earnPreferenceStorage {
|
||||
|
||||
override var promotionInfo: String? by stringPref().withDefault(null)
|
||||
|
||||
override var userData: String? by stringPref().withDefault(null)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.feature.learn2earn.data.api
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Learn2earnPreferenceStorage {
|
||||
|
||||
var promotionInfo: String?
|
||||
|
||||
var userData: String?
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.feature.learn2earn.data.api
|
||||
|
||||
import com.tangem.datasource.api.promotion.models.*
|
||||
import com.tangem.feature.learn2earn.data.models.PromoUserData
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Learn2earnRepository {
|
||||
|
||||
fun getUserData(): PromoUserData
|
||||
|
||||
fun updateUserData(userData: PromoUserData)
|
||||
|
||||
fun getProgramName(): String
|
||||
|
||||
suspend fun getPromotionInfo(): Result<PromotionInfoResponse>
|
||||
|
||||
suspend fun validate(walletId: String): ValidateResponse
|
||||
|
||||
suspend fun requestAward(walletId: String, address: String): AwardResponse
|
||||
|
||||
suspend fun validateCode(walletId: String, code: String?): CodeValidateResponse
|
||||
|
||||
suspend fun requestAwardByCode(walletId: String, address: String, code: String?): CodeAwardResponse
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.feature.learn2earn.data.di
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.promotion.PromotionApi
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.di.PromotionOneInch
|
||||
import com.tangem.feature.learn2earn.data.DefaultLearn2earnRepository
|
||||
import com.tangem.feature.learn2earn.data.DefaultPreferenceStorage
|
||||
import com.tangem.feature.learn2earn.data.api.Learn2earnPreferenceStorage
|
||||
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class Learn2earnDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePreferenceStorage(@ApplicationContext context: Context): Learn2earnPreferenceStorage {
|
||||
return DefaultPreferenceStorage(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRepository(
|
||||
preferenceStorage: Learn2earnPreferenceStorage,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@PromotionOneInch promotionApi: PromotionApi,
|
||||
dispatchers: AppCoroutineDispatcherProvider,
|
||||
): Learn2earnRepository {
|
||||
return DefaultLearn2earnRepository(
|
||||
preferencesStorage = preferenceStorage,
|
||||
api = promotionApi,
|
||||
dispatchers = dispatchers,
|
||||
moshi = moshi,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.feature.learn2earn.data.models
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class PromoUserData(
|
||||
val promoCode: String?,
|
||||
val isRegisteredInPromotion: Boolean,
|
||||
val isAlreadyReceivedAward: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
package com.tangem.feature.learn2earn.domain
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.datasource.api.promotion.models.AbstractPromotionResponse
|
||||
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
|
||||
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
|
||||
import com.tangem.feature.learn2earn.data.models.PromoUserData
|
||||
import com.tangem.feature.learn2earn.domain.api.*
|
||||
import com.tangem.feature.learn2earn.domain.models.Promotion
|
||||
import com.tangem.feature.learn2earn.domain.models.PromotionError
|
||||
import com.tangem.feature.learn2earn.domain.models.toDomainError
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DefaultLearn2earnInteractor(
|
||||
private val repository: Learn2earnRepository,
|
||||
private val userWalletManager: UserWalletManager,
|
||||
private val derivationManager: DerivationManager,
|
||||
dependencyProvider: Learn2earnDependencyProvider,
|
||||
) : Learn2earnInteractor {
|
||||
|
||||
override var webViewResultHandler: WebViewResultHandler? = null
|
||||
|
||||
private lateinit var promotion: Promotion
|
||||
|
||||
private val webViewUriBuilder: WebViewUriBuilder = WebViewUriBuilder(
|
||||
authCredentialsProvider = dependencyProvider.getWebViewAuthCredentialsProvider(),
|
||||
userCountryCodeProvider = dependencyProvider.getUserCountryCodeProvider(),
|
||||
promoCodeProvider = { repository.getUserData().promoCode },
|
||||
)
|
||||
|
||||
override suspend fun init() {
|
||||
initPromotionInfo()
|
||||
}
|
||||
|
||||
override fun isUserHadPromoCode(): Boolean {
|
||||
return repository.getUserData().promoCode != null
|
||||
}
|
||||
|
||||
override fun isNeedToShowViewOnStoriesScreen(): Boolean {
|
||||
return promotionIsActive()
|
||||
}
|
||||
|
||||
override suspend fun isNeedToShowViewOnMainScreen(): Boolean {
|
||||
if (!promotionIsActive()) return false
|
||||
|
||||
val response = repository.validate(userWalletManager.getWalletId())
|
||||
return response.valid == true
|
||||
}
|
||||
|
||||
override fun isUserRegisteredInPromotion(): Boolean {
|
||||
return repository.getUserData().isRegisteredInPromotion
|
||||
}
|
||||
|
||||
override fun getAwardAmount(): Int {
|
||||
val promoCode = repository.getUserData().promoCode
|
||||
val awardAmount = promotion.getPromotionInfo().getData(promoCode).award.toInt()
|
||||
|
||||
return awardAmount
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override suspend fun requestAward(): Result<Unit> {
|
||||
val awardCurrency = getCurrencyForAward()
|
||||
?: return Result.failure(PromotionError.UnknownError("Currency for award is null"))
|
||||
|
||||
val walletId = userWalletManager.getWalletId()
|
||||
val promoCode = repository.getUserData().promoCode
|
||||
|
||||
val error = if (promoCode == null) {
|
||||
requestAward(walletId, awardCurrency)
|
||||
} else {
|
||||
requestAwardWithPromoCode(walletId, awardCurrency, promoCode)
|
||||
}
|
||||
|
||||
return if (error == null) {
|
||||
Result.success(Unit)
|
||||
} else {
|
||||
val domainError = error.toDomainError()
|
||||
handlePromotionError(domainError)
|
||||
Result.failure(domainError)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestAward(walletId: String, awardCurrency: Currency): AbstractPromotionResponse.Error? {
|
||||
val validateResponse = repository.validate(walletId)
|
||||
return if (validateResponse.valid == true) {
|
||||
val address = getWalletAddressForAward(awardCurrency)
|
||||
val awardResponse = repository.requestAward(walletId, address)
|
||||
if (awardResponse.status == true) {
|
||||
updateUserData { it.copy(isAlreadyReceivedAward = true) }
|
||||
null
|
||||
} else {
|
||||
awardResponse.error
|
||||
}
|
||||
} else {
|
||||
validateResponse.error
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestAwardWithPromoCode(
|
||||
walletId: String,
|
||||
awardCurrency: Currency,
|
||||
promoCode: String,
|
||||
): AbstractPromotionResponse.Error? {
|
||||
val codeValidateResponse = repository.validateCode(walletId, promoCode)
|
||||
return if (codeValidateResponse.valid == true) {
|
||||
val address = getWalletAddressForAward(awardCurrency)
|
||||
val awardWithCodeResponse = repository.requestAwardByCode(walletId, address, promoCode)
|
||||
if (awardWithCodeResponse.status == true) {
|
||||
updateUserData { it.copy(isAlreadyReceivedAward = true) }
|
||||
null
|
||||
} else {
|
||||
awardWithCodeResponse.error
|
||||
}
|
||||
} else {
|
||||
codeValidateResponse.error
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getWalletAddressForAward(currency: Currency): String {
|
||||
val derivationPath = deriveOrAddTokens(currency)
|
||||
return userWalletManager.getWalletAddress(currency.networkId, derivationPath)
|
||||
}
|
||||
|
||||
private suspend fun deriveOrAddTokens(currency: Currency): String {
|
||||
val derivationPath = derivationManager.getDerivationPathForBlockchain(currency.networkId)
|
||||
if (derivationPath.isNullOrEmpty()) error("derivationPath shouldn't be empty")
|
||||
|
||||
if (!derivationManager.hasDerivation(currency.networkId, derivationPath)) {
|
||||
derivationManager.deriveMissingBlockchains(currency)
|
||||
}
|
||||
if (!userWalletManager.isTokenAdded(currency, derivationPath)) {
|
||||
userWalletManager.addToken(currency, derivationPath)
|
||||
}
|
||||
return derivationPath
|
||||
}
|
||||
|
||||
private fun handlePromotionError(error: PromotionError) {
|
||||
when (error) {
|
||||
is PromotionError.CodeNotFound -> {
|
||||
updateUserData { it.copy(promoCode = null) }
|
||||
}
|
||||
is PromotionError.CardAlreadyHasAward, is PromotionError.WalletAlreadyHasAward,
|
||||
is PromotionError.CodeWasAlreadyUsed,
|
||||
-> {
|
||||
updateUserData { it.copy(isAlreadyReceivedAward = true) }
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildUriForNewUser(): Uri {
|
||||
return webViewUriBuilder.buildUriForNewUser()
|
||||
}
|
||||
|
||||
override fun buildUriForOldUser(): Uri {
|
||||
return webViewUriBuilder.buildUriForOldUser()
|
||||
}
|
||||
|
||||
override fun getBasicAuthHeaders(): ArrayList<String> {
|
||||
return HeadersConverter().convert(webViewUriBuilder.getBasicAuthHeaders())
|
||||
}
|
||||
|
||||
override fun handleRedirect(uri: Uri): RedirectConsequences {
|
||||
if (webViewUriBuilder.isReadyForExistedCardAwardRedirect(uri)) {
|
||||
updateUserData { it.copy(isRegisteredInPromotion = true) }
|
||||
webViewResultHandler?.handleResult(WebViewResult.ReadyForAward)
|
||||
return RedirectConsequences.FINISH_SESSION
|
||||
}
|
||||
|
||||
return if (webViewUriBuilder.isPromoCodeRedirect(uri)) {
|
||||
webViewUriBuilder.extractPromoCode(uri)?.let { code ->
|
||||
updateUserData {
|
||||
it.copy(
|
||||
promoCode = code,
|
||||
isRegisteredInPromotion = true,
|
||||
)
|
||||
}
|
||||
webViewResultHandler?.handleResult(WebViewResult.PromoCodeReceived)
|
||||
}
|
||||
RedirectConsequences.NOTHING
|
||||
} else {
|
||||
RedirectConsequences.PROCEED
|
||||
}
|
||||
}
|
||||
|
||||
private fun promotionIsActive(): Boolean {
|
||||
val userData = repository.getUserData()
|
||||
val isActive = when {
|
||||
userData.isAlreadyReceivedAward -> false
|
||||
promotion.isError() -> false
|
||||
else -> {
|
||||
val data = promotion.getPromotionInfo().getData(userData.promoCode)
|
||||
data.status == PromotionInfoResponse.Status.ACTIVE
|
||||
}
|
||||
}
|
||||
|
||||
return isActive
|
||||
}
|
||||
|
||||
private suspend fun initPromotionInfo() {
|
||||
promotion = repository.getPromotionInfo()
|
||||
.fold(
|
||||
onSuccess = { response ->
|
||||
val responseError = response.error
|
||||
if (responseError == null) {
|
||||
val npeMessage = { "Shouldn't be null" }
|
||||
Promotion(
|
||||
info = Promotion.PromotionInfo(
|
||||
newCard = requireNotNull(response.newCard, npeMessage),
|
||||
oldCard = requireNotNull(response.oldCard, npeMessage),
|
||||
awardPaymentToken = requireNotNull(response.awardPaymentToken, npeMessage),
|
||||
),
|
||||
error = null,
|
||||
)
|
||||
} else {
|
||||
Promotion(
|
||||
info = null,
|
||||
error = responseError.toDomainError(),
|
||||
)
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
Promotion(
|
||||
info = null,
|
||||
error = PromotionError.NetworkUnreachable,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCurrencyForAward(): Currency? {
|
||||
val token = promotion.info?.awardPaymentToken ?: return null
|
||||
|
||||
return Currency.NonNativeToken(
|
||||
id = token.id,
|
||||
name = token.name,
|
||||
symbol = token.symbol,
|
||||
networkId = token.networkId,
|
||||
contractAddress = token.contractAddress,
|
||||
decimalCount = token.decimalCount,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateUserData(updateBlock: (PromoUserData) -> PromoUserData): PromoUserData {
|
||||
return updateBlock(repository.getUserData()).apply {
|
||||
repository.updateUserData(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Promotion.PromotionInfo.getData(promoCode: String?): PromotionInfoResponse.Data {
|
||||
return if (promoCode == null) {
|
||||
newCard
|
||||
} else {
|
||||
oldCard
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.feature.learn2earn.domain
|
||||
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
/**
|
||||
* A converter that helps prepare a list of headers to be added to the Bundle.
|
||||
* ArrayList is used to prevent cast exceptions when the convert result is placed in a Bundle.
|
||||
* @see Learn2earnRouter#openWebView() putStringArrayListExtra
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class HeadersConverter : TwoWayConverter<Map<String, String>, ArrayList<String>> {
|
||||
|
||||
override fun convert(value: Map<String, String>): ArrayList<String> {
|
||||
val list = value.toList().map {
|
||||
"${it.first}$SPLIT_SIGN${it.second}"
|
||||
}
|
||||
return ArrayList(list)
|
||||
}
|
||||
|
||||
override fun convertBack(value: ArrayList<String>): Map<String, String> {
|
||||
return value.map { it.split(SPLIT_SIGN) }.associate { it[0] to it[1] }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val SPLIT_SIGN = ":"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.feature.learn2earn.domain
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.feature.learn2earn.impl.BuildConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WebViewUriBuilder(
|
||||
private val authCredentialsProvider: () -> String?,
|
||||
private val userCountryCodeProvider: () -> String,
|
||||
private val promoCodeProvider: () -> String?,
|
||||
) {
|
||||
|
||||
fun buildUriForNewUser(): Uri {
|
||||
val builder = makeWebViewUriBuilder()
|
||||
.appendQueryParameter("type", QUERY_NEW_CARD)
|
||||
|
||||
promoCodeProvider.invoke()?.let {
|
||||
builder.appendQueryParameter("code", it)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun buildUriForOldUser(): Uri {
|
||||
val builder = makeWebViewUriBuilder()
|
||||
.appendQueryParameter("type", QUERY_EXISTED_CARD)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun getBasicAuthHeaders(): Map<String, String> {
|
||||
val credentials = authCredentialsProvider.invoke()
|
||||
return if (credentials == null) {
|
||||
mapOf()
|
||||
} else {
|
||||
mapOf("Authorization" to "Basic $credentials")
|
||||
}
|
||||
}
|
||||
|
||||
fun isPromoCodeRedirect(uri: Uri): Boolean {
|
||||
return uri.toString().contains(SUFFIX_CODE_CREATED)
|
||||
}
|
||||
|
||||
fun isReadyForExistedCardAwardRedirect(uri: Uri): Boolean {
|
||||
return uri.toString().endsWith(SUFFIX_READY_FOR_AWARD)
|
||||
}
|
||||
|
||||
fun extractPromoCode(uri: Uri): String? {
|
||||
val url = uri.toString()
|
||||
|
||||
return if (isPromoCodeRedirect(uri)) {
|
||||
val replaceString = makeWebViewUriBuilder().build().toString() + SUFFIX_CODE_CREATED
|
||||
val code = url.replace(replaceString, "")
|
||||
code
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeWebViewUriBuilder(): Uri.Builder {
|
||||
val builder = Uri.Builder()
|
||||
builder.scheme(SCHEME)
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
builder.authority(DEV_BASE_URL)
|
||||
builder.appendPath(userCountryCodeProvider.invoke())
|
||||
builder.appendPath(DEV_PATH_PROMOTION)
|
||||
} else {
|
||||
builder.authority(BASE_URL)
|
||||
builder.appendPath(userCountryCodeProvider.invoke())
|
||||
builder.appendPath(PATH_PROMOTION)
|
||||
}
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val SCHEME = "https"
|
||||
|
||||
const val BASE_URL = "tangem.com"
|
||||
const val PATH_PROMOTION = "promotion"
|
||||
|
||||
const val QUERY_NEW_CARD = "new-card"
|
||||
const val QUERY_EXISTED_CARD = "existed-card"
|
||||
|
||||
const val DEV_BASE_URL = "devweb.tangem.com"
|
||||
const val DEV_PATH_PROMOTION = "promotion-test"
|
||||
|
||||
const val SUFFIX_CODE_CREATED = "/code-created?code="
|
||||
const val SUFFIX_READY_FOR_AWARD = "/ready-for-existed-card-award"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.feature.learn2earn.domain.api
|
||||
|
||||
/**
|
||||
* Interface, a wrapper that allows us to get values from the AppStateHolder located in the app module
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Learn2earnDependencyProvider {
|
||||
|
||||
fun getUserCountryCodeProvider(): () -> String
|
||||
|
||||
fun getWebViewAuthCredentialsProvider(): () -> String?
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.feature.learn2earn.domain.api
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Learn2earnInteractor : WebViewRedirectHandler {
|
||||
|
||||
var webViewResultHandler: WebViewResultHandler?
|
||||
|
||||
suspend fun init()
|
||||
|
||||
fun isUserHadPromoCode(): Boolean
|
||||
|
||||
fun isNeedToShowViewOnStoriesScreen(): Boolean
|
||||
|
||||
suspend fun isNeedToShowViewOnMainScreen(): Boolean
|
||||
|
||||
fun isUserRegisteredInPromotion(): Boolean
|
||||
|
||||
fun getAwardAmount(): Int
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
suspend fun requestAward(): Result<Unit>
|
||||
|
||||
fun buildUriForNewUser(): Uri
|
||||
|
||||
fun buildUriForOldUser(): Uri
|
||||
|
||||
fun getBasicAuthHeaders(): ArrayList<String>
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.feature.learn2earn.domain.api
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
/**
|
||||
* Handler that helps determine what actions to take inside the WebView of the Learn2earnWebViewActivity after
|
||||
* a redirect has been processed by WebViewRedirectHandler
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface WebViewRedirectHandler {
|
||||
fun handleRedirect(uri: Uri): RedirectConsequences
|
||||
}
|
||||
|
||||
enum class RedirectConsequences {
|
||||
NOTHING,
|
||||
PROCEED,
|
||||
FINISH_SESSION,
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.feature.learn2earn.domain.api
|
||||
|
||||
/**
|
||||
* Handler that helps determine a result of the Learn2earnWebViewActivity webView actions.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface WebViewResultHandler {
|
||||
fun handleResult(result: WebViewResult)
|
||||
}
|
||||
|
||||
sealed class WebViewResult {
|
||||
object PromoCodeReceived : WebViewResult()
|
||||
object ReadyForAward : WebViewResult()
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.feature.learn2earn.domain.di
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.feature.learn2earn.data.api.Learn2earnRepository
|
||||
import com.tangem.feature.learn2earn.domain.DefaultLearn2earnInteractor
|
||||
import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider
|
||||
import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
|
||||
import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler
|
||||
import com.tangem.feature.learn2earn.presentation.Learn2earnRouter
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class Learn2earnDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRouter(@ApplicationContext context: Context): Learn2earnRouter {
|
||||
return Learn2earnRouter(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideInteractor(
|
||||
repository: Learn2earnRepository,
|
||||
dependencyProvider: Learn2earnDependencyProvider,
|
||||
userWalletManager: UserWalletManager,
|
||||
derivationManager: DerivationManager,
|
||||
): Learn2earnInteractor {
|
||||
return DefaultLearn2earnInteractor(
|
||||
repository = repository,
|
||||
userWalletManager = userWalletManager,
|
||||
derivationManager = derivationManager,
|
||||
dependencyProvider = dependencyProvider,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWebViewHandler(interactor: Learn2earnInteractor): WebViewRedirectHandler {
|
||||
return interactor
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.feature.learn2earn.domain.models
|
||||
|
||||
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class Promotion(
|
||||
val info: PromotionInfo?,
|
||||
val error: PromotionError?,
|
||||
) {
|
||||
|
||||
@Throws(NullPointerException::class)
|
||||
fun getPromotionInfo(): PromotionInfo = info!!
|
||||
|
||||
fun isError(): Boolean = error != null
|
||||
|
||||
data class PromotionInfo(
|
||||
val newCard: PromotionInfoResponse.Data,
|
||||
val oldCard: PromotionInfoResponse.Data,
|
||||
val awardPaymentToken: PromotionInfoResponse.TokenData,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.feature.learn2earn.domain.models
|
||||
|
||||
import com.tangem.datasource.api.promotion.models.AbstractPromotionResponse
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class PromotionError(val code: Int, val description: String) : Throwable(
|
||||
message = "code: $code, description: $description",
|
||||
) {
|
||||
|
||||
object NetworkUnreachable : PromotionError(code = 0, "Network or service not available")
|
||||
|
||||
class CodeNotFound(code: Int, description: String) : PromotionError(code, description)
|
||||
class CodeWasNotAppliedInShop(code: Int, description: String) : PromotionError(code, description)
|
||||
class CodeWasAlreadyUsed(code: Int, description: String) : PromotionError(code, description)
|
||||
class WalletAlreadyHasAward(code: Int, description: String) : PromotionError(code, description)
|
||||
class CardAlreadyHasAward(code: Int, description: String) : PromotionError(code, description)
|
||||
class ProgramNotFound(code: Int, description: String) : PromotionError(code, description)
|
||||
class ProgramWasEnd(code: Int, description: String) : PromotionError(code, description)
|
||||
|
||||
class UnknownError(description: String) : PromotionError(code = -1, description)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
internal fun AbstractPromotionResponse.Error.toDomainError(): PromotionError = when (code) {
|
||||
101 -> PromotionError.CodeNotFound(code, message)
|
||||
102 -> PromotionError.CodeWasNotAppliedInShop(code, message)
|
||||
103 -> PromotionError.CodeWasAlreadyUsed(code, message)
|
||||
104 -> PromotionError.WalletAlreadyHasAward(code, message)
|
||||
105 -> PromotionError.CardAlreadyHasAward(code, message)
|
||||
106 -> PromotionError.ProgramNotFound(code, message)
|
||||
107 -> PromotionError.ProgramWasEnd(code, message)
|
||||
else -> PromotionError.UnknownError("$code:$message")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue