Updated on 2026-08-14
This commit is contained in:
commit
9c99bab352
92 changed files with 2908 additions and 417 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"))
|
||||
|
|
|
|||
|
|
@ -138,6 +138,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"
|
||||
|
|
|
|||
|
|
@ -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="
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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() },
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.feature.learn2earn.presentation
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.tangem.feature.learn2earn.presentation.webView.Learn2earnWebViewActivity
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class Learn2earnRouter(
|
||||
private val context: Context,
|
||||
) {
|
||||
|
||||
fun openWebView(uri: Uri, rawHeaders: ArrayList<String>) {
|
||||
val intent = Intent(context, Learn2earnWebViewActivity::class.java)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(Learn2earnWebViewActivity.EXTRA_WEB_URI, uri.toString())
|
||||
.putStringArrayListExtra(Learn2earnWebViewActivity.EXTRA_WEB_HEADERS, rawHeaders)
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
package com.tangem.feature.learn2earn.presentation
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
|
||||
import com.tangem.feature.learn2earn.domain.api.WebViewResult
|
||||
import com.tangem.feature.learn2earn.domain.api.WebViewResultHandler
|
||||
import com.tangem.feature.learn2earn.domain.models.PromotionError
|
||||
import com.tangem.feature.learn2earn.presentation.ui.state.*
|
||||
import com.tangem.lib.crypto.models.errors.UserCancelledException
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@HiltViewModel
|
||||
class Learn2earnViewModel @Inject constructor(
|
||||
private val interactor: Learn2earnInteractor,
|
||||
private val router: Learn2earnRouter,
|
||||
private val dispatchers: AppCoroutineDispatcherProvider,
|
||||
) : ViewModel() {
|
||||
|
||||
var uiState: Learn2earnState by mutableStateOf(
|
||||
Learn2earnState.init(
|
||||
uiActions = Learn2earnUiActions(
|
||||
onButtonStoryClick = ::onButtonStoryClick,
|
||||
onButtonMainClick = ::onButtonMainClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
private set
|
||||
|
||||
init {
|
||||
uiState = uiState.updateStoriesVisibility(interactor.isNeedToShowViewOnStoriesScreen())
|
||||
}
|
||||
|
||||
fun onMainScreenCreated() {
|
||||
updateMainScreenViews()
|
||||
}
|
||||
|
||||
private fun updateMainScreenViews() {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
if (interactor.isNeedToShowViewOnMainScreen()) {
|
||||
updateUi {
|
||||
uiState.changeGetBounsDescription(getBonusDescription())
|
||||
.updateGetBonusVisibility(isVisible = true)
|
||||
}
|
||||
} else {
|
||||
updateUi { uiState.updateGetBonusVisibility(isVisible = false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onButtonStoryClick() {
|
||||
router.openWebView(interactor.buildUriForNewUser(), interactor.getBasicAuthHeaders())
|
||||
}
|
||||
|
||||
private fun onButtonMainClick() {
|
||||
if (!interactor.isUserHadPromoCode() && !interactor.isUserRegisteredInPromotion()) {
|
||||
subscribeToWebViewResultEvents()
|
||||
router.openWebView(interactor.buildUriForOldUser(), interactor.getBasicAuthHeaders())
|
||||
return
|
||||
}
|
||||
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
updateUi { uiState.updateProgress(showProgress = true) }
|
||||
|
||||
runCatching { interactor.requestAward() }
|
||||
.onSuccess { requestAwardResult ->
|
||||
requestAwardResult.fold(
|
||||
onSuccess = {
|
||||
val onHideDialog = {
|
||||
uiState = uiState.updateGetBonusVisibility(isVisible = false)
|
||||
.hideDialog()
|
||||
}
|
||||
val successDialog = MainScreenState.Dialog.Claimed(
|
||||
onOk = onHideDialog,
|
||||
onDismissRequest = onHideDialog,
|
||||
)
|
||||
updateUi { uiState.showDialog(successDialog) }
|
||||
},
|
||||
onFailure = {
|
||||
val errorDialog = createErrorDialog(it.toDomainError())
|
||||
updateUi {
|
||||
uiState.updateProgress(showProgress = false)
|
||||
.showDialog(errorDialog)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
.onFailure { exception ->
|
||||
updateUi { uiState.updateProgress(showProgress = false) }
|
||||
if (exception is UserCancelledException) return@launch
|
||||
|
||||
val errorDialog = createErrorDialog(exception.toDomainError())
|
||||
updateUi { uiState.showDialog(errorDialog) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBonusDescription(): MainScreenState.Description {
|
||||
return if (interactor.isUserRegisteredInPromotion()) {
|
||||
MainScreenState.Description.GetBonus
|
||||
} else {
|
||||
MainScreenState.Description.Learn(interactor.getAwardAmount())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createErrorDialog(error: PromotionError): MainScreenState.Dialog {
|
||||
return when (error) {
|
||||
is PromotionError.CodeWasNotAppliedInShop -> {
|
||||
val onHideDialog = { uiState = uiState.hideDialog() }
|
||||
MainScreenState.Dialog.PromoCodeNotRegistered(
|
||||
onOk = {
|
||||
uiState = uiState.hideDialog()
|
||||
subscribeToWebViewResultEvents()
|
||||
router.openWebView(interactor.buildUriForNewUser(), interactor.getBasicAuthHeaders())
|
||||
},
|
||||
onCancel = onHideDialog,
|
||||
onDismissRequest = onHideDialog,
|
||||
)
|
||||
}
|
||||
is PromotionError.CodeNotFound,
|
||||
is PromotionError.CodeWasAlreadyUsed,
|
||||
is PromotionError.CardAlreadyHasAward,
|
||||
is PromotionError.WalletAlreadyHasAward,
|
||||
is PromotionError.ProgramNotFound,
|
||||
is PromotionError.ProgramWasEnd,
|
||||
-> {
|
||||
val onHideDialog = {
|
||||
uiState = uiState.hideDialog()
|
||||
.updateGetBonusVisibility(isVisible = false)
|
||||
}
|
||||
MainScreenState.Dialog.Error(
|
||||
error = error,
|
||||
onOk = onHideDialog,
|
||||
onDismissRequest = onHideDialog,
|
||||
)
|
||||
}
|
||||
is PromotionError.UnknownError, PromotionError.NetworkUnreachable -> {
|
||||
val onHideDialog = { uiState = uiState.hideDialog() }
|
||||
MainScreenState.Dialog.Error(
|
||||
error = error,
|
||||
onOk = onHideDialog,
|
||||
onDismissRequest = onHideDialog,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToWebViewResultEvents() {
|
||||
interactor.webViewResultHandler = object : WebViewResultHandler {
|
||||
override fun handleResult(result: WebViewResult) {
|
||||
interactor.webViewResultHandler = null
|
||||
when (result) {
|
||||
WebViewResult.ReadyForAward -> updateMainScreenViews()
|
||||
WebViewResult.PromoCodeReceived -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateUi(updateBlock: suspend () -> Learn2earnState) {
|
||||
withContext(dispatchers.io) {
|
||||
val newState = updateBlock.invoke()
|
||||
withContext(dispatchers.main) { uiState = newState }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Throwable.toDomainError(): PromotionError {
|
||||
return this as? PromotionError ?: PromotionError.UnknownError(message ?: "Unknown error")
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
package com.tangem.feature.learn2earn.presentation.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
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.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.learn2earn.impl.R
|
||||
import com.tangem.feature.learn2earn.presentation.ui.component.GradientCircle
|
||||
import com.tangem.feature.learn2earn.presentation.ui.state.MainScreenState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun GetBonusView(state: MainScreenState, modifier: Modifier = Modifier) {
|
||||
if (!state.isVisible) return
|
||||
|
||||
val shape = TangemTheme.shapes.roundedCornersMedium
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clickable(onClick = state.onClick)
|
||||
.height(TangemTheme.dimens.size96)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = shape,
|
||||
),
|
||||
) {
|
||||
ContentBackground(shape)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(modifier = Modifier.padding(horizontal = TangemTheme.dimens.size16)) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.img_1inch_logo_42_40),
|
||||
alpha = state.logoState.alpha,
|
||||
contentDescription = null,
|
||||
)
|
||||
if (state.showProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(size = TangemTheme.dimens.size28),
|
||||
color = TangemColorPalette.White,
|
||||
strokeWidth = TangemTheme.dimens.size2,
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(end = TangemTheme.dimens.size40),
|
||||
) {
|
||||
Text(
|
||||
text = state.description.title.resolveReference(),
|
||||
style = TangemTypography.body1,
|
||||
color = TangemTheme.colors.text.primary2,
|
||||
)
|
||||
SpacerH4()
|
||||
Text(
|
||||
text = state.description.subtitle.resolveReference(),
|
||||
style = TangemTypography.caption,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size40)
|
||||
.padding(end = TangemTheme.dimens.size16)
|
||||
.align(Alignment.CenterEnd),
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(Color.Gray),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun ContentBackground(shape: Shape) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(shape)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
GradientCircle(
|
||||
size = 340.dp,
|
||||
offsetX = (-103).dp,
|
||||
offsetY = 16.dp,
|
||||
startColor = Color(0xFF1D5DC7),
|
||||
endColor = Color(0x000B173D),
|
||||
)
|
||||
GradientCircle(
|
||||
size = 268.dp,
|
||||
offsetX = 90.dp,
|
||||
offsetY = 0.dp,
|
||||
startColor = Color(0xD8FF0CCA),
|
||||
endColor = Color(0x000B0E17),
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action.copy(alpha = 0.7f),
|
||||
shape = shape,
|
||||
)
|
||||
.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun GetBonusViewPreview_Light(@PreviewParameter(GetBonusViewPreviewProvider::class) state: MainScreenState) {
|
||||
TangemTheme(
|
||||
isDark = false,
|
||||
) {
|
||||
GetBonusView(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun GetBonusViewPreview_Dark(@PreviewParameter(GetBonusViewPreviewProvider::class) state: MainScreenState) {
|
||||
TangemTheme(
|
||||
isDark = true,
|
||||
) {
|
||||
GetBonusView(state)
|
||||
}
|
||||
}
|
||||
|
||||
private class GetBonusViewPreviewProvider : CollectionPreviewParameterProvider<MainScreenState>(
|
||||
collection = listOf(
|
||||
MainScreenState(
|
||||
isVisible = true,
|
||||
onClick = {},
|
||||
description = MainScreenState.Description.Learn(1),
|
||||
logoState = MainScreenState.LogoState.Idle,
|
||||
showProgress = false,
|
||||
),
|
||||
MainScreenState(
|
||||
isVisible = true,
|
||||
onClick = {},
|
||||
description = MainScreenState.Description.Learn(2),
|
||||
logoState = MainScreenState.LogoState.InProgress,
|
||||
showProgress = true,
|
||||
),
|
||||
MainScreenState(
|
||||
isVisible = true,
|
||||
onClick = {},
|
||||
description = MainScreenState.Description.GetBonus,
|
||||
logoState = MainScreenState.LogoState.Idle,
|
||||
showProgress = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package com.tangem.feature.learn2earn.presentation.ui
|
||||
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.components.TextButton
|
||||
import com.tangem.feature.learn2earn.impl.R
|
||||
import com.tangem.feature.learn2earn.presentation.ui.state.MainScreenState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun Learn2earnDialogs(dialog: MainScreenState.Dialog?) {
|
||||
when (dialog) {
|
||||
is MainScreenState.Dialog.Claimed -> ClaimedDialog(dialog)
|
||||
is MainScreenState.Dialog.PromoCodeNotRegistered -> PromoCodeNotRegisteredDialog(dialog)
|
||||
is MainScreenState.Dialog.Error -> ErrorDialog(dialog)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ClaimedDialog(dialog: MainScreenState.Dialog.Claimed) {
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.common_success))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(id = R.string.main_promotion_credited))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
text = stringResource(id = R.string.common_ok),
|
||||
onClick = dialog.onOk,
|
||||
)
|
||||
},
|
||||
onDismissRequest = dialog.onDismissRequest,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PromoCodeNotRegisteredDialog(dialog: MainScreenState.Dialog.PromoCodeNotRegistered) {
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.common_error))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(id = R.string.main_promotion_no_purchase))
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
text = stringResource(id = R.string.common_cancel),
|
||||
onClick = dialog.onCancel,
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
text = stringResource(id = R.string.common_buy),
|
||||
onClick = dialog.onOk,
|
||||
)
|
||||
},
|
||||
onDismissRequest = dialog.onDismissRequest,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ErrorDialog(dialog: MainScreenState.Dialog.Error) {
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.common_error))
|
||||
},
|
||||
text = {
|
||||
Text(text = dialog.error.description)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
text = stringResource(id = R.string.common_ok),
|
||||
onClick = dialog.onOk,
|
||||
)
|
||||
},
|
||||
onDismissRequest = dialog.onDismissRequest,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.feature.learn2earn.presentation.ui
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.learn2earn.presentation.ui.state.Learn2earnState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Learn2earnMainPageScreen(state: Learn2earnState, modifier: Modifier = Modifier) {
|
||||
GetBonusView(
|
||||
state = state.mainScreenState,
|
||||
modifier = modifier
|
||||
.padding(horizontal = TangemTheme.dimens.size16)
|
||||
.padding(vertical = TangemTheme.dimens.size8),
|
||||
)
|
||||
Learn2earnDialogs(state.mainScreenState.dialog)
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package com.tangem.feature.learn2earn.presentation.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH32
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.learn2earn.impl.R
|
||||
import com.tangem.feature.learn2earn.presentation.ui.component.GradientCircle
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun Learn2earnStoriesScreen(onLearnClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
ContentBackground(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.align(Alignment.Center),
|
||||
)
|
||||
StoryDescription(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.size40)
|
||||
.fillMaxSize(),
|
||||
headerText = stringResource(id = R.string.story_learn_title),
|
||||
bodyText = stringResource(id = R.string.story_learn_description),
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.padding(bottom = TangemTheme.dimens.size68)
|
||||
.align(Alignment.BottomCenter),
|
||||
painter = painterResource(id = R.drawable.img_1inch_logo_401_378),
|
||||
contentDescription = null,
|
||||
)
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.size16)
|
||||
.padding(bottom = TangemTheme.dimens.size84)
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter),
|
||||
text = stringResource(id = R.string.story_learn_learn),
|
||||
onClick = onLearnClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun ContentBackground(modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
GradientCircle(
|
||||
size = 800.dp,
|
||||
offsetX = (-220).dp,
|
||||
offsetY = (-200).dp,
|
||||
startColor = Color(0xCE164594),
|
||||
endColor = Color(0x000B173D),
|
||||
)
|
||||
GradientCircle(
|
||||
size = 800.dp,
|
||||
offsetX = 200.dp,
|
||||
offsetY = 290.dp,
|
||||
startColor = Color(0xFF3D0230),
|
||||
endColor = Color(0x000B0E17),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StoryDescription(headerText: String, bodyText: String, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SpacerH32()
|
||||
Text(
|
||||
text = headerText,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.h1.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
color = TangemTheme.colors.text.primary2,
|
||||
)
|
||||
SpacerH16()
|
||||
Text(
|
||||
text = bodyText,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTypography.subtitle1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Learn2earnStoriesScreenPreview_Light() {
|
||||
TangemTheme(
|
||||
isDark = false,
|
||||
) {
|
||||
Learn2earnStoriesScreen(
|
||||
onLearnClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Learn2earnStoriesScreenPreview_Dark() {
|
||||
TangemTheme(
|
||||
isDark = true,
|
||||
) {
|
||||
Learn2earnStoriesScreen(
|
||||
onLearnClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.feature.learn2earn.presentation.ui.component
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun GradientCircle(
|
||||
size: Dp,
|
||||
offsetX: Dp,
|
||||
offsetY: Dp,
|
||||
startColor: Color,
|
||||
endColor: Color,
|
||||
radius: Dp = size / 2,
|
||||
) {
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.size(size)
|
||||
.offset(x = offsetX, y = offsetY),
|
||||
onDraw = {
|
||||
drawCircle(
|
||||
radius = radius.toPx(),
|
||||
brush = Brush.radialGradient(
|
||||
radius = radius.toPx(),
|
||||
colors = listOf(startColor, endColor),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.feature.learn2earn.presentation.ui.state
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.WrappedList
|
||||
import com.tangem.feature.learn2earn.domain.models.PromotionError
|
||||
import com.tangem.feature.learn2earn.impl.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class Learn2earnState(
|
||||
val storyScreenState: StoryScreenState,
|
||||
val mainScreenState: MainScreenState,
|
||||
) {
|
||||
companion object
|
||||
}
|
||||
|
||||
data class StoryScreenState(
|
||||
val isVisible: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class MainScreenState(
|
||||
val isVisible: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
val description: Description,
|
||||
val showProgress: Boolean,
|
||||
val logoState: LogoState,
|
||||
val dialog: Dialog? = null,
|
||||
) {
|
||||
|
||||
sealed class LogoState(val alpha: Float) {
|
||||
object Idle : LogoState(alpha = 1.0f)
|
||||
object InProgress : LogoState(alpha = 0.2f)
|
||||
}
|
||||
|
||||
sealed class Description(val title: TextReference, val subtitle: TextReference) {
|
||||
|
||||
class Learn(award: Int) : Description(
|
||||
TextReference.Res(R.string.main_learn_title),
|
||||
TextReference.PluralRes(R.plurals.main_learn_subtitle, award, WrappedList(listOf(award))),
|
||||
)
|
||||
|
||||
object GetBonus : Description(
|
||||
TextReference.Res(R.string.main_get_bonus_title),
|
||||
TextReference.Res(R.string.main_get_bonus_subtitle),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Dialog {
|
||||
|
||||
data class Claimed(
|
||||
val onOk: () -> Unit,
|
||||
val onDismissRequest: () -> Unit,
|
||||
) : Dialog()
|
||||
|
||||
data class PromoCodeNotRegistered(
|
||||
val onOk: () -> Unit,
|
||||
val onCancel: () -> Unit,
|
||||
val onDismissRequest: () -> Unit,
|
||||
) : Dialog()
|
||||
|
||||
data class Error(
|
||||
val error: PromotionError,
|
||||
val onOk: () -> Unit,
|
||||
val onDismissRequest: () -> Unit,
|
||||
) : Dialog()
|
||||
}
|
||||
}
|
||||
|
||||
class Learn2earnUiActions(
|
||||
val onButtonStoryClick: () -> Unit,
|
||||
val onButtonMainClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package com.tangem.feature.learn2earn.presentation.ui.state
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal fun Learn2earnState.Companion.init(uiActions: Learn2earnUiActions): Learn2earnState {
|
||||
return Learn2earnState(
|
||||
storyScreenState = StoryScreenState(
|
||||
isVisible = false,
|
||||
onClick = uiActions.onButtonStoryClick,
|
||||
),
|
||||
mainScreenState = MainScreenState(
|
||||
isVisible = false,
|
||||
onClick = uiActions.onButtonMainClick,
|
||||
description = MainScreenState.Description.Learn(0),
|
||||
logoState = MainScreenState.LogoState.Idle,
|
||||
showProgress = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun Learn2earnState.updateStoriesVisibility(isVisible: Boolean): Learn2earnState {
|
||||
return if (storyScreenState.isVisible == isVisible) {
|
||||
this
|
||||
} else {
|
||||
copy(
|
||||
storyScreenState = storyScreenState.copy(
|
||||
isVisible = isVisible,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Learn2earnState.updateGetBonusVisibility(isVisible: Boolean): Learn2earnState {
|
||||
return if (mainScreenState.isVisible == isVisible) {
|
||||
this
|
||||
} else {
|
||||
copy(
|
||||
mainScreenState = mainScreenState.copy(
|
||||
isVisible = isVisible,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Learn2earnState.changeGetBounsDescription(description: MainScreenState.Description): Learn2earnState {
|
||||
return if (mainScreenState.description == description) {
|
||||
this
|
||||
} else {
|
||||
copy(
|
||||
mainScreenState = mainScreenState.copy(
|
||||
description = description,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Learn2earnState.showDialog(dialog: MainScreenState.Dialog): Learn2earnState {
|
||||
return if (mainScreenState.dialog == dialog) {
|
||||
this
|
||||
} else {
|
||||
copy(
|
||||
mainScreenState = mainScreenState.copy(
|
||||
dialog = dialog,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Learn2earnState.hideDialog(): Learn2earnState {
|
||||
return if (mainScreenState.dialog == null) {
|
||||
this
|
||||
} else {
|
||||
copy(
|
||||
mainScreenState = mainScreenState.copy(
|
||||
dialog = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Learn2earnState.updateProgress(showProgress: Boolean): Learn2earnState {
|
||||
return if (mainScreenState.showProgress == showProgress) {
|
||||
this
|
||||
} else {
|
||||
copy(
|
||||
mainScreenState = mainScreenState.copy(
|
||||
showProgress = showProgress,
|
||||
logoState = if (showProgress) {
|
||||
MainScreenState.LogoState.InProgress
|
||||
} else {
|
||||
MainScreenState.LogoState.Idle
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.tangem.feature.learn2earn.presentation.webView
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.ActionBar
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.tangem.feature.learn2earn.impl.R
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class Learn2earnWebViewActivity : AppCompatActivity() {
|
||||
|
||||
private val viewModel by viewModels<WebViewViewModel>()
|
||||
|
||||
private lateinit var actionBar: ActionBar
|
||||
private lateinit var webView: WebView
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_web_view)
|
||||
|
||||
val webViewData = viewModel.extractData(intent)
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
actionBar = supportActionBar!!
|
||||
actionBar.setDisplayHomeAsUpEnabled(true)
|
||||
actionBar.setDisplayShowHomeEnabled(true)
|
||||
actionBar.title = webViewData.uri.authority
|
||||
webView = findViewById(R.id.web_view)
|
||||
|
||||
webView.settings.javaScriptEnabled = true
|
||||
webView.settings.domStorageEnabled = true
|
||||
webView.settings.loadsImagesAutomatically = true
|
||||
|
||||
webView.webViewClient = Learn2earnWebViewClient(
|
||||
helper = viewModel,
|
||||
headers = webViewData.headers,
|
||||
finishSessionHandler = { finish() },
|
||||
)
|
||||
webView.loadUrl(
|
||||
webViewData.uri.toString(),
|
||||
webViewData.headers,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
finish()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
webView.clearHistory()
|
||||
webView.clearCache(true)
|
||||
webView.destroy()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_WEB_URI = "webViewUri"
|
||||
const val EXTRA_WEB_HEADERS = "webViewHeaders"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.feature.learn2earn.presentation.webView
|
||||
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import com.tangem.feature.learn2earn.domain.api.RedirectConsequences
|
||||
import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler
|
||||
import timber.log.Timber
|
||||
|
||||
internal class Learn2earnWebViewClient(
|
||||
private val helper: WebViewRedirectHandler,
|
||||
private val headers: Map<String, String>,
|
||||
private val finishSessionHandler: () -> Unit,
|
||||
) : WebViewClient() {
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
|
||||
Timber.d("url: ${request.url}")
|
||||
when (helper.handleRedirect(request.url)) {
|
||||
RedirectConsequences.PROCEED -> {
|
||||
val mergedHeaders = headers.toMutableMap()
|
||||
request.requestHeaders?.let { mergedHeaders.putAll(it) }
|
||||
view.loadUrl(request.url.toString(), mergedHeaders)
|
||||
}
|
||||
RedirectConsequences.FINISH_SESSION -> {
|
||||
finishSessionHandler.invoke()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.feature.learn2earn.presentation.webView
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.feature.learn2earn.domain.HeadersConverter
|
||||
import com.tangem.feature.learn2earn.domain.api.RedirectConsequences
|
||||
import com.tangem.feature.learn2earn.domain.api.WebViewRedirectHandler
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@HiltViewModel
|
||||
internal class WebViewViewModel @Inject constructor(
|
||||
private val handler: WebViewRedirectHandler,
|
||||
) : ViewModel(), WebViewRedirectHandler {
|
||||
|
||||
override fun handleRedirect(uri: Uri): RedirectConsequences {
|
||||
return handler.handleRedirect(uri)
|
||||
}
|
||||
|
||||
fun extractData(intent: Intent): WebViewData {
|
||||
val uriString = requireNotNull(intent.getStringExtra(Learn2earnWebViewActivity.EXTRA_WEB_URI)) {
|
||||
"The Intent property EXTRA_WEB_URI is null"
|
||||
}
|
||||
val rawHeaders = intent.getStringArrayListExtra(Learn2earnWebViewActivity.EXTRA_WEB_HEADERS) ?: ArrayList()
|
||||
|
||||
return WebViewData(
|
||||
uri = Uri.parse(uriString),
|
||||
headers = HeadersConverter().convertBack(rawHeaders),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class WebViewData(
|
||||
val uri: Uri,
|
||||
val headers: Map<String, String>,
|
||||
)
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/coordinator_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||
android:layout_width="match_parent"
|
||||
app:liftOnScroll="true"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_close_24" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<WebView
|
||||
android:id="@+id/web_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -60,6 +60,9 @@ include(":features:tester:impl")
|
|||
|
||||
include(":features:wallet:api")
|
||||
include(":features:wallet:impl")
|
||||
|
||||
include(":features:learn2earn:api")
|
||||
include(":features:learn2earn:impl")
|
||||
// endregion Feature modules
|
||||
|
||||
// region Domain modules
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue