Updated on 2026-08-14
This commit is contained in:
commit
63849d3c02
155 changed files with 2317 additions and 607 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 07cfce37ff84e70081aca82d9948acb13c5c07b0
|
||||
Subproject commit a494a9b49335d891dc5836d478c843432a857621
|
||||
|
|
@ -13,20 +13,20 @@ sealed class AnalyticsParam {
|
|||
}
|
||||
|
||||
sealed class CardBalanceState(val value: String) {
|
||||
object Empty : CardBalanceState("Empty")
|
||||
object Full : CardBalanceState("Full")
|
||||
data object Empty : CardBalanceState("Empty")
|
||||
data object Full : CardBalanceState("Full")
|
||||
companion object
|
||||
}
|
||||
|
||||
sealed class RateApp(val value: String) {
|
||||
object Liked : RateApp("Liked")
|
||||
object Closed : RateApp("Close")
|
||||
data object Liked : RateApp("Liked")
|
||||
data object Closed : RateApp("Close")
|
||||
}
|
||||
|
||||
sealed class OnOffState(val value: String) {
|
||||
|
||||
object On : OnOffState("On")
|
||||
object Off : OnOffState("Off")
|
||||
data object On : OnOffState("On")
|
||||
data object Off : OnOffState("Off")
|
||||
|
||||
companion object {
|
||||
|
||||
|
|
@ -35,13 +35,13 @@ sealed class AnalyticsParam {
|
|||
}
|
||||
|
||||
sealed class UserCode(val value: String) {
|
||||
object AccessCode : UserCode("Access Code")
|
||||
data object AccessCode : UserCode("Access Code")
|
||||
}
|
||||
|
||||
sealed class SecurityMode(val value: String) {
|
||||
object AccessCode : SecurityMode("Access Code")
|
||||
object Passcode : SecurityMode("Passcode")
|
||||
object LongTap : SecurityMode("Long Tap")
|
||||
data object AccessCode : SecurityMode("Access Code")
|
||||
data object Passcode : SecurityMode("Passcode")
|
||||
data object LongTap : SecurityMode("Long Tap")
|
||||
|
||||
companion object {
|
||||
fun from(option: SecurityOption): SecurityMode = when (option) {
|
||||
|
|
@ -56,8 +56,8 @@ sealed class AnalyticsParam {
|
|||
|
||||
val key: String = "Status"
|
||||
|
||||
object Enabled : AccessCodeRecoveryStatus("Enabled")
|
||||
object Disabled : AccessCodeRecoveryStatus("Disabled")
|
||||
data object Enabled : AccessCodeRecoveryStatus("Enabled")
|
||||
data object Disabled : AccessCodeRecoveryStatus("Disabled")
|
||||
|
||||
companion object {
|
||||
fun from(enabled: Boolean): AccessCodeRecoveryStatus {
|
||||
|
|
@ -67,21 +67,21 @@ sealed class AnalyticsParam {
|
|||
}
|
||||
|
||||
sealed class Error(val value: String) {
|
||||
object App : Error("App Error")
|
||||
object CardSdk : Error("Card Sdk Error")
|
||||
object BlockchainSdk : Error("Blockchain Sdk Error")
|
||||
data object App : Error("App Error")
|
||||
data object CardSdk : Error("Card Sdk Error")
|
||||
data object BlockchainSdk : Error("Blockchain Sdk Error")
|
||||
}
|
||||
|
||||
sealed class WalletCreationType(val value: String) {
|
||||
object PrivateKey : WalletCreationType(value = "Private Key")
|
||||
object NewSeed : WalletCreationType(value = "New Seed")
|
||||
object SeedImport : WalletCreationType(value = "Seed Import")
|
||||
data object PrivateKey : WalletCreationType(value = "Private Key")
|
||||
data object NewSeed : WalletCreationType(value = "New Seed")
|
||||
data object SeedImport : WalletCreationType(value = "Seed Import")
|
||||
}
|
||||
|
||||
sealed class AppTheme(val value: String) {
|
||||
object System : AppTheme("System")
|
||||
object Dark : AppTheme("Dark")
|
||||
object Light : AppTheme("Light")
|
||||
data object System : AppTheme("System")
|
||||
data object Dark : AppTheme("Dark")
|
||||
data object Light : AppTheme("Light")
|
||||
|
||||
companion object {
|
||||
fun fromAppThemeMode(mode: AppThemeMode): AppTheme {
|
||||
|
|
@ -104,6 +104,7 @@ sealed class AnalyticsParam {
|
|||
const val PERMISSION_TYPE = "Permission Type"
|
||||
const val PRODUCT_TYPE = "Product Type"
|
||||
const val FIRMWARE = "Firmware"
|
||||
const val USER_WALLET_ID = "User Wallet ID"
|
||||
const val CURRENCY = "Currency"
|
||||
const val ERROR_DESCRIPTION = "Error Description"
|
||||
const val ERROR_CODE = "Error Code"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.userwallets.UserWalletIdBuilder
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
|
|
@ -18,6 +19,8 @@ class CardContextInterceptor(
|
|||
private val scanResponse: ScanResponse,
|
||||
) : ParamsInterceptor {
|
||||
|
||||
private val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
|
||||
override fun id(): String = CardContextInterceptor.id()
|
||||
|
||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
|
||||
|
|
@ -32,6 +35,9 @@ class CardContextInterceptor(
|
|||
params[AnalyticsParam.BATCH] = card.batchId
|
||||
params[AnalyticsParam.PRODUCT_TYPE] = getProductType(scanResponse)
|
||||
params[AnalyticsParam.FIRMWARE] = card.firmwareVersion.stringValue
|
||||
if (userWalletId != null) {
|
||||
params[AnalyticsParam.USER_WALLET_ID] = userWalletId.stringValue
|
||||
}
|
||||
|
||||
ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)?.let {
|
||||
params[AnalyticsParam.CURRENCY] = it.value
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
|||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object ThemeModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppThemeModeHolder(): AppThemeModeHolder {
|
||||
return MutableAppThemeModeHolder
|
||||
}
|
||||
|
|
|
|||
24
app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt
Normal file
24
app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object UiDependenciesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUiDependencies(hapticManager: HapticManager, appThemeModeHolder: AppThemeModeHolder): UiDependencies {
|
||||
return object : UiDependencies {
|
||||
override val hapticManager = hapticManager
|
||||
override val appThemeModeHolder = appThemeModeHolder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ import com.tangem.domain.demo.DemoConfig
|
|||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -69,4 +71,10 @@ internal object CardDomainModule {
|
|||
): GetExtendedPublicKeyForCurrencyUseCase {
|
||||
return GetExtendedPublicKeyForCurrencyUseCase(derivationsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideDeleteSavedAccessCodesUseCase(tangemSdkManager: TangemSdkManager): DeleteSavedAccessCodesUseCase {
|
||||
return DefaultDeleteSavedAccessCodesUseCase(tangemSdkManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@ import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
|||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.settings.repositories.AppRatingRepository
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.repositories.SwapPromoRepository
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
|
||||
import dagger.Module
|
||||
|
|
@ -108,17 +108,25 @@ internal object SettingsDomainModule {
|
|||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideShouldShowSwapPromoWalletUseCase(
|
||||
swapPromoRepository: SwapPromoRepository,
|
||||
promoSettingsRepository: PromoSettingsRepository,
|
||||
): ShouldShowSwapPromoWalletUseCase {
|
||||
return ShouldShowSwapPromoWalletUseCase(swapPromoRepository)
|
||||
return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideShouldShowTravalaPromoWalletUseCase(
|
||||
promoSettingsRepository: PromoSettingsRepository,
|
||||
): ShouldShowTravalaPromoWalletUseCase {
|
||||
return ShouldShowTravalaPromoWalletUseCase(promoSettingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideShouldShowSwapPromoTokenUseCase(
|
||||
swapPromoRepository: SwapPromoRepository,
|
||||
promoSettingsRepository: PromoSettingsRepository,
|
||||
): ShouldShowSwapPromoTokenUseCase {
|
||||
return ShouldShowSwapPromoTokenUseCase(swapPromoRepository)
|
||||
return ShouldShowSwapPromoTokenUseCase(promoSettingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -346,4 +346,12 @@ internal object TokensDomainModule {
|
|||
): RunPolkadotAccountHealthCheckUseCase {
|
||||
return RunPolkadotAccountHealthCheckUseCase(repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideGetNetworkStatusesUseCase(networksRepository: NetworksRepository): GetNetworkAddressesUseCase {
|
||||
return GetNetworkAddressesUseCase(
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
|
|||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -54,4 +51,10 @@ internal object TransactionDomainModule {
|
|||
fun provideIsFeeApproximateUseCase(feeRepository: FeeRepository): IsFeeApproximateUseCase {
|
||||
return IsFeeApproximateUseCase(feeRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideValidateTransactionUseCase(transactionRepository: TransactionRepository): ValidateTransactionUseCase {
|
||||
return ValidateTransactionUseCase(transactionRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.tap.domain.card
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
|
||||
internal class DefaultDeleteSavedAccessCodesUseCase(
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
) : DeleteSavedAccessCodesUseCase {
|
||||
|
||||
override suspend fun invoke(cardId: String): Either<Throwable, Unit> {
|
||||
tangemSdkManager.deleteSavedUserCodes(setOf(cardId))
|
||||
.doOnFailure { return it.left() }
|
||||
.doOnSuccess { return Unit.right() }
|
||||
|
||||
return Unit.right()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenScreen
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.viewmodels.AddCustomTokenViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -24,10 +23,7 @@ import javax.inject.Inject
|
|||
internal class AddCustomTokenFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ object ContractAddressValidator {
|
|||
|
||||
private fun validateAddress(blockchain: Blockchain, address: String): Boolean {
|
||||
return when (blockchain) {
|
||||
Blockchain.Unknown, Blockchain.Binance, Blockchain.BinanceTestnet -> {
|
||||
SuccessAddressValidator.validate(address)
|
||||
}
|
||||
else -> {
|
||||
blockchain.validateAddress(address)
|
||||
}
|
||||
Blockchain.Unknown,
|
||||
Blockchain.Binance,
|
||||
Blockchain.BinanceTestnet,
|
||||
Blockchain.Cardano,
|
||||
-> SuccessAddressValidator.validate(address)
|
||||
else -> blockchain.validateAddress(address)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -17,10 +16,7 @@ import javax.inject.Inject
|
|||
internal class AppCurrencySelectorFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel: AppCurrencySelectorViewModel by viewModels()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
|
|||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -20,10 +19,7 @@ import javax.inject.Inject
|
|||
internal class AppSettingsFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var appCurrencyRepository: AppCurrencyRepository
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -17,10 +16,7 @@ import javax.inject.Inject
|
|||
internal class CardSettingsFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel: CardSettingsViewModel by viewModels()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ import androidx.compose.runtime.MutableState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -23,10 +22,7 @@ class AccessCodeRecoveryFragment : ComposeFragment(), StoreSubscriber<DetailsSta
|
|||
mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState?.accessCodeRecovery))
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ import androidx.compose.ui.Modifier
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.feedback.GetSupportFeedbackEmailUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
|
|
@ -24,10 +23,7 @@ import javax.inject.Inject
|
|||
internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState> {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var walletsRepository: WalletsRepository
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ import androidx.compose.runtime.MutableState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -18,10 +17,7 @@ import javax.inject.Inject
|
|||
internal class ResetCardFragment : ComposeFragment(), StoreSubscriber<DetailsState> {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel = ResetCardViewModel(store)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ import androidx.compose.runtime.MutableState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -18,10 +17,7 @@ import javax.inject.Inject
|
|||
internal class SecurityModeFragment : ComposeFragment(), StoreSubscriber<DetailsState> {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel = SecurityModeViewModel(store)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -22,10 +21,7 @@ import javax.inject.Inject
|
|||
internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber<WalletConnectState> {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel: WalletConnectViewModel by viewModels()
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ import androidx.lifecycle.lifecycleScope
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -31,10 +30,7 @@ import javax.inject.Inject
|
|||
class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
|||
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
|
|
@ -39,6 +40,7 @@ internal class MainViewModel @Inject constructor(
|
|||
private val blockchainSDKFactory: BlockchainSDKFactory,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
) : ViewModel(), MainIntents {
|
||||
|
|
@ -61,6 +63,7 @@ internal class MainViewModel @Inject constructor(
|
|||
viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() }
|
||||
|
||||
updateAppCurrencies()
|
||||
updateSendFeatureToggle()
|
||||
observeFlips()
|
||||
displayBalancesHidingStatusToast()
|
||||
displayHiddenBalancesModalNotification()
|
||||
|
|
@ -108,6 +111,12 @@ internal class MainViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateSendFeatureToggle() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
sendFeatureToggles.fetchNewSendEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeFlips() {
|
||||
listenToFlipsUseCase().launchIn(viewModelScope)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,9 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeBottomSheetFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.features.main.MainViewModel
|
||||
import com.tangem.tap.features.main.model.ModalNotification
|
||||
import com.tangem.tap.features.main.ui.components.ModalNotificationContent
|
||||
|
|
@ -23,10 +22,7 @@ import javax.inject.Inject
|
|||
internal class ModalNotificationBottomSheetFragment : ComposeBottomSheetFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel: MainViewModel by activityViewModels()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeBottomSheetFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.tap.features.saveWallet.ui.components.EnrollBiometricsDialogContent
|
||||
import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent
|
||||
|
|
@ -26,10 +25,7 @@ import javax.inject.Inject
|
|||
internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
override val expandedHeightFraction: Float = .98f
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|||
import com.tangem.blockchain.blockchains.algorand.AlgorandTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder
|
||||
import com.tangem.blockchain.blockchains.hedera.HederaTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ton.TonTransactionExtras
|
||||
|
|
@ -20,6 +20,7 @@ import com.tangem.core.analytics.models.Basic
|
|||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.demo.DemoTransactionSender
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
|
|
@ -31,7 +32,6 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.domain.TangemSigner
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.domain.demo.DemoTransactionSender
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
|
||||
|
|
@ -170,11 +170,7 @@ private fun sendTransaction(
|
|||
transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionExtras(it)) }
|
||||
transactionExtras.cosmosMemoState?.memo?.let { txData = txData.copy(extras = CosmosTransactionExtras(it)) }
|
||||
transactionExtras.tonMemoState?.memo?.let { txData = txData.copy(extras = TonTransactionExtras(it)) }
|
||||
transactionExtras.hederaMemoState?.memo?.let {
|
||||
txData = txData.copy(
|
||||
extras = HederaTransactionBuilder.HederaTransactionExtras(it),
|
||||
)
|
||||
}
|
||||
transactionExtras.hederaMemoState?.memo?.let { txData = txData.copy(extras = HederaTransactionExtras(it)) }
|
||||
transactionExtras.algorandMemoState?.memo?.let { txData = txData.copy(extras = AlgorandTransactionExtras(it)) }
|
||||
|
||||
scope.launch {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.features.tokens.impl.presentation.ui.TokensListScreen
|
||||
import com.tangem.tap.features.tokens.impl.presentation.viewmodels.TokensListViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -24,10 +23,7 @@ import javax.inject.Inject
|
|||
internal class TokensListFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
|
|
|
|||
|
|
@ -15,11 +15,10 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
|
|||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.common.analytics.events.SignIn
|
||||
import com.tangem.tap.common.extensions.eraseContext
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
|
|
@ -32,10 +31,7 @@ import javax.inject.Inject
|
|||
internal class WelcomeFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
|
|||
import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder
|
||||
import com.tangem.blockchain.blockchains.hedera.HederaTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.optimism.EthereumOptimisticRollupWalletManager
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarMemo
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
|
||||
|
|
@ -141,7 +141,7 @@ class TransactionManagerImpl(
|
|||
Blockchain.XRP -> memo.toLongOrNull()?.let { XrpTransactionBuilder.XrpTransactionExtras(it) }
|
||||
Blockchain.Cosmos -> CosmosTransactionExtras(memo)
|
||||
Blockchain.TON -> TonTransactionExtras(memo)
|
||||
Blockchain.Hedera -> HederaTransactionBuilder.HederaTransactionExtras(memo)
|
||||
Blockchain.Hedera -> HederaTransactionExtras(memo)
|
||||
Blockchain.Algorand -> AlgorandTransactionExtras(memo)
|
||||
else -> null
|
||||
}
|
||||
|
|
@ -224,26 +224,33 @@ class TransactionManagerImpl(
|
|||
when (fee.data) {
|
||||
is TransactionFee.Single -> {
|
||||
val normalFee = (fee.data as TransactionFee.Single).normal
|
||||
val singleFee = ProxyFee(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
)
|
||||
ProxyFees.SingleFee(
|
||||
singleFee = singleFee,
|
||||
)
|
||||
val singleFee = if (normalFee as? Fee.CardanoToken != null) {
|
||||
ProxyFee.CardanoToken(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
minAdaValue = normalFee.minAdaValue,
|
||||
)
|
||||
} else {
|
||||
ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
)
|
||||
}
|
||||
|
||||
ProxyFees.SingleFee(singleFee = singleFee)
|
||||
}
|
||||
is TransactionFee.Choosable -> {
|
||||
val choosableFee = fee.data as TransactionFee.Choosable
|
||||
ProxyFees.MultipleFees(
|
||||
minFee = ProxyFee(
|
||||
minFee = ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
|
||||
),
|
||||
normalFee = ProxyFee(
|
||||
normalFee = ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
|
||||
),
|
||||
priorityFee = ProxyFee(
|
||||
priorityFee = ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
|
||||
),
|
||||
|
|
@ -300,15 +307,15 @@ class TransactionManagerImpl(
|
|||
is Result.Success -> {
|
||||
val choosableFee = fee.data
|
||||
|
||||
val minProxyFee = ProxyFee(
|
||||
val minProxyFee = ProxyFee.Common(
|
||||
gasLimit = (choosableFee.minimum as Fee.Ethereum).gasLimit,
|
||||
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
|
||||
)
|
||||
val normalProxyFee = ProxyFee(
|
||||
val normalProxyFee = ProxyFee.Common(
|
||||
gasLimit = (choosableFee.normal as Fee.Ethereum).gasLimit,
|
||||
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
|
||||
)
|
||||
val priorityProxyFee = ProxyFee(
|
||||
val priorityProxyFee = ProxyFee.Common(
|
||||
gasLimit = (choosableFee.priority as Fee.Ethereum).gasLimit,
|
||||
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
|
||||
)
|
||||
|
|
@ -463,7 +470,7 @@ class TransactionManagerImpl(
|
|||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
)
|
||||
val minFee = ProxyFee(
|
||||
val minFee = ProxyFee.Common(
|
||||
gasLimit = gasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
|
|
@ -471,7 +478,7 @@ class TransactionManagerImpl(
|
|||
decimals = blockchain.decimals(),
|
||||
),
|
||||
)
|
||||
val normalFee = ProxyFee(
|
||||
val normalFee = ProxyFee.Common(
|
||||
gasLimit = gasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
|
|
@ -479,7 +486,7 @@ class TransactionManagerImpl(
|
|||
decimals = blockchain.decimals(),
|
||||
),
|
||||
)
|
||||
val priorityFee = ProxyFee(
|
||||
val priorityFee = ProxyFee.Common(
|
||||
gasLimit = gasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
|
|
|
|||
|
|
@ -3,46 +3,46 @@ package com.tangem.core.analytics.models
|
|||
sealed class AnalyticsParam {
|
||||
|
||||
sealed class CardBalanceState(val value: String) {
|
||||
object Empty : CardBalanceState("Empty")
|
||||
object Full : CardBalanceState("Full")
|
||||
object CustomToken : CardBalanceState("Custom Token")
|
||||
object BlockchainError : CardBalanceState("Blockchain Error")
|
||||
object NoRate : CardBalanceState("No Rate")
|
||||
data object Empty : CardBalanceState("Empty")
|
||||
data object Full : CardBalanceState("Full")
|
||||
data object CustomToken : CardBalanceState("Custom Token")
|
||||
data object BlockchainError : CardBalanceState("Blockchain Error")
|
||||
data object NoRate : CardBalanceState("No Rate")
|
||||
companion object
|
||||
}
|
||||
|
||||
sealed class TokenBalanceState(val value: String) {
|
||||
object Empty : TokenBalanceState("Empty")
|
||||
object Full : TokenBalanceState("Full")
|
||||
data object Empty : TokenBalanceState("Empty")
|
||||
data object Full : TokenBalanceState("Full")
|
||||
}
|
||||
|
||||
sealed class RateApp(val value: String) {
|
||||
object Liked : RateApp("Liked")
|
||||
object Disliked : RateApp("Disliked")
|
||||
object Closed : RateApp("Close")
|
||||
data object Liked : RateApp("Liked")
|
||||
data object Disliked : RateApp("Disliked")
|
||||
data object Closed : RateApp("Close")
|
||||
}
|
||||
|
||||
sealed class OnOffState(val value: String) {
|
||||
object On : OnOffState("On")
|
||||
object Off : OnOffState("Off")
|
||||
data object On : OnOffState("On")
|
||||
data object Off : OnOffState("Off")
|
||||
}
|
||||
|
||||
sealed class OrganizeSortType(val value: String) {
|
||||
object ByBalance : OrganizeSortType("By Balance")
|
||||
object Manually : OrganizeSortType("Manually")
|
||||
data object ByBalance : OrganizeSortType("By Balance")
|
||||
data object Manually : OrganizeSortType("Manually")
|
||||
}
|
||||
|
||||
sealed class UserCode(val value: String) {
|
||||
object AccessCode : UserCode("Access Code")
|
||||
object Passcode : UserCode("Passcode")
|
||||
data object AccessCode : UserCode("Access Code")
|
||||
data object Passcode : UserCode("Passcode")
|
||||
}
|
||||
|
||||
sealed class AccessCodeRecoveryStatus(val value: String) {
|
||||
|
||||
val key: String = "Status"
|
||||
|
||||
object Enabled : AccessCodeRecoveryStatus("Enabled")
|
||||
object Disabled : AccessCodeRecoveryStatus("Disabled")
|
||||
data object Enabled : AccessCodeRecoveryStatus("Enabled")
|
||||
data object Disabled : AccessCodeRecoveryStatus("Disabled")
|
||||
|
||||
companion object {
|
||||
fun from(enabled: Boolean): AccessCodeRecoveryStatus {
|
||||
|
|
@ -52,9 +52,9 @@ sealed class AnalyticsParam {
|
|||
}
|
||||
|
||||
sealed class Error(val value: String) {
|
||||
object App : Error("App Error")
|
||||
object CardSdk : Error("Card Sdk Error")
|
||||
object BlockchainSdk : Error("Blockchain Sdk Error")
|
||||
data object App : Error("App Error")
|
||||
data object CardSdk : Error("Card Sdk Error")
|
||||
data object BlockchainSdk : Error("Blockchain Sdk Error")
|
||||
}
|
||||
|
||||
sealed class ScreensSources(val value: String) {
|
||||
|
|
@ -86,8 +86,8 @@ sealed class AnalyticsParam {
|
|||
val permissionType: String,
|
||||
) : TxSentFrom("Approve"), TxData
|
||||
|
||||
object WalletConnect : TxSentFrom("WalletConnect")
|
||||
object Sell : TxSentFrom("Sell")
|
||||
data object WalletConnect : TxSentFrom("WalletConnect")
|
||||
data object Sell : TxSentFrom("Sell")
|
||||
}
|
||||
|
||||
sealed interface TxData {
|
||||
|
|
@ -97,10 +97,11 @@ sealed class AnalyticsParam {
|
|||
}
|
||||
|
||||
sealed class FeeType(val value: String) {
|
||||
object Fixed : FeeType("Fixed")
|
||||
object Min : FeeType("Min")
|
||||
object Normal : FeeType("Normal")
|
||||
object Max : FeeType("Max")
|
||||
data object Fixed : FeeType("Fixed")
|
||||
data object Min : FeeType("Min")
|
||||
data object Normal : FeeType("Normal")
|
||||
data object Max : FeeType("Max")
|
||||
data object Custom : FeeType("Custom")
|
||||
|
||||
companion object {
|
||||
fun fromString(feeType: String): FeeType {
|
||||
|
|
@ -116,13 +117,13 @@ sealed class AnalyticsParam {
|
|||
}
|
||||
|
||||
sealed class WalletCreationType(val value: String) {
|
||||
object PrivateKey : WalletCreationType("Private key")
|
||||
object NewSeed : WalletCreationType("New seed")
|
||||
object SeedImport : WalletCreationType("Seed import")
|
||||
data object PrivateKey : WalletCreationType("Private key")
|
||||
data object NewSeed : WalletCreationType("New seed")
|
||||
data object SeedImport : WalletCreationType("Seed import")
|
||||
}
|
||||
|
||||
sealed class WalletType(val value: String) {
|
||||
object MultiCurrency : WalletType(value = "Multicurrency")
|
||||
data object MultiCurrency : WalletType(value = "Multicurrency")
|
||||
class SingleCurrency(currencyName: String) : WalletType(currencyName)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ data class PromotionInfoResponse(
|
|||
data class BannerState(
|
||||
@Json(name = "timeline") val timeline: Timeline,
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "link") val link: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import retrofit2.http.*
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
interface TangemTechApi {
|
||||
|
||||
@GET("coins")
|
||||
|
|
@ -126,4 +127,7 @@ interface TangemTechApi {
|
|||
@Header("card_id") cardId: String,
|
||||
@Path("account_id") accountId: Int,
|
||||
): ApiResponse<UserTokensAccountResponse>
|
||||
|
||||
@GET("features")
|
||||
suspend fun getFeatures(): ApiResponse<FeaturesResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
data class FeaturesResponse(
|
||||
@Json(name = "send") val isNewSendEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -99,6 +99,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
),
|
||||
chiaFireAcademyApiKey = configValues.chiaFireAcademyApiKey,
|
||||
chiaTangemApiKey = configValues.chiaTangemApiKey,
|
||||
polygonScanApiKey = configValues.polygonScanApiKey,
|
||||
),
|
||||
amplitudeApiKey = configValues.amplitudeApiKey,
|
||||
sprinklr = configValues.sprinklr,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class ConfigValueModel(
|
|||
val chiaTangemApiKey: String?,
|
||||
val devExpress: ExpressModel?,
|
||||
val express: ExpressModel?,
|
||||
val polygonScanApiKey: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ object PreferencesKeys {
|
|||
booleanPreferencesKey(name = "isTokenSwapPromoChangellyShown")
|
||||
}
|
||||
|
||||
val IS_WALLET_TRAVALA_PROMO_SHOWN_KEY by lazy {
|
||||
booleanPreferencesKey(name = "isWalletTravalaPromoShown")
|
||||
}
|
||||
|
||||
val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") }
|
||||
|
||||
val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") }
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@
|
|||
},
|
||||
{
|
||||
"name": "REDESIGNED_SEND_SCREEN_ENABLED",
|
||||
"version": "5.9.1"
|
||||
"version": "5.10.0"
|
||||
},
|
||||
{
|
||||
"name": "LOCAL_USER_LOGS_ENABLED",
|
||||
"version": "5.10.0"
|
||||
"version": "5.11.0"
|
||||
},
|
||||
{
|
||||
"name": "GENERATE_XPUB_ENABLED",
|
||||
|
|
@ -26,5 +26,9 @@
|
|||
{
|
||||
"name": "TOKEN_LIST_LCE_ENABLED",
|
||||
"version": "5.10.0"
|
||||
},
|
||||
{
|
||||
"name": "CARDANO_TOKENS_SUPPORT_ENABLED",
|
||||
"version": "5.11.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,16 +4,12 @@
|
|||
<string name="add_tokens_title">Валюты</string>
|
||||
<string name="address_qr_code_message_format">Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств.</string>
|
||||
<string name="alert_button_request_support">Обратиться в поддержку</string>
|
||||
<string name="alert_button_try_again">Попробовать снова</string>
|
||||
<string name="alert_demo_feature_disabled">Эта функция недоступна в демонстрационном режиме</string>
|
||||
<string name="alert_failed_to_send_transaction_message">Причина: %s</string>
|
||||
<string name="alert_failed_to_send_transaction_title">Не могу отправить транзакцию</string>
|
||||
<string name="alert_manage_tokens_unsupported_blockchain_by_card_message">Выбранный кошелёк не поддерживает сеть %1$s</string>
|
||||
<string name="alert_manage_tokens_unsupported_curve_message">Для активации криптографии сети %1$s необходимо сбросить кошелек до заводских настроек. Пожалуйста, выведите свои средства, чтобы не потерять их, после сброса доступ к текущему кошельку будет невозможен.</string>
|
||||
<string name="alert_manage_tokens_unsupported_message">Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки.</string>
|
||||
<string name="alert_negative_app_rate_sent_message">Спасибо за ваш отзыв. Мы ответим в кратчайшие сроки.</string>
|
||||
<string name="alert_negative_app_rate_sent_title">Ваши предложения отправлены</string>
|
||||
<string name="alert_troubleshooting_scan_card_message">Пожалуйста, попробуйте приложить карту в точности, как показано на анимации, или запросите поддержку.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">У вас возникли трудности со сканированием карты?</string>
|
||||
<string name="alert_unsupported_card">Эта карта не предназначена для работы с этим приложением</string>
|
||||
<string name="app_settings_default_fee_footer">Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться.</string>
|
||||
|
|
@ -28,8 +24,6 @@
|
|||
<string name="app_settings_theme_mode_dark">Тёмная</string>
|
||||
<string name="app_settings_theme_mode_light">Светлая</string>
|
||||
<string name="app_settings_theme_mode_system">Как в системе</string>
|
||||
<string name="app_settings_theme_selection_footer">При выборе настройки как в системе приложение будет использовать тему в соответствии с настройками вашего устройства</string>
|
||||
<string name="app_settings_theme_selection_system_short">Системная</string>
|
||||
<string name="app_settings_theme_selector_title">Тема</string>
|
||||
<string name="app_settings_title">Настройки приложения</string>
|
||||
<string name="balance_hidden_description">Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\"</string>
|
||||
|
|
@ -57,10 +51,12 @@
|
|||
<string name="card_settings_reset_card_to_factory">Заводские настройки</string>
|
||||
<string name="card_settings_security_mode">Тип безопасности</string>
|
||||
<string name="card_settings_title">Настройки карты</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_description">Ввиду особенностей сети Cardano при транзакции токена %1$s помимо комиссии сети будет списано %2$s</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_description">Чтобы совершить транзакцию %1$s, вы должны внести некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA. (рекомендуется баланс в размере 5 ADA)</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_title">Недостаточно ADA для отправки токена</string>
|
||||
<string name="cardano_max_amount_has_token_description">Сеть Cardano не позволяет вам оставлять на своем балансе сумму ниже минимального значения %1$s, если у вас есть токены. Пожалуйста, убедитесь, что у вас осталось немного ADA.</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_description">Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_title">Требования к транзакции Cardano</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_description">Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA)</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_title">Недостаточно ADA для транзакции</string>
|
||||
<string name="cardano_max_amount_has_token_description">Вы должны поддерживать некоторое количество ADA, поскольку у вас на балансе есть токены в сети Cardano</string>
|
||||
<string name="cardano_max_amount_has_token_title">Недостаточно ADA</string>
|
||||
<string name="common_accept">Принять</string>
|
||||
<string name="common_access_denied">Доступ запрещен</string>
|
||||
<string name="common_apply">Применить</string>
|
||||
|
|
@ -81,7 +77,6 @@
|
|||
<string name="common_create">Создать</string>
|
||||
<string name="common_delete">Удалить</string>
|
||||
<string name="common_disabled">Отключено</string>
|
||||
<string name="common_disconnect">Отключить</string>
|
||||
<string name="common_done">Готово</string>
|
||||
<string name="common_enable">Включить</string>
|
||||
<string name="common_enabled">Включено</string>
|
||||
|
|
@ -102,6 +97,7 @@
|
|||
<string name="common_locked">Заблокирован</string>
|
||||
<string name="common_main_network">Основная сеть</string>
|
||||
<string name="common_network_fee_title">Сетевая комиссия</string>
|
||||
<string name="common_network_fee_warning_content">Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии</string>
|
||||
<string name="common_next">Далее</string>
|
||||
<string name="common_no">Нет</string>
|
||||
<string name="common_no_address">Нет адреса</string>
|
||||
|
|
@ -114,7 +110,6 @@
|
|||
<string name="common_reject">Отклонить</string>
|
||||
<string name="common_reload">Перезагрузить</string>
|
||||
<string name="common_rename">Переименовать</string>
|
||||
<string name="common_retry">Повторить</string>
|
||||
<string name="common_save_changes">Сохранить изменения</string>
|
||||
<string name="common_search">Искать</string>
|
||||
<string name="common_search_tokens">Поиск токенов</string>
|
||||
|
|
@ -138,7 +133,6 @@
|
|||
<string name="common_understand">Я понял</string>
|
||||
<string name="common_unknown_error">Произошла ошибка. Пожалуйста, попробуйте снова.</string>
|
||||
<string name="common_unreachable">Недоступно</string>
|
||||
<string name="common_warning">Предупреждение</string>
|
||||
<string name="common_yes">Да</string>
|
||||
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
|
||||
<string name="currency_subtitle_expanded">Доступные сети</string>
|
||||
|
|
@ -185,7 +179,6 @@
|
|||
<string name="details_row_title_flip_to_hide">Скрывать балансы жестом переворота</string>
|
||||
<string name="details_row_title_issuer">Эмитент</string>
|
||||
<string name="details_row_title_signed_hashes">Подписано</string>
|
||||
<string name="details_security_management_warning">Если вы забудете код, то потеряете доступ к своим средствам. Восстановление кода невозможно.</string>
|
||||
<string name="details_title">Подробности</string>
|
||||
<string name="disclaimer_error_loading">Проверьте подключение с интернетом или переключитесь на другую сеть</string>
|
||||
<string name="disclaimer_title">Условия использования</string>
|
||||
|
|
@ -267,6 +260,9 @@
|
|||
<string name="main_swap_changelly_promotion_message">Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля.</string>
|
||||
<string name="main_swap_changelly_promotion_title">Обмен с Changelly, %s комиссии</string>
|
||||
<string name="main_tokens">Токены</string>
|
||||
<string name="main_travala_promotion_button">Забронировать</string>
|
||||
<string name="main_travala_promotion_description" formatted="false">Оплатите его криптой и сэкономьте **50 долларов** через нашего партнера Travala: **%1s - %2s**</string>
|
||||
<string name="main_travala_promotion_title">Забронируйте отпуск с Tangem</string>
|
||||
<string name="manage_tokens_add">Добавить</string>
|
||||
<string name="manage_tokens_edit">Изменить</string>
|
||||
<string name="manage_tokens_list_header_title">Рыночная капитализация</string>
|
||||
|
|
@ -445,11 +441,13 @@
|
|||
<string name="send_alert_transaction_failed_text">Причина: %1$s\nКод: %2$s</string>
|
||||
<string name="send_alert_transaction_failed_title">Транзакция не выполнена</string>
|
||||
<string name="send_amount_label">Сумма</string>
|
||||
<string name="send_bitcoin_custom_fee_footer">Вы можете установить комиссию за транзакцию, изменив значение в поле Satoshi per vByte.</string>
|
||||
<string name="send_date_format">%1$s, %2$s</string>
|
||||
<string name="send_destination_hint_address">Адрес</string>
|
||||
<string name="send_destination_tag_field">Код назначения</string>
|
||||
<string name="send_enter_address_field">Введите адрес</string>
|
||||
<string name="send_error_address_same_as_wallet">Адрес совпадает с адресом кошелька</string>
|
||||
<string name="send_evm_custom_fee_footer">Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение.</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Недопустимый Tag. Он не будет добавлен в транзакцию.</string>
|
||||
<string name="send_extras_error_invalid_memo">Недопустимый Memo. Он не будет добавлен в транзакцию.</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
|
|
@ -468,26 +466,23 @@
|
|||
<string name="send_max_amount">Всё</string>
|
||||
<string name="send_max_amount_label">Максимальная сумма</string>
|
||||
<string name="send_max_fee">Комиссия не превысит </string>
|
||||
<string name="send_max_fee_footer">Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение.</string>
|
||||
<string name="send_memo_destination_tag_error">Допустим ввод только цифр</string>
|
||||
<string name="send_network_fee_warning_content">Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии</string>
|
||||
<string name="send_network_fee_warning_title">Покрытие сетевой комиссии</string>
|
||||
<string name="send_notification_exceed_balance_text">Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса</string>
|
||||
<string name="send_notification_exceed_balance_title">Недостаточно средств</string>
|
||||
<string name="send_notification_existential_deposit_button">Оставить %s</string>
|
||||
<string name="send_notification_existential_deposit_text">Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе.</string>
|
||||
<string name="send_notification_existential_deposit_title">Экзистенциальный депозит</string>
|
||||
<string name="send_notification_fee_too_high_text">Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна.</string>
|
||||
<string name="send_notification_fee_too_high_title">Установлена высокая комиссия</string>
|
||||
<string name="send_notification_high_fee_text">Ввиду особенности сети Tezos комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %s.</string>
|
||||
<string name="send_notification_high_fee_text">Ввиду особенности сети %1$s комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %2$s.</string>
|
||||
<string name="send_notification_high_fee_title">Комиссия повышена</string>
|
||||
<string name="send_notification_invalid_amount_text">Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению</string>
|
||||
<string name="send_notification_invalid_amount_title">Недопустимая сумма</string>
|
||||
<string name="send_notification_invalid_minimum_amount_text">Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_text">Адрес получателя не активирован. \nПожалуйста, измените сумму отправки, чтобы продолжить.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_title">Сумма отправки не может быть менее %s</string>
|
||||
<string name="send_notification_leave_button">Оставить %s</string>
|
||||
<string name="send_notification_reduce_by">Уменьшить на %s</string>
|
||||
<string name="send_notification_reduce_to">Уменьшить до %s</string>
|
||||
<string name="send_notification_transaction_delay_text">Обратите внимание, что при определенных параметрах комиссии возможны задержки по вашей транзакции</string>
|
||||
<string name="send_notification_transaction_delay_title">Возможны задержки по транзакции</string>
|
||||
<string name="send_notification_transaction_limit_text">Из-за ограничений %1$s в одну транзакцию может поместиться только %2$s UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму.</string>
|
||||
|
|
@ -497,7 +492,7 @@
|
|||
<string name="send_recent_transactions">Последние</string>
|
||||
<string name="send_recipient">Получатель</string>
|
||||
<string name="send_recipient_address_error">Неверный адрес</string>
|
||||
<string name="send_recipient_address_footer">Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов</string>
|
||||
<string name="send_recipient_address_footer">Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов.</string>
|
||||
<string name="send_recipient_label">Отправить</string>
|
||||
<string name="send_recipient_memo_footer">Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств.</string>
|
||||
<string name="send_recipient_wallets_title">Мои кошельки</string>
|
||||
|
|
@ -506,7 +501,7 @@
|
|||
<string name="send_sending">Отправка</string>
|
||||
<string name="send_summary_tap_hint">Нажмите на любое поле, чтобы изменить его</string>
|
||||
<string name="send_summary_title">Отправка %s</string>
|
||||
<string name="send_summary_transaction_description">Вы отправляете %1$s, включая комиссию сети %2$s</string>
|
||||
<string name="send_summary_transaction_description">Вы отправляете **%1$s**, включая комиссию сети %2$s</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>
|
||||
|
|
@ -535,7 +530,6 @@
|
|||
<string name="swapping_give_permission">Дать разрешение</string>
|
||||
<string name="swapping_high_price_impact_description">Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.</string>
|
||||
<string name="swapping_insufficient_funds">Недостаточно средств</string>
|
||||
<string name="swapping_network_fee_warning_content">Сумма отправки будет уменьшена для покрытия выбранного уровня комиссии</string>
|
||||
<string name="swapping_permission_buttons_approve">Подтвердить</string>
|
||||
<string name="swapping_permission_current_transaction">Текущая транзакция</string>
|
||||
<string name="swapping_permission_fee_footer">Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена.</string>
|
||||
|
|
@ -560,7 +554,6 @@
|
|||
<string name="token_button_unavailability_reason_pending_transaction_sell">Продажа средств станет доступной после завершения транзакции(-ий) в сети %s</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_send">Отправка средств станет доступной после завершения транзакции(-ий) в сети %s</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">В данный момент продажа %s недоступна. Следите за нашими обновлениями.</string>
|
||||
<string name="token_details_choose_address">Выберите адрес</string>
|
||||
<string name="token_details_generate_xpub">Сгенерировать XPUB</string>
|
||||
<string name="token_details_hide_alert_hide">Скрыть</string>
|
||||
<string name="token_details_hide_alert_message">Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.</string>
|
||||
|
|
@ -568,7 +561,7 @@
|
|||
<string name="token_details_hide_token">Скрыть токен</string>
|
||||
<string name="token_details_token_type_subtitle">%1$s токен в сети %%image%% %2$s</string>
|
||||
<string name="token_details_token_type_subtitle_no_standard">Токен в сети %%image%% %1$s</string>
|
||||
<string name="token_details_unable_hide_alert_message">Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.</string>
|
||||
<string name="token_details_unable_hide_alert_message">Токен %1$s (%2$s) является основной валютой в сети %3$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети</string>
|
||||
<string name="token_details_unable_hide_alert_title">Невозможно скрыть %s</string>
|
||||
<string name="token_swap_changelly_promotion_message">Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля.</string>
|
||||
<string name="token_swap_changelly_promotion_title">Обмен с Changelly, %s комиссии</string>
|
||||
|
|
@ -597,6 +590,7 @@
|
|||
<string name="user_wallet_list_delete_prompt">Вы уверены, что хотите удалить этот кошелек?</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">Произошла ошибка, пожалуйста, отсканируйте свою карту для входа</string>
|
||||
<string name="user_wallet_list_error_wallet_already_saved">Этот кошелек уже был сохранен, вы можете добавить другой</string>
|
||||
<string name="user_wallet_list_rename_popup_error_already_exists">Кошелек с именем %s уже существует</string>
|
||||
<string name="user_wallet_list_rename_popup_placeholder">Имя кошелька</string>
|
||||
<string name="user_wallet_list_rename_popup_title">Переименование кошелька</string>
|
||||
<string name="user_wallet_list_unlock_all">Разблокировать все</string>
|
||||
|
|
@ -643,6 +637,8 @@
|
|||
<string name="warning_access_denied_message">Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку</string>
|
||||
<string name="warning_backup_errors_message">Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян.</string>
|
||||
<string name="warning_backup_errors_title">Ошибка активации</string>
|
||||
<string name="warning_beacon_chain_retirement_content">По решению разработчиков сети BNB, стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять свои активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении чтобы перевести их в cеть BNB Smart Chain.</string>
|
||||
<string name="warning_beacon_chain_retirement_title">Отключение сети BNB Beacon Chain</string>
|
||||
<string name="warning_button_could_be_better">Можно лучше</string>
|
||||
<string name="warning_button_like_it">Нравится</string>
|
||||
<string name="warning_button_ok">Понятно!</string>
|
||||
|
|
@ -698,7 +694,6 @@
|
|||
<string name="warning_rent_fee_title">Необходима плата за аренду сети</string>
|
||||
<string name="warning_send_blocked_funds_for_fee_message">%1$s - это монета в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести немного %4$s (%5$s), чтобы покрыть комиссию сети.</string>
|
||||
<string name="warning_send_blocked_funds_for_fee_title">Недостаточно %1$s для оплаты комиссии сети</string>
|
||||
<string name="warning_send_blocked_pending_transactions_title">Транзакция в обработке</string>
|
||||
<string name="warning_solana_fee_message">Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку.</string>
|
||||
<string name="warning_solana_fee_title">Оповещение сети Солана</string>
|
||||
<string name="warning_solana_rent_fee_message">Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату.</string>
|
||||
|
|
|
|||
|
|
@ -4,16 +4,12 @@
|
|||
<string name="add_tokens_title">Manage tokens</string>
|
||||
<string name="address_qr_code_message_format">Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds.</string>
|
||||
<string name="alert_button_request_support">Request support</string>
|
||||
<string name="alert_button_try_again">Try again</string>
|
||||
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
|
||||
<string name="alert_failed_to_send_transaction_message">Reason: %s</string>
|
||||
<string name="alert_failed_to_send_transaction_title">Can\'t send a transaction</string>
|
||||
<string name="alert_manage_tokens_unsupported_blockchain_by_card_message">The selected does not support the %1$s network</string>
|
||||
<string name="alert_manage_tokens_unsupported_curve_message">To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset.</string>
|
||||
<string name="alert_manage_tokens_unsupported_message">Tokens in %1$s network are not supported by this card due to firmware limitation.</string>
|
||||
<string name="alert_negative_app_rate_sent_message">Thank you for your feedback. We will respond as soon as possible</string>
|
||||
<string name="alert_negative_app_rate_sent_title">Your suggestions were sent</string>
|
||||
<string name="alert_troubleshooting_scan_card_message">Please try to tap the card exactly as shown in the animation or request support.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">Are you having difficulty scanning your card?</string>
|
||||
<string name="alert_unsupported_card">This card is not designed to work with this app</string>
|
||||
<string name="app_settings_default_fee">Default Fee</string>
|
||||
|
|
@ -29,8 +25,6 @@
|
|||
<string name="app_settings_theme_mode_dark">Dark</string>
|
||||
<string name="app_settings_theme_mode_light">Light</string>
|
||||
<string name="app_settings_theme_mode_system">System default</string>
|
||||
<string name="app_settings_theme_selection_footer">If system is selected, the app will auto-adjust based on your device\'s system settings</string>
|
||||
<string name="app_settings_theme_selection_system_short">System</string>
|
||||
<string name="app_settings_theme_selector_title">Theme</string>
|
||||
<string name="app_settings_title">App Settings</string>
|
||||
<string name="balance_hidden_description">To hide or show your balances, simply flip your device screen down, or switch it off in Settings</string>
|
||||
|
|
@ -56,10 +50,12 @@
|
|||
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
|
||||
<string name="card_settings_security_mode">Security Mode</string>
|
||||
<string name="card_settings_title">Card Settings</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_description">Due to the peculiarities of the Cardano network, when transacting the %1$s token, in addition to the network commission, %2$s will be charged</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_description">To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value. (5 ADA balance recommended)</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_title">Insufficient ADA to token transfer</string>
|
||||
<string name="cardano_max_amount_has_token_description">The Cardano network does not allow you to leave an amount on your balance below the minimum value %1$s if you have tokens. Please ensure that you have some ADA remaining.</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_description">In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_title">Cardano transaction requirements</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_description">To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended)</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_title">Insufficient ADA for token transfer</string>
|
||||
<string name="cardano_max_amount_has_token_description">You must maintain some ADA because you have some tokens on the Cardano blockchain</string>
|
||||
<string name="cardano_max_amount_has_token_title">Not enough ADA</string>
|
||||
<string name="common_accept">Accept</string>
|
||||
<string name="common_access_denied">Access denied</string>
|
||||
<string name="common_apply">Apply</string>
|
||||
|
|
@ -80,7 +76,6 @@
|
|||
<string name="common_create">Create</string>
|
||||
<string name="common_delete">Delete</string>
|
||||
<string name="common_disabled">Disabled</string>
|
||||
<string name="common_disconnect">Disconnect</string>
|
||||
<string name="common_done">Done</string>
|
||||
<string name="common_enable">Enable</string>
|
||||
<string name="common_enabled">Enabled</string>
|
||||
|
|
@ -101,6 +96,7 @@
|
|||
<string name="common_locked">Locked</string>
|
||||
<string name="common_main_network">Main network</string>
|
||||
<string name="common_network_fee_title">Network fee</string>
|
||||
<string name="common_network_fee_warning_content">Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level</string>
|
||||
<string name="common_next">Next</string>
|
||||
<string name="common_no">No</string>
|
||||
<string name="common_no_address">No address</string>
|
||||
|
|
@ -113,7 +109,6 @@
|
|||
<string name="common_reject">Reject</string>
|
||||
<string name="common_reload">Reload</string>
|
||||
<string name="common_rename">Rename</string>
|
||||
<string name="common_retry">Retry</string>
|
||||
<string name="common_save_changes">Save changes</string>
|
||||
<string name="common_search">Search</string>
|
||||
<string name="common_search_tokens">Search tokens</string>
|
||||
|
|
@ -137,7 +132,6 @@
|
|||
<string name="common_understand">I understand</string>
|
||||
<string name="common_unknown_error">There was an error. Please try again.</string>
|
||||
<string name="common_unreachable">Unreachable</string>
|
||||
<string name="common_warning">Warning</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>
|
||||
|
|
@ -184,7 +178,6 @@
|
|||
<string name="details_row_title_flip_to_hide">Flip-to-Hide Balances</string>
|
||||
<string name="details_row_title_issuer">Issuer</string>
|
||||
<string name="details_row_title_signed_hashes">Signed</string>
|
||||
<string name="details_security_management_warning">If you forget the code you will lose access to your funds. Code recovery is not possible.</string>
|
||||
<string name="details_title">Details</string>
|
||||
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
|
||||
<string name="disclaimer_title">Terms of Service</string>
|
||||
|
|
@ -267,6 +260,9 @@
|
|||
<string name="main_swap_changelly_promotion_message">Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s</string>
|
||||
<string name="main_swap_changelly_promotion_title">Swap with Changelly, %s fees</string>
|
||||
<string name="main_tokens">Tokens</string>
|
||||
<string name="main_travala_promotion_button">Book now</string>
|
||||
<string name="main_travala_promotion_description" formatted="false">Save **$50** while booking via our partner Travala: **%1s - %2s**</string>
|
||||
<string name="main_travala_promotion_title">Book your holidays with Tangem and pay in crypto</string>
|
||||
<string name="manage_tokens_add">Add</string>
|
||||
<string name="manage_tokens_edit">Edit</string>
|
||||
<string name="manage_tokens_list_header_title">Coin market cap</string>
|
||||
|
|
@ -442,11 +438,13 @@
|
|||
<string name="send_amount_label">Amount</string>
|
||||
<string name="send_base_fee">Base fee</string>
|
||||
<string name="send_base_fee_footer">Represents the part of the transaction fee that goes to the miner</string>
|
||||
<string name="send_bitcoin_custom_fee_footer">You can set your transaction fee by adjusting the value in the Satoshi per vByte field.</string>
|
||||
<string name="send_date_format">%1$s, %2$s</string>
|
||||
<string name="send_destination_hint_address">Address</string>
|
||||
<string name="send_destination_tag_field">Destination Tag</string>
|
||||
<string name="send_enter_address_field">Enter address</string>
|
||||
<string name="send_error_address_same_as_wallet">Address is the same as wallet address</string>
|
||||
<string name="send_evm_custom_fee_footer">The fee that will be charged for your transaction. You can set your own value.</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
|
|
@ -465,26 +463,23 @@
|
|||
<string name="send_max_amount">Max</string>
|
||||
<string name="send_max_amount_label">Maximum amount</string>
|
||||
<string name="send_max_fee">Max fee</string>
|
||||
<string name="send_max_fee_footer">The fee that will be charged for your transaction. You can set your own value.</string>
|
||||
<string name="send_memo_destination_tag_error">Numbers only for Destination Tag</string>
|
||||
<string name="send_network_fee_warning_content">Amount sent will be reduced by %1$s (%2$s) to cover the selected fee level</string>
|
||||
<string name="send_network_fee_warning_title">Network fee coverage</string>
|
||||
<string name="send_notification_exceed_balance_text">Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance</string>
|
||||
<string name="send_notification_exceed_balance_title">Total exceeds balance</string>
|
||||
<string name="send_notification_existential_deposit_button">Leave %s</string>
|
||||
<string name="send_notification_existential_deposit_text">The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance.</string>
|
||||
<string name="send_notification_existential_deposit_title">Existential deposit</string>
|
||||
<string name="send_notification_fee_too_high_text">The commission amount is %s times the recommended amount. Make sure that the custom settings are correct.</string>
|
||||
<string name="send_notification_fee_too_high_title">Custom fee is high</string>
|
||||
<string name="send_notification_high_fee_text">Due to the peculiarities of the Tezos network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %s.</string>
|
||||
<string name="send_notification_high_fee_text">Due to the peculiarities of the %1$s network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %2$s.</string>
|
||||
<string name="send_notification_high_fee_title">The fee is higher</string>
|
||||
<string name="send_notification_invalid_amount_text">The included commission exceeds the transfer amount, leading to a negative value</string>
|
||||
<string name="send_notification_invalid_amount_title">Invalid amount</string>
|
||||
<string name="send_notification_invalid_minimum_amount_text">The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_text">Target account is not created. Please change the amount to send.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_title">The amount to send must be at least %s</string>
|
||||
<string name="send_notification_leave_button">Leave %s</string>
|
||||
<string name="send_notification_reduce_by">Reduce by %s</string>
|
||||
<string name="send_notification_reduce_to">Reduce to %s</string>
|
||||
<string name="send_notification_transaction_delay_text">Kindly be aware that your transaction may experience delays under specific fee settings</string>
|
||||
<string name="send_notification_transaction_delay_title">Transaction delays are possible</string>
|
||||
<string name="send_notification_transaction_limit_text">Due to %1$s limitations only %2$s UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount.</string>
|
||||
|
|
@ -496,7 +491,7 @@
|
|||
<string name="send_recent_transactions">Recent</string>
|
||||
<string name="send_recipient">Recipient</string>
|
||||
<string name="send_recipient_address_error">Not a valid address</string>
|
||||
<string name="send_recipient_address_footer">Ensure that you are sending funds to an %s wallet address. Errors may result in the loss of your tokens</string>
|
||||
<string name="send_recipient_address_footer">Ensure the receiving wallet address is on the %s network to avoid losing your tokens</string>
|
||||
<string name="send_recipient_label">Send to</string>
|
||||
<string name="send_recipient_memo_footer">A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds</string>
|
||||
<string name="send_recipient_wallets_title">My wallets</string>
|
||||
|
|
@ -505,7 +500,7 @@
|
|||
<string name="send_sending">Sending...</string>
|
||||
<string name="send_summary_tap_hint">Tap any field to change it</string>
|
||||
<string name="send_summary_title">Send %s</string>
|
||||
<string name="send_summary_transaction_description">You are sending %1$s including a network fee of %2$s</string>
|
||||
<string name="send_summary_transaction_description">You are sending **%1$s** including a network fee of %2$s</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>
|
||||
|
|
@ -535,7 +530,6 @@
|
|||
<string name="swapping_give_permission">Give Permission</string>
|
||||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">Insufficient funds</string>
|
||||
<string name="swapping_network_fee_warning_content">Sending amount will be reduced to cover the selected fee level</string>
|
||||
<string name="swapping_permission_buttons_approve">Approve</string>
|
||||
<string name="swapping_permission_current_transaction">Current transaction</string>
|
||||
<string name="swapping_permission_fee_footer">The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap.</string>
|
||||
|
|
@ -560,7 +554,6 @@
|
|||
<string name="token_button_unavailability_reason_pending_transaction_sell">Selling funds will be available once the pending transaction(s) in network %s is complete</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_send">Sending funds will be available once the pending transaction(s) in network %s is complete</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">Selling %s is not available at the moment. Please check our updates.</string>
|
||||
<string name="token_details_choose_address">Choose address</string>
|
||||
<string name="token_details_generate_xpub">Generate XPUB</string>
|
||||
<string name="token_details_hide_alert_hide">Hide</string>
|
||||
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
|
||||
|
|
@ -568,7 +561,7 @@
|
|||
<string name="token_details_hide_token">Hide token</string>
|
||||
<string name="token_details_token_type_subtitle">%1$s token in %%image%% %2$s network</string>
|
||||
<string name="token_details_token_type_subtitle_no_standard">Token in %%image%% %1$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_message">The %1$s (%2$s) token is the main currency on the %3$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_swap_changelly_promotion_message">Exchange this token for another at %1$s service fees from February %2$s-%3$s.</string>
|
||||
<string name="token_swap_changelly_promotion_title">Swap with Changelly, %s fees</string>
|
||||
|
|
@ -597,6 +590,7 @@
|
|||
<string name="user_wallet_list_delete_prompt">Are you sure you want to delete this wallet?</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card to log in</string>
|
||||
<string name="user_wallet_list_error_wallet_already_saved">This wallet has already been saved, you can add another one</string>
|
||||
<string name="user_wallet_list_rename_popup_error_already_exists">The wallet with name %s already exists</string>
|
||||
<string name="user_wallet_list_rename_popup_placeholder">Wallet name</string>
|
||||
<string name="user_wallet_list_rename_popup_title">Rename Wallet</string>
|
||||
<string name="user_wallet_list_unlock_all">Unlock all</string>
|
||||
|
|
@ -643,6 +637,8 @@
|
|||
<string name="warning_access_denied_message">Use %s or scan a card to unlock access to your wallet</string>
|
||||
<string name="warning_backup_errors_message">Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost.</string>
|
||||
<string name="warning_backup_errors_title">Activation error</string>
|
||||
<string name="warning_beacon_chain_retirement_content">According to BNB network developers, support for the BEP-2 standard will end in June 2024. To avoid losing assets with this standard, please convert them to the BEP-20 standard. Use our swap service to transfer them to the BNB Smart Chain network.</string>
|
||||
<string name="warning_beacon_chain_retirement_title">BNB Beacon Chain will shut down</string>
|
||||
<string name="warning_button_could_be_better">Could be better</string>
|
||||
<string name="warning_button_like_it">Like it</string>
|
||||
<string name="warning_button_ok">Ok, Got it!</string>
|
||||
|
|
@ -674,7 +670,7 @@
|
|||
<string name="warning_hedera_missing_token_association_message">This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message_brief">This token must be associated with your Hedera account before you can receive it</string>
|
||||
<string name="warning_hedera_missing_token_association_title">Associate your token</string>
|
||||
<string name="warning_hedera_token_association_not_enough_hbar_message">Hot enough %s. Top up your Hedera account to associate this token</string>
|
||||
<string name="warning_hedera_token_association_not_enough_hbar_message">Not enough %s. Top up your Hedera account to associate this token</string>
|
||||
<string name="warning_low_signatures_message">Only %s signatures are left on this card. You must withdraw all of your funds.</string>
|
||||
<string name="warning_low_signatures_title">Low signature count</string>
|
||||
<string name="warning_manage_tokens_legacy_derivation_message">Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds.</string>
|
||||
|
|
@ -696,7 +692,6 @@
|
|||
<string name="warning_rent_fee_title">Network rent fee required</string>
|
||||
<string name="warning_send_blocked_funds_for_fee_message">%1$s is an asset in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee.</string>
|
||||
<string name="warning_send_blocked_funds_for_fee_title">Insufficient %1$s to cover network fee</string>
|
||||
<string name="warning_send_blocked_pending_transactions_title">Transaction pending</string>
|
||||
<string name="warning_solana_fee_message">The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction.</string>
|
||||
<string name="warning_solana_fee_title">Solana Network Alert</string>
|
||||
<string name="warning_solana_rent_fee_message">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>
|
||||
|
|
|
|||
|
|
@ -46,4 +46,5 @@ dependencies {
|
|||
implementation(deps.zxing.qrCore)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.markdown)
|
||||
}
|
||||
11
core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt
Normal file
11
core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.core.ui
|
||||
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
|
||||
interface UiDependencies {
|
||||
|
||||
val hapticManager: HapticManager
|
||||
|
||||
val appThemeModeHolder: AppThemeModeHolder
|
||||
}
|
||||
|
|
@ -95,10 +95,14 @@ fun AmountTextField(
|
|||
SimpleTextField(
|
||||
value = value,
|
||||
onValueChange = { newText ->
|
||||
if (decimalFormat.isValidSymbols(newText)) {
|
||||
val trimmed = decimalFormat.getValidatedNumberWithFixedDecimals(newText, decimals)
|
||||
onValueChange(trimmed)
|
||||
}
|
||||
onValueChange(
|
||||
prepareEnter(
|
||||
oldValue = value,
|
||||
newValue = newText,
|
||||
decimalFormat = decimalFormat,
|
||||
decimals = decimals,
|
||||
),
|
||||
)
|
||||
},
|
||||
textStyle = textStyle.copy(
|
||||
fontSize = fontSize,
|
||||
|
|
@ -117,8 +121,37 @@ fun AmountTextField(
|
|||
}
|
||||
}
|
||||
|
||||
private fun prepareEnter(oldValue: String, newValue: String, decimalFormat: DecimalFormat, decimals: Int): String {
|
||||
val decimalSymbol = decimalFormat.decimalFormatSymbols.decimalSeparator
|
||||
return if (decimalFormat.isValidSymbols(newValue)) {
|
||||
val parsedValue = newValue.parseBigDecimalOrNull()?.toPlainString()
|
||||
?: if (newValue.isBlank()) "" else oldValue
|
||||
val replacedWithSymbol = if (parsedValue.findLast { it != decimalSymbol } != null) {
|
||||
when {
|
||||
parsedValue.findLast { it == COMMA_SEPARATOR } != null -> {
|
||||
parsedValue.replace(COMMA_SEPARATOR, decimalSymbol)
|
||||
}
|
||||
parsedValue.findLast { it == POINT_SEPARATOR } != null -> {
|
||||
parsedValue.replace(POINT_SEPARATOR, decimalSymbol)
|
||||
}
|
||||
else -> parsedValue
|
||||
}
|
||||
} else {
|
||||
parsedValue
|
||||
}
|
||||
val joinedSymbol = if (newValue.endsWith(COMMA_SEPARATOR) || newValue.endsWith(POINT_SEPARATOR)) {
|
||||
replacedWithSymbol.plus(decimalSymbol)
|
||||
} else {
|
||||
replacedWithSymbol
|
||||
}
|
||||
decimalFormat.getValidatedNumberWithFixedDecimals(joinedSymbol, decimals)
|
||||
} else {
|
||||
oldValue
|
||||
}
|
||||
}
|
||||
|
||||
private fun DecimalFormat.isValidSymbols(text: String): Boolean {
|
||||
return checkDecimalSeparatorDuplicate(text) && checkGroupingSeparator(text)
|
||||
return checkDecimalSeparatorDuplicate(text)
|
||||
}
|
||||
|
||||
// region preview
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ fun SimpleTextField(
|
|||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
color: Color = TangemTheme.colors.text.primary1,
|
||||
textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color),
|
||||
placeholderColor: Color = TangemTheme.colors.text.disabled,
|
||||
readOnly: Boolean = false,
|
||||
isValuePasted: Boolean = false,
|
||||
onValuePastedTriggerDismiss: () -> Unit = {},
|
||||
|
|
@ -108,6 +109,7 @@ fun SimpleTextField(
|
|||
value = value,
|
||||
textStyle = textStyle,
|
||||
textValue = textValue,
|
||||
color = placeholderColor,
|
||||
)
|
||||
},
|
||||
modifier = modifier
|
||||
|
|
@ -122,6 +124,7 @@ private fun SimpleTextPlaceholder(
|
|||
value: String,
|
||||
textStyle: TextStyle,
|
||||
textValue: @Composable () -> Unit,
|
||||
color: Color = TangemTheme.colors.text.disabled,
|
||||
) {
|
||||
Box {
|
||||
if (value.isBlank() && placeholder != null) {
|
||||
|
|
@ -132,7 +135,7 @@ private fun SimpleTextPlaceholder(
|
|||
Text(
|
||||
text = it.resolveReference(),
|
||||
style = textStyle,
|
||||
color = TangemTheme.colors.text.disabled,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,12 +51,14 @@ fun Notification(
|
|||
modifier: Modifier = Modifier,
|
||||
containerColor: Color? = null,
|
||||
iconTint: Color? = null,
|
||||
isEnabled: Boolean = true,
|
||||
) {
|
||||
BaseContainer(
|
||||
buttonsState = config.buttonsState,
|
||||
onClick = config.onClick,
|
||||
modifier = modifier,
|
||||
containerColor = containerColor,
|
||||
isEnabled = isEnabled,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
|
||||
|
|
@ -67,15 +69,16 @@ fun Notification(
|
|||
iconTint = iconTint,
|
||||
title = config.title,
|
||||
subtitle = config.subtitle,
|
||||
isClickableComponent = config.onClick != null,
|
||||
isClickableComponent = isEnabled && config.onClick != null,
|
||||
)
|
||||
|
||||
Buttons(state = config.buttonsState)
|
||||
Buttons(state = config.buttonsState, isEnabled = isEnabled)
|
||||
}
|
||||
|
||||
CloseableIconButton(
|
||||
onClick = config.onCloseClick,
|
||||
modifier = Modifier.align(alignment = Alignment.TopEnd),
|
||||
isEnabled = isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +88,7 @@ private fun BaseContainer(
|
|||
buttonsState: NotificationConfig.ButtonsState?,
|
||||
onClick: (() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean = true,
|
||||
containerColor: Color? = null,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
|
|
@ -101,7 +105,7 @@ private fun BaseContainer(
|
|||
modifier = modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
|
||||
.fillMaxWidth(),
|
||||
enabled = onClick != null,
|
||||
enabled = onClick != null && isEnabled,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
color = containerColor ?: tempContainerColor,
|
||||
) {
|
||||
|
|
@ -179,27 +183,31 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Buttons(state: NotificationButtonsState?) {
|
||||
private fun Buttons(state: NotificationButtonsState?, isEnabled: Boolean = true) {
|
||||
when (state) {
|
||||
is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(config = state)
|
||||
is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state)
|
||||
is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state)
|
||||
is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(
|
||||
config = state,
|
||||
isEnabled = isEnabled,
|
||||
)
|
||||
is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state, isEnabled = isEnabled)
|
||||
is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state, isEnabled = isEnabled)
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig) {
|
||||
private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig, isEnabled: Boolean = true) {
|
||||
SecondaryButton(
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig) {
|
||||
private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonConfig, isEnabled: Boolean = true) {
|
||||
if (config.iconResId != null) {
|
||||
PrimaryButtonIconEnd(
|
||||
text = config.text.resolveReference(),
|
||||
|
|
@ -207,6 +215,7 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo
|
|||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
} else {
|
||||
PrimaryButton(
|
||||
|
|
@ -214,18 +223,20 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo
|
|||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) {
|
||||
private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig, isEnabled: Boolean = true) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8)) {
|
||||
SecondaryButton(
|
||||
text = config.secondaryText.resolveReference(),
|
||||
onClick = config.onSecondaryClick,
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
|
||||
PrimaryButton(
|
||||
|
|
@ -233,12 +244,13 @@ private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) {
|
|||
onClick = config.onPrimaryClick,
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) {
|
||||
private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier, isEnabled: Boolean = true) {
|
||||
AnimatedVisibility(visible = onClick != null, modifier = modifier) {
|
||||
onClick ?: return@AnimatedVisibility
|
||||
|
||||
|
|
@ -255,6 +267,7 @@ private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Mod
|
|||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = LocalIndication.current,
|
||||
role = Role.Button,
|
||||
enabled = isEnabled,
|
||||
onClick = onClick,
|
||||
),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,11 @@ fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier =
|
|||
modifier = modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium),
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.clickable(
|
||||
enabled = config.onClick != null,
|
||||
onClick = config.onClick ?: {},
|
||||
),
|
||||
) {
|
||||
val (iconRef, titleRef, subtitleRef, closeIconRef, buttonRef, backgroundRef) = createRefs()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
package com.tangem.core.ui.components.notifications
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.ScaleFactor
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.LineBreak
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonColors
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemColorPalette.White
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Travala notification with image background
|
||||
* @see <a href="https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=11690-12057&mode=design&t=eFnsA9sNytcQIoQ4-4">Travala Promo</a>
|
||||
*/
|
||||
@Suppress("LongMethod", "DestructuringDeclarationWithTooManyEntries")
|
||||
@Composable
|
||||
fun TravalaNotificationWithBackground(config: NotificationConfig, modifier: Modifier = Modifier) {
|
||||
val button = config.buttonsState as? NotificationConfig.ButtonsState.SecondaryButtonConfig
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(Color.Black)
|
||||
.clickable(
|
||||
enabled = config.onClick != null,
|
||||
onClick = config.onClick ?: {},
|
||||
),
|
||||
propagateMinConstraints = true,
|
||||
contentAlignment = Alignment.TopStart,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_travala_banner_promo_background),
|
||||
contentDescription = null,
|
||||
contentScale = TravalaBackgroundScale(density),
|
||||
alignment = Alignment.TopStart,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.wrapContentSize(unbounded = true, align = Alignment.TopStart)
|
||||
.align(Alignment.TopStart),
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_travala_banner_promo_background_2),
|
||||
contentDescription = null,
|
||||
contentScale = TravalaBackgroundScale(density),
|
||||
alignment = Alignment.TopStart,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.wrapContentSize(unbounded = true, align = Alignment.TopEnd)
|
||||
.align(Alignment.TopEnd),
|
||||
)
|
||||
Column {
|
||||
Row {
|
||||
Box(modifier = Modifier.size(87.dp))
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(top = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = config.title.resolveReference(),
|
||||
style = TangemTheme.typography.button.copy(
|
||||
lineBreak = LineBreak.Heading,
|
||||
),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
SpacerH8()
|
||||
Text(
|
||||
text = formatSubtitle(config.subtitle.resolveReference()),
|
||||
style = TangemTheme.typography.caption2.copy(
|
||||
lineBreak = LineBreak.Heading,
|
||||
),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.text.constantWhite,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
start = TangemTheme.dimens.spacing2,
|
||||
)
|
||||
.size(TangemTheme.dimens.size16)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
) {
|
||||
config.onCloseClick?.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
TangemButton(
|
||||
text = button?.text?.resolveReference().orEmpty(),
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = button?.onClick ?: {},
|
||||
colors = TangemButtonColors(
|
||||
backgroundColor = White.copy(alpha = 0.3f),
|
||||
contentColor = White,
|
||||
disabledBackgroundColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
),
|
||||
enabled = true,
|
||||
showProgress = false,
|
||||
modifier = Modifier
|
||||
.padding(TangemTheme.dimens.spacing12)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TRAVALA_BACKGROUND_SRC_IMG_SCALE = 4
|
||||
|
||||
private class TravalaBackgroundScale(
|
||||
val density: Density,
|
||||
) : ContentScale {
|
||||
override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor {
|
||||
with(density) {
|
||||
val originalWidth = (srcSize.width / TRAVALA_BACKGROUND_SRC_IMG_SCALE).dp.toPx()
|
||||
val widthScale = originalWidth / srcSize.width
|
||||
val originalHeight = (srcSize.height / TRAVALA_BACKGROUND_SRC_IMG_SCALE).dp.toPx()
|
||||
val heightScale = originalHeight / srcSize.height
|
||||
return ScaleFactor(widthScale, heightScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun formatSubtitle(subtitle: String): AnnotatedString {
|
||||
val pattern = Regex("\\*\\*(.*?)\\*\\*")
|
||||
var startIndex = 0
|
||||
val annotatedString = buildAnnotatedString {
|
||||
pattern.findAll(subtitle).forEach { matchResult ->
|
||||
val index = matchResult.range.first
|
||||
val matchedValue = matchResult.groups[1]?.value ?: ""
|
||||
|
||||
// appends unformatted part
|
||||
append(subtitle.substring(startIndex, index))
|
||||
|
||||
// applies style on ^^-wrapped parts
|
||||
withStyle(SpanStyle(fontWeight = TangemTheme.typography.caption1.fontWeight)) {
|
||||
append(matchedValue)
|
||||
}
|
||||
|
||||
// goes to next part
|
||||
startIndex = matchResult.range.last + 1
|
||||
}
|
||||
|
||||
// appends remaining ending if exists
|
||||
append(subtitle.substring(startIndex))
|
||||
}
|
||||
|
||||
return annotatedString
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TravalaNotificationWithBackgroundPreview() {
|
||||
TangemTheme {
|
||||
TravalaNotificationWithBackground(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.main_travala_promotion_title,
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.main_travala_promotion_description,
|
||||
formatArgs = wrappedList("May 13", "June 12"),
|
||||
),
|
||||
iconResId = R.drawable.img_swap_promo,
|
||||
backgroundResId = R.drawable.img_travala_banner_promo_background,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(id = R.string.token_swap_promotion_button),
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.tangem.core.ui.components.snackbar
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SnackbarData
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Snackbar to inform the user about copying text to the clipboard
|
||||
*
|
||||
* @param message message
|
||||
* @param modifier modifier
|
||||
*
|
||||
* @see <a href = https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2001-728&t=kDzSZDx0m0sk4iYz-4
|
||||
* >Figma</a>
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun CopiedTextSnackbar(message: TextReference, modifier: Modifier = Modifier) {
|
||||
BaseSnackbar(message = message, modifier = modifier)
|
||||
}
|
||||
|
||||
/**
|
||||
* Snackbar to inform the user about copying text to the clipboard.
|
||||
*
|
||||
* @param snackbarData this is needed to better support Material3.SnackbarHost, but only supports the message field
|
||||
* @param modifier modifier
|
||||
*
|
||||
* @see <a href = https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2001-728&t=kDzSZDx0m0sk4iYz-4
|
||||
* >Figma</a>
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun CopiedTextSnackbar(snackbarData: SnackbarData, modifier: Modifier = Modifier) {
|
||||
BaseSnackbar(message = stringReference(snackbarData.visuals.message), modifier = modifier)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BaseSnackbar(message: TextReference, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(color = TangemTheme.colors.icon.secondary, shape = TangemTheme.shapes.roundedCorners8)
|
||||
.heightIn(min = TangemTheme.dimens.size48)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
MarkIcon()
|
||||
|
||||
MessageText(text = message, modifier = Modifier.weight(weight = 1f, fill = false))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MarkIcon() {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_check_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MessageText(text: TextReference, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
modifier = modifier,
|
||||
color = TangemTheme.colors.text.disabled,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(widthDp = 344, showBackground = true, fontScale = 1f)
|
||||
@Preview(widthDp = 344, showBackground = true, fontScale = 1f, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview(widthDp = 344, showBackground = true, fontScale = 2f)
|
||||
@Composable
|
||||
private fun Preview_CopiedTextSnackbar(
|
||||
@PreviewParameter(CopiedTextSnackbarDataProvider::class) message: TextReference,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
CopiedTextSnackbar(message = message)
|
||||
}
|
||||
}
|
||||
|
||||
private class CopiedTextSnackbarDataProvider : CollectionPreviewParameterProvider<TextReference>(
|
||||
collection = listOf(
|
||||
stringReference(value = "Copied!"),
|
||||
stringReference(value = "Contract address copied!"),
|
||||
stringReference(value = "Coooooooooooontract addreeeeeeeeeeeeeeeess coooooooooooooooopied!"),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.core.ui.components.snackbar
|
||||
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
/**
|
||||
* SnackbarHost to inform the user about copying text to the clipboard
|
||||
* Based on Material3 component. It's best way to show [CopiedTextSnackbar].
|
||||
*
|
||||
* @param hostState snackbar host state
|
||||
* @param modifier modifier
|
||||
*
|
||||
* @see <a href = https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2001-728&t=kDzSZDx0m0sk4iYz-4
|
||||
* >Figma</a>
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun CopiedTextSnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) {
|
||||
SnackbarHost(hostState = hostState, modifier = modifier) {
|
||||
CopiedTextSnackbar(snackbarData = it)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.core.ui.components.snackbar
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* Tangem snackbar.
|
||||
* Based on Material3 component. It can be presented as one line or multi lines snackbar – depends on text length.
|
||||
*
|
||||
* @param data snackbar data
|
||||
* @param modifier modifier
|
||||
* @param actionOnNewLine flag that indicates if the action should be displayed on a new line (default: false)
|
||||
*
|
||||
* @see <a href = https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=682-761&t=kDzSZDx0m0sk4iYz-4
|
||||
* >Figma</a>
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TangemSnackbar(data: SnackbarData, modifier: Modifier = Modifier, actionOnNewLine: Boolean = false) {
|
||||
Snackbar(
|
||||
modifier = modifier,
|
||||
action = {
|
||||
ActionButton(label = data.visuals.actionLabel, onClick = data::performAction)
|
||||
},
|
||||
actionOnNewLine = actionOnNewLine,
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
containerColor = TangemTheme.colors.icon.secondary,
|
||||
) {
|
||||
MessageText(text = data.visuals.message)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionButton(label: String?, onClick: () -> Unit) {
|
||||
if (!label.isNullOrBlank()) {
|
||||
TextButton(
|
||||
onClick = onClick,
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = TangemTheme.colors.text.primary2,
|
||||
),
|
||||
content = {
|
||||
Text(
|
||||
text = label,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.button,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MessageText(text: String) {
|
||||
Text(
|
||||
text = text,
|
||||
color = TangemTheme.colors.text.disabled,
|
||||
textAlign = TextAlign.Start,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT!
|
||||
* Preview doesn't work correctly, check on device or start [TangemSnackbarHost]'s preview in interactive mode *
|
||||
*/
|
||||
@Preview(widthDp = 344, showBackground = true, fontScale = 1f)
|
||||
@Preview(widthDp = 344, showBackground = true, fontScale = 1f, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview(widthDp = 344, showBackground = true, fontScale = 2f)
|
||||
@Composable
|
||||
private fun Preview_TangemSnackbar(@PreviewParameter(TangemSnackbarModelProvider::class) model: TangemSnackbarModel) {
|
||||
TangemThemePreview {
|
||||
TangemSnackbar(data = model.snackbarData, actionOnNewLine = model.actionOnNewLine)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.core.ui.components.snackbar
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* Tangem snackbar host.
|
||||
* Based on Material3 component. It's best way to show [TangemSnackbar].
|
||||
*
|
||||
* @param hostState snackbar host state
|
||||
* @param modifier modifier
|
||||
* @param actionOnNewLine flag that indicates if the action should be displayed on a new line (default: false)
|
||||
*
|
||||
* @see <a href = https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=682-761&t=kDzSZDx0m0sk4iYz-4
|
||||
* >Figma</a>
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TangemSnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier, actionOnNewLine: Boolean = false) {
|
||||
SnackbarHost(hostState = hostState, modifier = modifier) { data ->
|
||||
TangemSnackbar(data = data, actionOnNewLine = actionOnNewLine)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 344, showBackground = true, fontScale = 1f)
|
||||
@Preview(widthDp = 344, showBackground = true, fontScale = 1f, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview(widthDp = 344, showBackground = true, fontScale = 2f)
|
||||
@Composable
|
||||
private fun Preview_TangemSnackbar(@PreviewParameter(TangemSnackbarModelProvider::class) model: TangemSnackbarModel) {
|
||||
TangemThemePreview {
|
||||
val snackbarHostState = remember(::SnackbarHostState)
|
||||
|
||||
TangemSnackbarHost(hostState = snackbarHostState, actionOnNewLine = model.actionOnNewLine)
|
||||
|
||||
LaunchedEffect(key1 = null) {
|
||||
snackbarHostState.showSnackbar(visuals = model.snackbarData.visuals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.core.ui.components.snackbar
|
||||
|
||||
import androidx.compose.material3.SnackbarData
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarVisuals
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
|
||||
internal data class TangemSnackbarModel(val snackbarData: SnackbarData, val actionOnNewLine: Boolean)
|
||||
|
||||
internal class TangemSnackbarModelProvider : CollectionPreviewParameterProvider<TangemSnackbarModel>(
|
||||
listOf(
|
||||
createTangemSnackbarModel(
|
||||
message = "Single-line description.",
|
||||
actionLabel = "Button",
|
||||
actionOnNewLine = false,
|
||||
),
|
||||
createTangemSnackbarModel(
|
||||
message = "Very loooooooooooong single-line description.",
|
||||
actionLabel = "Button",
|
||||
actionOnNewLine = false,
|
||||
),
|
||||
createTangemSnackbarModel(
|
||||
message = "Single-line description.",
|
||||
actionLabel = "Very looooooong button name",
|
||||
actionOnNewLine = false,
|
||||
),
|
||||
createTangemSnackbarModel(
|
||||
message = "Single-line description.",
|
||||
actionLabel = "Button",
|
||||
actionOnNewLine = true,
|
||||
),
|
||||
createTangemSnackbarModel(
|
||||
message = "Very loooooooooooong single-line description.",
|
||||
actionLabel = "Button",
|
||||
actionOnNewLine = true,
|
||||
),
|
||||
createTangemSnackbarModel(
|
||||
message = "Single-line description.",
|
||||
actionLabel = "Very looooooong button name",
|
||||
actionOnNewLine = true,
|
||||
),
|
||||
),
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun createTangemSnackbarModel(
|
||||
message: String,
|
||||
actionLabel: String,
|
||||
actionOnNewLine: Boolean,
|
||||
): TangemSnackbarModel {
|
||||
return TangemSnackbarModel(
|
||||
snackbarData = createSnackbarData(message, actionLabel),
|
||||
actionOnNewLine = actionOnNewLine,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSnackbarData(message: String, actionLabel: String): SnackbarData {
|
||||
return object : SnackbarData {
|
||||
|
||||
override val visuals: SnackbarVisuals
|
||||
get() = object : SnackbarVisuals {
|
||||
override val message: String = message
|
||||
override val actionLabel: String = actionLabel
|
||||
override val duration: SnackbarDuration = SnackbarDuration.Short // Never-mind
|
||||
override val withDismissAction: Boolean = false // Never-mind
|
||||
}
|
||||
|
||||
override fun dismiss() = Unit
|
||||
override fun performAction() = Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.core.ui.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
import org.intellij.markdown.ast.ASTNode
|
||||
import org.intellij.markdown.ast.getTextInNode
|
||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
||||
import org.intellij.markdown.parser.MarkdownParser
|
||||
|
||||
/** Markdown parser */
|
||||
@Composable
|
||||
fun rememberMarkdownParser() = remember {
|
||||
MarkdownParser(CommonMarkFlavourDescriptor())
|
||||
}
|
||||
|
||||
/**
|
||||
* Styling markdown tree recursively
|
||||
*
|
||||
* @param markdownText original text
|
||||
* @param node current processed node
|
||||
*/
|
||||
@Composable
|
||||
fun AnnotatedString.Builder.appendMarkdown(markdownText: String, node: ASTNode): AnnotatedString.Builder {
|
||||
when (node.type) {
|
||||
MarkdownElementTypes.MARKDOWN_FILE, MarkdownElementTypes.PARAGRAPH -> {
|
||||
node.children.forEach { childNode ->
|
||||
appendMarkdown(
|
||||
markdownText = markdownText,
|
||||
node = childNode,
|
||||
)
|
||||
}
|
||||
}
|
||||
MarkdownElementTypes.STRONG -> {
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Medium)) {
|
||||
node.children
|
||||
.drop(2)
|
||||
.dropLast(2)
|
||||
.forEach { childNode ->
|
||||
appendMarkdown(
|
||||
markdownText = markdownText,
|
||||
node = childNode,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
append(node.getTextInNode(markdownText).toString())
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
|
@ -8,6 +8,9 @@ import androidx.compose.runtime.Immutable
|
|||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
|
||||
/**
|
||||
* Utility class for creating text as [String] or [StringRes].
|
||||
|
|
@ -160,6 +163,29 @@ fun TextReference.resolveReference(resources: Resources): String {
|
|||
}
|
||||
}
|
||||
|
||||
/** Resolve [TextReference] as [AnnotatedString] */
|
||||
@Composable
|
||||
fun TextReference.resolveAnnotatedReference(): AnnotatedString {
|
||||
return when (this) {
|
||||
is TextReference.Res -> {
|
||||
val args = formatArgs
|
||||
.map { if (it is TextReference) it.resolveReference() else it }
|
||||
.toTypedArray()
|
||||
|
||||
formatAnnotated(stringResource(id = id, *args))
|
||||
}
|
||||
is TextReference.PluralRes -> formatAnnotated(
|
||||
pluralStringResource(id, count, *formatArgs.toTypedArray()),
|
||||
)
|
||||
is TextReference.Str -> formatAnnotated(value)
|
||||
is TextReference.Combined -> buildAnnotatedString {
|
||||
refs.forEach {
|
||||
append(formatAnnotated(it.resolveReference()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Concatenate [this] reference with [ref] */
|
||||
operator fun TextReference.plus(ref: TextReference): TextReference {
|
||||
return when (this) {
|
||||
|
|
@ -169,4 +195,14 @@ operator fun TextReference.plus(ref: TextReference): TextReference {
|
|||
is TextReference.Str,
|
||||
-> TextReference.Combined(refs = wrappedList(this, ref))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun formatAnnotated(rawString: String): AnnotatedString {
|
||||
val markdownDescriptor = rememberMarkdownParser()
|
||||
val parsedTree = markdownDescriptor.parse(MarkdownElementTypes.MARKDOWN_FILE, rawString, true)
|
||||
|
||||
return buildAnnotatedString {
|
||||
appendMarkdown(markdownText = rawString, node = parsedTree)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,8 @@ import androidx.compose.runtime.ReadOnlyComposable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
|
||||
/**
|
||||
|
|
@ -23,14 +22,9 @@ import com.tangem.domain.apptheme.model.AppThemeMode
|
|||
internal interface ComposeScreen {
|
||||
|
||||
/**
|
||||
* The holder for managing the current application theme mode.
|
||||
* The holder for ui dependencies.
|
||||
*/
|
||||
val appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
/**
|
||||
* Haptic manager.
|
||||
*/
|
||||
val hapticManager: HapticManager
|
||||
val uiDependencies: UiDependencies
|
||||
|
||||
/**
|
||||
* The screen modifier.
|
||||
|
|
@ -59,11 +53,11 @@ internal interface ComposeScreen {
|
|||
internal fun ComposeScreen.createComposeView(context: Context): ComposeView {
|
||||
return ComposeView(context).apply {
|
||||
setContent {
|
||||
val appThemeMode by appThemeModeHolder.appThemeMode
|
||||
val appThemeMode by uiDependencies.appThemeModeHolder.appThemeMode
|
||||
|
||||
TangemTheme(
|
||||
isDark = shouldUseDarkTheme(appThemeMode),
|
||||
hapticManager = hapticManager,
|
||||
hapticManager = uiDependencies.hapticManager,
|
||||
) {
|
||||
ScreenContent(modifier = screenModifier)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.format.DateTimeFormat
|
||||
import org.joda.time.format.DateTimeFormatter
|
||||
|
|
@ -55,6 +56,16 @@ object DateTimeFormatters {
|
|||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
/**
|
||||
* In API version < 24, there may be some problems with getting the best date and time format pattern.
|
||||
*/
|
||||
val dateMMMMd: DateTimeFormatter by lazy {
|
||||
DateTimeFormatterBuilder()
|
||||
.appendPattern(DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMMM d"))
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
val dateTimeFormatter: DateTimeFormatter by lazy {
|
||||
DateTimeFormat.forPattern("dd.MM.yyyy HH:mm")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import java.text.DecimalFormatSymbols
|
|||
import java.util.Locale
|
||||
|
||||
private const val TEXT_CHUNK_THOUSAND = 3
|
||||
private const val POINT_SEPARATOR = '.'
|
||||
private const val COMMA_SEPARATOR = ','
|
||||
private const val SCIENTIFIC_NOTATION = 'e'
|
||||
const val POINT_SEPARATOR = '.'
|
||||
const val COMMA_SEPARATOR = ','
|
||||
const val DECIMAL_SEPARATOR_LIMIT = 1
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 441 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 409 KiB |
|
|
@ -23,7 +23,17 @@ internal class DefaultPromoRepository(
|
|||
}.getOrNull()
|
||||
}
|
||||
|
||||
override suspend fun getTravalaPromoBanner(): PromoBanner? {
|
||||
return runCatching(dispatchers.io) {
|
||||
promoResponseConverter.convert(
|
||||
tangemApi.getPromotionInfo(TRAVALA)
|
||||
.getOrThrow(),
|
||||
)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val CHANGELLY_NAME = "changelly"
|
||||
private const val TRAVALA = "travala"
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ class PromoResponseConverter : Converter<PromotionInfoResponse, PromoBanner?> {
|
|||
name = value.name,
|
||||
bannerState = PromoBanner.BannerState(
|
||||
status = bannerState.status,
|
||||
link = bannerState.link,
|
||||
timeline = PromoBanner.Timeline(
|
||||
start = DateTime.parse(bannerState.timeline.start),
|
||||
end = DateTime.parse(bannerState.timeline.end),
|
||||
|
|
|
|||
|
|
@ -3,36 +3,48 @@ package com.tangem.data.settings
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_TRAVALA_PROMO_SHOWN_KEY
|
||||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.domain.settings.repositories.SwapPromoRepository
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Repository for showing swap promo notification.
|
||||
*/
|
||||
class DefaultSwapPromoRepository(
|
||||
class DefaultPromoSettingsRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : SwapPromoRepository {
|
||||
override fun isReadyToShowWalletPromo(): Flow<Boolean> {
|
||||
) : PromoSettingsRepository {
|
||||
override fun isReadyToShowWalletSwapPromo(): Flow<Boolean> {
|
||||
return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY, true)
|
||||
}
|
||||
|
||||
override fun isReadyToShowTokenPromo(): Flow<Boolean> {
|
||||
override fun isReadyToShowTokenSwapPromo(): Flow<Boolean> {
|
||||
return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY, true)
|
||||
}
|
||||
|
||||
override suspend fun setNeverToShowWalletPromo() {
|
||||
override suspend fun setNeverToShowWalletSwapPromo() {
|
||||
appPreferencesStore.store(
|
||||
key = IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY,
|
||||
value = false,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun setNeverToShowTokenPromo() {
|
||||
override suspend fun setNeverToShowTokenSwapPromo() {
|
||||
appPreferencesStore.store(
|
||||
key = IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY,
|
||||
value = false,
|
||||
)
|
||||
}
|
||||
|
||||
override fun isReadyToShowWalletTravalaPromo(): Flow<Boolean> {
|
||||
return appPreferencesStore.get(IS_WALLET_TRAVALA_PROMO_SHOWN_KEY, true)
|
||||
}
|
||||
|
||||
override suspend fun setNeverToShowWalletTravalaPromo() {
|
||||
appPreferencesStore.store(
|
||||
key = IS_WALLET_TRAVALA_PROMO_SHOWN_KEY,
|
||||
value = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@ package com.tangem.data.settings.di
|
|||
|
||||
import com.tangem.data.settings.DefaultAppRatingRepository
|
||||
import com.tangem.data.settings.DefaultSettingsRepository
|
||||
import com.tangem.data.settings.DefaultSwapPromoRepository
|
||||
import com.tangem.data.settings.DefaultPromoSettingsRepository
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.settings.repositories.AppRatingRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.repositories.SwapPromoRepository
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -31,7 +31,7 @@ internal object SettingsDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSwapPromoRepository(appPreferencesStore: AppPreferencesStore): SwapPromoRepository {
|
||||
return DefaultSwapPromoRepository(appPreferencesStore = appPreferencesStore)
|
||||
fun providePromoSettingsSettingsRepository(appPreferencesStore: AppPreferencesStore): PromoSettingsRepository {
|
||||
return DefaultPromoSettingsRepository(appPreferencesStore = appPreferencesStore)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ dependencies {
|
|||
implementation(deps.tangem.blockchain)
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Domain */
|
||||
|
|
@ -28,4 +29,6 @@ dependencies {
|
|||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
implementation(deps.timber)
|
||||
}
|
||||
|
|
@ -4,23 +4,26 @@ import androidx.core.text.isDigitsOnly
|
|||
import com.tangem.blockchain.blockchains.algorand.AlgorandTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder
|
||||
import com.tangem.blockchain.blockchains.hedera.HederaTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarMemo
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ton.TonTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultTransactionRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val walletManagersStore: WalletManagersStore,
|
||||
private val coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : TransactionRepository {
|
||||
|
||||
|
|
@ -41,17 +44,54 @@ internal class DefaultTransactionRepository(
|
|||
derivationPath = network.derivationPath.value,
|
||||
)
|
||||
|
||||
val txAmount = if (isSwap) {
|
||||
createAmountForSwap(amount)
|
||||
} else {
|
||||
amount
|
||||
}
|
||||
return@withContext walletManager?.createTransaction(txAmount, fee, destination)?.copy(
|
||||
return@withContext walletManager?.createTransactionInternal(
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
destination = destination,
|
||||
network = network,
|
||||
isSwap = isSwap,
|
||||
hash = hash,
|
||||
extras = getMemoExtras(network.id.value, memo),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun validateTransaction(
|
||||
amount: Amount,
|
||||
fee: Fee?,
|
||||
memo: String?,
|
||||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean,
|
||||
hash: String?,
|
||||
): Result<Unit> {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = walletManagersStore.getSyncOrNull(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
)
|
||||
|
||||
val validator = walletManager as? TransactionValidator
|
||||
|
||||
return if (validator != null) {
|
||||
val transaction = walletManager.createTransactionInternal(
|
||||
amount = amount,
|
||||
fee = fee ?: Fee.Common(amount = amount),
|
||||
memo = memo,
|
||||
destination = destination,
|
||||
network = network,
|
||||
isSwap = isSwap,
|
||||
hash = hash,
|
||||
)
|
||||
|
||||
validator.validate(transaction = transaction)
|
||||
} else {
|
||||
Timber.e("${walletManager?.wallet?.blockchain} does not support transaction validation")
|
||||
Result.success(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendTransaction(
|
||||
txData: TransactionData,
|
||||
signer: CommonSigner,
|
||||
|
|
@ -67,6 +107,28 @@ internal class DefaultTransactionRepository(
|
|||
(walletManager as TransactionSender).send(txData, signer)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun WalletManager.createTransactionInternal(
|
||||
amount: Amount,
|
||||
fee: Fee,
|
||||
memo: String?,
|
||||
destination: String,
|
||||
network: Network,
|
||||
isSwap: Boolean,
|
||||
hash: String?,
|
||||
): TransactionData {
|
||||
val txAmount = if (isSwap) {
|
||||
createAmountForSwap(amount)
|
||||
} else {
|
||||
amount
|
||||
}
|
||||
|
||||
return createTransaction(txAmount, fee, destination).copy(
|
||||
hash = hash,
|
||||
extras = getMemoExtras(network.id.value, memo),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getMemoExtras(networkId: String, memo: String?): TransactionExtras? {
|
||||
val blockchain = Blockchain.fromId(networkId)
|
||||
if (memo == null) return null
|
||||
|
|
@ -85,7 +147,7 @@ internal class DefaultTransactionRepository(
|
|||
Blockchain.TerraV2,
|
||||
-> CosmosTransactionExtras(memo)
|
||||
Blockchain.TON -> TonTransactionExtras(memo)
|
||||
Blockchain.Hedera -> HederaTransactionBuilder.HederaTransactionExtras(memo)
|
||||
Blockchain.Hedera -> HederaTransactionExtras(memo)
|
||||
Blockchain.Algorand -> AlgorandTransactionExtras(memo)
|
||||
else -> null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.transaction.di
|
|||
|
||||
import com.tangem.data.transaction.DefaultFeeRepository
|
||||
import com.tangem.data.transaction.DefaultTransactionRepository
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -20,10 +21,12 @@ internal object TransactionDataModule {
|
|||
@Singleton
|
||||
fun providesTransactionRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
walletManagersStore: WalletManagersStore,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
): TransactionRepository {
|
||||
return DefaultTransactionRepository(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
walletManagersStore = walletManagersStore,
|
||||
coroutineDispatcherProvider = coroutineDispatcherProvider,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.card
|
||||
|
||||
import arrow.core.Either
|
||||
|
||||
interface DeleteSavedAccessCodesUseCase {
|
||||
|
||||
suspend operator fun invoke(cardId: String): Either<Throwable, Unit>
|
||||
}
|
||||
|
|
@ -30,6 +30,12 @@ interface CardTypesResolver {
|
|||
|
||||
fun isSatoshiFriendsWallet(): Boolean
|
||||
|
||||
fun isBitcoinPizzaDayWallet(): Boolean
|
||||
|
||||
fun isVeChainWallet(): Boolean
|
||||
|
||||
fun isNewWorldEliteWallet(): Boolean
|
||||
|
||||
fun isWhiteWallet(): Boolean
|
||||
|
||||
fun isWallet2(): Boolean
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ internal class TangemCardTypesResolver(
|
|||
|
||||
override fun isSatoshiFriendsWallet(): Boolean = card.batchId == SATOSHI_WALLET_BATCH_ID
|
||||
|
||||
override fun isBitcoinPizzaDayWallet(): Boolean = card.batchId == BITCOIN_PIZZA_DAY_WALLET_BATCH_ID
|
||||
|
||||
override fun isVeChainWallet(): Boolean = card.batchId == VECHAIN_WALLET_BATCH_ID
|
||||
|
||||
override fun isNewWorldEliteWallet(): Boolean = card.batchId == NEW_WORLD_ELITE_WALLET_BATCH_ID
|
||||
|
||||
override fun isWhiteWallet(): Boolean {
|
||||
return walletData == null && card.firmwareVersion <= FirmwareVersion.HDWalletAvailable
|
||||
}
|
||||
|
|
@ -154,5 +160,8 @@ internal class TangemCardTypesResolver(
|
|||
const val WHITE_WALLET2_BATCH_ID = "AF15"
|
||||
const val TRILLIANT_WALLET_BATCH_ID = "AF16"
|
||||
const val AVRORA_WALLET_BATCH_ID = "AF18"
|
||||
const val BITCOIN_PIZZA_DAY_WALLET_BATCH_ID = "AF33"
|
||||
const val VECHAIN_WALLET_BATCH_ID = "AF29"
|
||||
const val NEW_WORLD_ELITE_WALLET_BATCH_ID = "AF26"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import com.tangem.domain.settings.repositories.SwapPromoRepository
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ShouldShowSwapPromoTokenUseCase(private val swapPromoRepository: SwapPromoRepository) {
|
||||
class ShouldShowSwapPromoTokenUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
|
||||
|
||||
operator fun invoke(): Flow<Boolean> = swapPromoRepository.isReadyToShowTokenPromo()
|
||||
operator fun invoke(): Flow<Boolean> = promoSettingsRepository.isReadyToShowTokenSwapPromo()
|
||||
|
||||
suspend fun neverToShow() = swapPromoRepository.setNeverToShowTokenPromo()
|
||||
suspend fun neverToShow() = promoSettingsRepository.setNeverToShowTokenSwapPromo()
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import com.tangem.domain.settings.repositories.SwapPromoRepository
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ShouldShowSwapPromoWalletUseCase(private val swapPromoRepository: SwapPromoRepository) {
|
||||
class ShouldShowSwapPromoWalletUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
|
||||
|
||||
operator fun invoke(): Flow<Boolean> = swapPromoRepository.isReadyToShowWalletPromo()
|
||||
operator fun invoke(): Flow<Boolean> = promoSettingsRepository.isReadyToShowWalletSwapPromo()
|
||||
|
||||
suspend fun neverToShow() = swapPromoRepository.setNeverToShowWalletPromo()
|
||||
suspend fun neverToShow() = promoSettingsRepository.setNeverToShowWalletSwapPromo()
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ShouldShowTravalaPromoWalletUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
|
||||
|
||||
operator fun invoke(): Flow<Boolean> = promoSettingsRepository.isReadyToShowWalletTravalaPromo()
|
||||
|
||||
suspend fun neverToShow() = promoSettingsRepository.setNeverToShowWalletTravalaPromo()
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.settings.repositories
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface PromoSettingsRepository {
|
||||
fun isReadyToShowWalletSwapPromo(): Flow<Boolean>
|
||||
|
||||
fun isReadyToShowTokenSwapPromo(): Flow<Boolean>
|
||||
|
||||
suspend fun setNeverToShowWalletSwapPromo()
|
||||
|
||||
suspend fun setNeverToShowTokenSwapPromo()
|
||||
|
||||
fun isReadyToShowWalletTravalaPromo(): Flow<Boolean>
|
||||
|
||||
suspend fun setNeverToShowWalletTravalaPromo()
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.domain.settings.repositories
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SwapPromoRepository {
|
||||
fun isReadyToShowWalletPromo(): Flow<Boolean>
|
||||
|
||||
fun isReadyToShowTokenPromo(): Flow<Boolean>
|
||||
|
||||
suspend fun setNeverToShowWalletPromo()
|
||||
|
||||
suspend fun setNeverToShowTokenPromo()
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ dependencies {
|
|||
|
||||
/** Project - Other */
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
/** Android - Other */
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ data class PromoBanner(
|
|||
data class BannerState(
|
||||
val timeline: Timeline,
|
||||
val status: String,
|
||||
val link: String?,
|
||||
)
|
||||
|
||||
data class Timeline(
|
||||
|
|
|
|||
|
|
@ -45,4 +45,6 @@ sealed class CryptoCurrencyWarning {
|
|||
val startDateTime: DateTime,
|
||||
val endDateTime: DateTime,
|
||||
) : CryptoCurrencyWarning()
|
||||
|
||||
data object BeaconChainShutdown : CryptoCurrencyWarning()
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import com.tangem.utils.isNullOrZero
|
||||
|
|
@ -74,6 +75,7 @@ class GetCurrencyWarningsUseCase(
|
|||
*coinRelatedWarnings.toTypedArray(),
|
||||
getNetworkUnavailableWarning(currencyStatus),
|
||||
getNetworkNoAccountWarning(currencyStatus),
|
||||
getBeaconChainShutdownWarning(currency.network.id),
|
||||
)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
|
@ -261,6 +263,10 @@ class GetCurrencyWarningsUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getBeaconChainShutdownWarning(networkId: Network.ID): CryptoCurrencyWarning.BeaconChainShutdown? {
|
||||
return if (BlockchainUtils.isBeaconChain(networkId.value)) CryptoCurrencyWarning.BeaconChainShutdown else null
|
||||
}
|
||||
|
||||
private fun BigDecimal?.isZero(): Boolean {
|
||||
return this?.signum() == 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetNetworkAddressesUseCase(
|
||||
internal val networksRepository: NetworksRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId, network: Network): Flow<String> =
|
||||
networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network))
|
||||
.map { networkStatuses ->
|
||||
when (val networkStatus = networkStatuses.singleOrNull { it.network.id == network.id }?.value) {
|
||||
is NetworkStatus.NoAccount -> networkStatus.address.defaultAddress.value
|
||||
is NetworkStatus.Unreachable -> networkStatus.address?.defaultAddress?.value.orEmpty()
|
||||
is NetworkStatus.Verified -> networkStatus.address.defaultAddress.value
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,4 +5,6 @@ import com.tangem.domain.promo.PromoBanner
|
|||
interface PromoRepository {
|
||||
|
||||
suspend fun getChangellyPromoBanner(): PromoBanner?
|
||||
|
||||
suspend fun getTravalaPromoBanner(): PromoBanner?
|
||||
}
|
||||
|
|
@ -22,6 +22,18 @@ interface TransactionRepository {
|
|||
hash: String?,
|
||||
): TransactionData?
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun validateTransaction(
|
||||
amount: Amount,
|
||||
fee: Fee?,
|
||||
memo: String?,
|
||||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean = false,
|
||||
hash: String? = null,
|
||||
): Result<Unit>
|
||||
|
||||
suspend fun sendTransaction(
|
||||
txData: TransactionData,
|
||||
signer: CommonSigner,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class ValidateTransactionUseCase(
|
||||
private val transactionRepository: TransactionRepository,
|
||||
) {
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
suspend operator fun invoke(
|
||||
amount: Amount,
|
||||
fee: Fee,
|
||||
memo: String?,
|
||||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean = false,
|
||||
hash: String? = null,
|
||||
): Either<Throwable, Unit> {
|
||||
return transactionRepository.validateTransaction(
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
destination = destination,
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
isSwap = isSwap,
|
||||
hash = hash,
|
||||
)
|
||||
.fold(onSuccess = { Unit.right() }, onFailure = { it.left() })
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.domain.wallets.usecase
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
/**
|
||||
* Use case for getting list of user wallets
|
||||
|
|
@ -15,4 +16,7 @@ class GetWalletsUseCase(private val userWalletsListManager: UserWalletsListManag
|
|||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
operator fun invoke(): Flow<List<UserWallet>> = userWalletsListManager.userWallets
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
suspend fun invokeSync(): List<UserWallet>? = userWalletsListManager.userWallets.firstOrNull()
|
||||
}
|
||||
|
|
@ -20,10 +20,9 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer
|
||||
import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter
|
||||
import com.tangem.feature.qrscanning.presentation.QrScanningContent
|
||||
|
|
@ -40,10 +39,7 @@ import kotlin.properties.Delegates
|
|||
internal class QrScanningFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var router: QrScanningRouter
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ import androidx.compose.foundation.layout.systemBarsPadding
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.feature.referral.router.ReferralRouter
|
||||
import com.tangem.feature.referral.ui.ReferralScreen
|
||||
import com.tangem.feature.referral.viewmodels.ReferralViewModel
|
||||
|
|
@ -21,10 +20,7 @@ import javax.inject.Inject
|
|||
class ReferralFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel by viewModels<ReferralViewModel>()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,4 +7,7 @@ interface SendFeatureToggles {
|
|||
|
||||
/** Availability of redesigned send screen */
|
||||
val isRedesignedSendEnabled: Boolean
|
||||
|
||||
/** Updates remote toggle */
|
||||
suspend fun fetchNewSendEnabled()
|
||||
}
|
||||
|
|
@ -48,6 +48,7 @@ dependencies {
|
|||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.datasource)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.features.send.impl.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
import com.tangem.features.send.impl.featuretoggles.DefaultSendFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -18,7 +20,15 @@ internal object SendFeatureTogglesModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendFeatureToggles(featureTogglesManager: FeatureTogglesManager): SendFeatureToggles {
|
||||
return DefaultSendFeatureToggles(featureTogglesManager = featureTogglesManager)
|
||||
fun provideSendFeatureToggles(
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
tangemTechApi: TangemTechApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): SendFeatureToggles {
|
||||
return DefaultSendFeatureToggles(
|
||||
featureTogglesManager = featureTogglesManager,
|
||||
tangemTechApi = tangemTechApi,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,41 @@
|
|||
package com.tangem.features.send.impl.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Default implementation of Send feature toggles
|
||||
*
|
||||
* @property featureTogglesManager manager for getting information about the availability of feature toggles
|
||||
* @property tangemTechApi api to get remote feature toggle for send
|
||||
* @property dispatchers coroutine dispatchers
|
||||
*/
|
||||
internal class DefaultSendFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SendFeatureToggles {
|
||||
|
||||
private val remoteSendEnabled: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||
|
||||
override val isRedesignedSendEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_SEND_SCREEN_ENABLED")
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_SEND_SCREEN_ENABLED") &&
|
||||
remoteSendEnabled.value
|
||||
|
||||
override suspend fun fetchNewSendEnabled() {
|
||||
runCatching(dispatchers.io) {
|
||||
tangemTechApi.getFeatures().getOrThrow()
|
||||
}.onSuccess { response ->
|
||||
remoteSendEnabled.update { response.isNewSendEnabled }
|
||||
}.onFailure {
|
||||
Timber.e(it.localizedMessage, "Unable to fetch new send toggle")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,10 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.send.impl.navigation.InnerSendRouter
|
||||
import com.tangem.features.send.impl.presentation.state.StateRouter
|
||||
|
|
@ -27,10 +26,7 @@ import javax.inject.Inject
|
|||
internal class SendFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
override lateinit var hapticManager: HapticManager
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var router: SendRouter
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.features.send.impl.presentation.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE
|
||||
|
|
@ -66,9 +68,9 @@ internal sealed class SendAnalyticEvents(
|
|||
data object FeeScreenOpened : SendAnalyticEvents(event = "Fee Screen Opened")
|
||||
|
||||
/** Selected fee (send after next screen opened) */
|
||||
data class SelectedFee(val feeType: SelectedFeeType) : SendAnalyticEvents(
|
||||
data class SelectedFee(val feeType: AnalyticsParam.FeeType) : SendAnalyticEvents(
|
||||
event = "Fee Selected",
|
||||
params = mapOf("Fee Type" to feeType.name),
|
||||
params = mapOf("Fee Type" to feeType.value),
|
||||
)
|
||||
|
||||
/** Custom fee selected */
|
||||
|
|
@ -97,7 +99,16 @@ internal sealed class SendAnalyticEvents(
|
|||
|
||||
// region Transaction Result
|
||||
/** Transaction send screen opened */
|
||||
data object TransactionScreenOpened : SendAnalyticEvents(event = "Transaction Sent Screen Opened")
|
||||
data class TransactionScreenOpened(
|
||||
val token: String,
|
||||
val feeType: AnalyticsParam.FeeType,
|
||||
) : SendAnalyticEvents(
|
||||
event = "Transaction Sent Screen Opened",
|
||||
params = mapOf(
|
||||
TOKEN to token,
|
||||
FEE_TYPE to feeType.value,
|
||||
),
|
||||
)
|
||||
|
||||
/** Share button clicked */
|
||||
data object ShareButtonClicked : SendAnalyticEvents(event = "Button - Share")
|
||||
|
|
@ -145,12 +156,4 @@ internal enum class EnterAddressSource {
|
|||
internal enum class SelectedCurrencyType(val value: String) {
|
||||
Token("Token"),
|
||||
AppCurrency("App Currency"),
|
||||
}
|
||||
|
||||
internal enum class SelectedFeeType {
|
||||
Min,
|
||||
Max,
|
||||
Fixed,
|
||||
Normal,
|
||||
Custom,
|
||||
}
|
||||
|
|
@ -2,8 +2,10 @@ package com.tangem.features.send.impl.presentation.analytics.utils
|
|||
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.send.impl.presentation.analytics.SelectedCurrencyType
|
||||
import com.tangem.features.send.impl.presentation.analytics.SelectedFeeType
|
||||
import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents
|
||||
import com.tangem.features.send.impl.presentation.analytics.SendScreenSource
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
|
|
@ -11,11 +13,13 @@ import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
|||
import com.tangem.features.send.impl.presentation.state.StateRouter
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
import com.tangem.utils.Provider
|
||||
|
||||
internal class SendScreenAnalyticSender(
|
||||
private val stateRouterProvider: Provider<StateRouter>,
|
||||
private val currentStateProvider: Provider<SendUiState>,
|
||||
private val cryptoCurrencyProvider: Provider<CryptoCurrency>,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) {
|
||||
fun send(prevScreen: SendUiStateType, state: SendUiState) {
|
||||
|
|
@ -73,16 +77,57 @@ internal class SendScreenAnalyticSender(
|
|||
)
|
||||
}
|
||||
|
||||
fun sendTransaction() {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val cryptoCurrency = cryptoCurrencyProvider()
|
||||
val feeState = state.getFeeState(isEditState) ?: return
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return
|
||||
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
|
||||
val feeType = getSendTransactionFeeType(feeSelectorState)
|
||||
analyticsEventHandler.send(
|
||||
SendAnalyticEvents.TransactionScreenOpened(
|
||||
token = cryptoCurrency.symbol,
|
||||
feeType = feeType,
|
||||
),
|
||||
)
|
||||
analyticsEventHandler.send(
|
||||
Basic.TransactionSent(
|
||||
sentFrom = AnalyticsParam.TxSentFrom.Send(
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
token = cryptoCurrency.symbol,
|
||||
feeType = feeType,
|
||||
),
|
||||
memoType = getSendTransactionMemoType(recipientState.memoTextField),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendSelectedFeeAnalytics(feeSelectorState: FeeSelectorState.Content) {
|
||||
val type = when (feeSelectorState.fees) {
|
||||
is TransactionFee.Single -> SelectedFeeType.Fixed
|
||||
is TransactionFee.Choosable -> when (feeSelectorState.selectedFee) {
|
||||
FeeType.Slow -> SelectedFeeType.Min
|
||||
FeeType.Market -> SelectedFeeType.Normal
|
||||
FeeType.Fast -> SelectedFeeType.Max
|
||||
FeeType.Custom -> SelectedFeeType.Custom
|
||||
}
|
||||
}
|
||||
val type = getSendTransactionFeeType(feeSelectorState)
|
||||
analyticsEventHandler.send(SendAnalyticEvents.SelectedFee(type))
|
||||
}
|
||||
|
||||
private fun getSendTransactionFeeType(feeSelectorState: FeeSelectorState.Content): AnalyticsParam.FeeType =
|
||||
when (feeSelectorState.fees) {
|
||||
is TransactionFee.Single -> AnalyticsParam.FeeType.Fixed
|
||||
is TransactionFee.Choosable -> when (feeSelectorState.selectedFee) {
|
||||
FeeType.Slow -> AnalyticsParam.FeeType.Min
|
||||
FeeType.Market -> AnalyticsParam.FeeType.Normal
|
||||
FeeType.Fast -> AnalyticsParam.FeeType.Max
|
||||
FeeType.Custom -> AnalyticsParam.FeeType.Custom
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSendTransactionMemoType(
|
||||
recipientMemo: SendTextField.RecipientMemo?,
|
||||
): Basic.TransactionSent.MemoType {
|
||||
val memo = recipientMemo?.value
|
||||
return when {
|
||||
memo?.isBlank() == true -> Basic.TransactionSent.MemoType.Empty
|
||||
memo?.isNotBlank() == true -> Basic.TransactionSent.MemoType.Full
|
||||
else -> Basic.TransactionSent.MemoType.Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
package com.tangem.features.send.impl.presentation.domain
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Available wallet to send
|
||||
*
|
||||
* @property name wallet name
|
||||
* @property userWalletId wallet id
|
||||
* @property address blockchain address
|
||||
*/
|
||||
@Immutable
|
||||
data class AvailableWallet(
|
||||
val name: String,
|
||||
val userWalletId: UserWalletId,
|
||||
val address: String,
|
||||
)
|
||||
|
|
@ -54,7 +54,7 @@ internal sealed class SendNotification(val config: NotificationConfig) {
|
|||
wrappedList(cryptoCurrency, utxoLimit, amountLimit),
|
||||
),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_reduce_to, wrappedList(amountLimit)),
|
||||
text = resourceReference(R.string.send_notification_leave_button, wrappedList(amountLimit)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
|
@ -98,7 +98,7 @@ internal sealed class SendNotification(val config: NotificationConfig) {
|
|||
title = resourceReference(R.string.send_notification_existential_deposit_title),
|
||||
subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_existential_deposit_button, wrappedList(deposit)),
|
||||
text = resourceReference(R.string.send_notification_leave_button, wrappedList(deposit)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
|
@ -119,12 +119,13 @@ internal sealed class SendNotification(val config: NotificationConfig) {
|
|||
),
|
||||
) {
|
||||
data class HighFeeError(
|
||||
val currencyName: String,
|
||||
val amount: String,
|
||||
val onConfirmClick: () -> Unit,
|
||||
val onCloseClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = resourceReference(R.string.send_notification_high_fee_title),
|
||||
subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(amount)),
|
||||
subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(currencyName, amount)),
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_reduce_by, wrappedList(amount)),
|
||||
onClick = onConfirmClick,
|
||||
|
|
@ -156,9 +157,33 @@ internal sealed class SendNotification(val config: NotificationConfig) {
|
|||
data class FeeCoverageNotification(val cryptoAmount: String, val fiatAmount: String) : Warning(
|
||||
title = resourceReference(R.string.send_network_fee_warning_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_network_fee_warning_content,
|
||||
R.string.common_network_fee_warning_content,
|
||||
wrappedList(cryptoAmount, fiatAmount),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface Cardano {
|
||||
|
||||
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Warning(
|
||||
title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_coin_will_be_send_with_token_description,
|
||||
formatArgs = wrappedList(minAdaValue, tokenName),
|
||||
),
|
||||
)
|
||||
|
||||
data object InsufficientBalanceToTransferCoin : Error(
|
||||
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
|
||||
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
|
||||
)
|
||||
|
||||
data class InsufficientBalanceToTransferToken(val tokenName: String) : Error(
|
||||
title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_insufficient_balance_to_send_token_description,
|
||||
formatArgs = wrappedList(tokenName),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.send.impl.presentation.state.confirm
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
|
|
@ -8,6 +9,7 @@ import com.tangem.blockchainsdk.utils.minimalAmount
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
|
||||
|
|
@ -15,6 +17,8 @@ import com.tangem.domain.tokens.model.CryptoCurrency
|
|||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.utils.convertToAmount
|
||||
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents
|
||||
|
|
@ -23,6 +27,7 @@ import com.tangem.features.send.impl.presentation.state.fee.*
|
|||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
import com.tangem.features.send.impl.presentation.utils.getFiatString
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTezos
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -46,6 +51,7 @@ internal class SendNotificationFactory(
|
|||
private val clickIntents: SendClickIntents,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
|
||||
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
) {
|
||||
|
||||
fun create(): Flow<ImmutableList<SendNotification>> = stateRouterProvider().currentState
|
||||
|
|
@ -66,19 +72,21 @@ internal class SendNotificationFactory(
|
|||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
)
|
||||
val sendingAmount = calculateSubtractedAmount(
|
||||
isFeeCoverage = isFeeCoverage,
|
||||
val sendingAmount = checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isFeeCoverage,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatusProvider(),
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = sendState.reduceAmountBy,
|
||||
)
|
||||
buildList {
|
||||
// errors
|
||||
addFeeUnreachableNotification(feeState.feeSelectorState)
|
||||
addExceedBalanceNotification(feeValue, sendingAmount)
|
||||
addExceedsBalanceNotification(feeState.fee)
|
||||
addDustWarningNotification(feeValue, sendingAmount)
|
||||
addDustWarningNotificationForSpecificBlockchains(feeValue, sendingAmount)
|
||||
addTransactionLimitErrorNotification(feeValue, sendingAmount)
|
||||
|
||||
// warnings
|
||||
addExistentialWarningNotification(feeValue, amountValue)
|
||||
addFeeCoverageNotification(
|
||||
|
|
@ -89,6 +97,9 @@ internal class SendNotificationFactory(
|
|||
addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce)
|
||||
addTooHighNotification(feeState.feeSelectorState)
|
||||
addTooLowNotification(feeState)
|
||||
|
||||
// blockchain specific
|
||||
addCardanoNotifications(sendingAmount, feeState.fee, state)
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +205,7 @@ internal class SendNotificationFactory(
|
|||
val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) {
|
||||
feeAmount
|
||||
} else {
|
||||
feeAmount + receivedAmount
|
||||
receivedAmount
|
||||
}
|
||||
val currencyDeposit = currencyChecksRepository.getExistentialDeposit(
|
||||
userWalletId,
|
||||
|
|
@ -260,6 +271,7 @@ internal class SendNotificationFactory(
|
|||
if (!ignoreAmountReduce && isTotalBalance && isTezos) {
|
||||
add(
|
||||
SendNotification.Warning.HighFeeError(
|
||||
currencyName = cryptoCurrencyStatus.currency.name,
|
||||
amount = threshold.toPlainString(),
|
||||
onConfirmClick = {
|
||||
clickIntents.onAmountReduceClick(
|
||||
|
|
@ -275,23 +287,35 @@ internal class SendNotificationFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendNotification>.addDustWarningNotification(
|
||||
feeAmount: BigDecimal,
|
||||
receivedAmount: BigDecimal,
|
||||
private suspend fun MutableList<SendNotification>.addDustWarningNotificationForSpecificBlockchains(
|
||||
feeValue: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val dustValue = currencyChecksRepository.getDustValue(
|
||||
userWalletProvider().walletId,
|
||||
cryptoCurrencyStatus.currency.network,
|
||||
) ?: return
|
||||
val isCardano = BlockchainUtils.isCardano(cryptoCurrencyStatusProvider().currency.network.id.value)
|
||||
|
||||
if (checkDustLimits(feeAmount, receivedAmount, dustValue)) {
|
||||
add(
|
||||
SendNotification.Error.MinimumAmountError(dustValue.toPlainString()),
|
||||
)
|
||||
if (!isCardano) {
|
||||
addDustWarningNotification(feeValue, sendingAmount)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
|
||||
val change = when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
balance - (feeAmount + receivedAmount)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
|
||||
balance - feeAmount
|
||||
}
|
||||
}
|
||||
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||
}
|
||||
|
||||
private fun MutableList<SendNotification>.addTooLowNotification(feeState: SendStates.FeeState) {
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
|
||||
val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return
|
||||
|
|
@ -393,13 +417,90 @@ internal class SendNotificationFactory(
|
|||
return Blockchain.fromNetworkId(this.currency.network.backendId) == Blockchain.Arbitrum
|
||||
}
|
||||
|
||||
private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
private suspend fun MutableList<SendNotification>.addCardanoNotifications(
|
||||
sendingAmount: BigDecimal,
|
||||
fee: Fee?,
|
||||
state: SendUiState,
|
||||
) {
|
||||
val sendingCurrency = cryptoCurrencyStatusProvider().currency
|
||||
if (!BlockchainUtils.isCardano(sendingCurrency.network.id.value)) return
|
||||
|
||||
val totalAmount = feeAmount + receivedAmount
|
||||
val change = balance - totalAmount
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||
validateTransactionUseCase(
|
||||
amount = sendingAmount.convertToAmount(sendingCurrency),
|
||||
fee = fee ?: return,
|
||||
memo = state.recipientState?.memoTextField?.value,
|
||||
destination = requireNotNull(state.recipientState?.addressTextField?.value),
|
||||
userWalletId = userWalletProvider().walletId,
|
||||
network = sendingCurrency.network,
|
||||
).fold(
|
||||
ifLeft = {
|
||||
addCardanoTransactionValidationError(
|
||||
error = it as? BlockchainSdkError.Cardano ?: return@fold,
|
||||
sendingCurrency = sendingCurrency,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
(fee as? Fee.CardanoToken)?.let {
|
||||
add(
|
||||
SendNotification.Cardano.MinAdaValueCharged(
|
||||
tokenName = sendingCurrency.name,
|
||||
minAdaValue = it.minAdaValue.parseBigDecimal(sendingCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendNotification>.addCardanoTransactionValidationError(
|
||||
error: BlockchainSdkError.Cardano,
|
||||
sendingCurrency: CryptoCurrency,
|
||||
) {
|
||||
when (error) {
|
||||
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
|
||||
add(SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
||||
when (sendingCurrency) {
|
||||
is CryptoCurrency.Coin -> SendNotification.Cardano.InsufficientBalanceToTransferCoin
|
||||
is CryptoCurrency.Token -> {
|
||||
SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
|
||||
}
|
||||
}.let(::add)
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
||||
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
||||
-> {
|
||||
val dustValue = currencyChecksRepository.getDustValue(
|
||||
userWalletId = userWalletProvider().walletId,
|
||||
network = sendingCurrency.network,
|
||||
) ?: return
|
||||
|
||||
add(
|
||||
SendNotification.Error.MinimumAmountError(
|
||||
amount = dustValue.parseBigDecimal(sendingCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendNotification>.addDustWarningNotification(
|
||||
feeValue: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val dustValue = currencyChecksRepository.getDustValue(
|
||||
userWalletProvider().walletId,
|
||||
cryptoCurrencyStatus.currency.network,
|
||||
) ?: return
|
||||
|
||||
if (checkDustLimits(feeValue, sendingAmount, dustValue)) {
|
||||
add(
|
||||
SendNotification.Error.MinimumAmountError(
|
||||
amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ internal fun checkFeeCoverage(
|
|||
/**
|
||||
* Calculates subtracted amount
|
||||
*/
|
||||
internal fun calculateSubtractedAmount(
|
||||
private fun calculateSubtractedAmount(
|
||||
isFeeCoverage: Boolean,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
amountValue: BigDecimal,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ internal class BitcoinCustomFeeConverter(
|
|||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
title = resourceReference(R.string.send_max_fee),
|
||||
footer = resourceReference(R.string.send_max_fee_footer),
|
||||
footer = resourceReference(R.string.send_bitcoin_custom_fee_footer),
|
||||
label = getFiatReference(
|
||||
rate = feeCurrency?.fiatRate,
|
||||
value = feeValue,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ internal class EthereumCustomFeeConverter(
|
|||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
title = resourceReference(R.string.send_max_fee),
|
||||
footer = resourceReference(R.string.send_max_fee_footer),
|
||||
footer = resourceReference(R.string.send_evm_custom_fee_footer),
|
||||
label = getFiatReference(
|
||||
rate = feeCurrency?.fiatRate,
|
||||
value = feeValue,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.features.send.impl.presentation.state.StateRouter
|
|||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import java.math.RoundingMode
|
||||
|
||||
internal class SendAmountFieldMaxAmountConverter(
|
||||
private val stateRouterProvider: Provider<StateRouter>,
|
||||
|
|
@ -33,7 +34,7 @@ internal class SendAmountFieldMaxAmountConverter(
|
|||
|
||||
val isDoneActionEnabled = !decimalCryptoValue.isNullOrZero()
|
||||
val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty()
|
||||
val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals).orEmpty()
|
||||
val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals, roundingMode = RoundingMode.HALF_UP).orEmpty()
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
amountState = amountState.copy(
|
||||
|
|
|
|||
|
|
@ -26,12 +26,15 @@ import com.tangem.core.ui.components.buttons.common.TangemButton
|
|||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
||||
import com.tangem.features.send.impl.presentation.utils.getFiatFormatted
|
||||
|
||||
@Composable
|
||||
internal fun SendNavigationButtons(
|
||||
|
|
@ -172,21 +175,27 @@ private fun SendingText(
|
|||
}
|
||||
|
||||
if (feeFiat != null && sendingFiat != null) {
|
||||
val sendingValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = sendingFiat,
|
||||
fiatCurrencyCode = feeState.appCurrency.code,
|
||||
fiatCurrencySymbol = feeState.appCurrency.symbol,
|
||||
val sendingValue = getFiatFormatted(
|
||||
value = sendingFiat,
|
||||
currencySymbol = feeState.appCurrency.symbol,
|
||||
currencyCode = feeState.appCurrency.code,
|
||||
)
|
||||
val feeValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = feeFiat,
|
||||
fiatCurrencyCode = feeState.appCurrency.code,
|
||||
fiatCurrencySymbol = feeState.appCurrency.symbol,
|
||||
val feeValue = getFiatFormatted(
|
||||
value = feeState.fee?.amount?.value,
|
||||
currencySymbol = feeState.appCurrency.symbol,
|
||||
currencyCode = feeState.appCurrency.code,
|
||||
)
|
||||
val textResource = remember(sendingValue, feeValue) {
|
||||
resourceReference(
|
||||
id = R.string.send_summary_transaction_description,
|
||||
formatArgs = wrappedList(sendingValue, feeValue),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = R.string.send_summary_transaction_description, sendingValue, feeValue),
|
||||
text = textResource.resolveAnnotatedReference(),
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ import kotlinx.coroutines.flow.withIndex
|
|||
@Composable
|
||||
internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
BackHandler(onBack = uiState.clickIntents::onBackClick)
|
||||
val onBackClick = uiState.clickIntents::onBackClick.takeIf {
|
||||
uiState.sendState?.isSending != true
|
||||
} ?: {}
|
||||
BackHandler(onBack = onBackClick)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ internal fun LazyListScope.notifications(
|
|||
notifications: ImmutableList<SendNotification>,
|
||||
modifier: Modifier = Modifier,
|
||||
hasPaddingAbove: Boolean = false,
|
||||
isClickDisabled: Boolean = false,
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = notifications,
|
||||
|
|
@ -44,6 +45,7 @@ internal fun LazyListScope.notifications(
|
|||
-> null
|
||||
is SendNotification.Error -> TangemTheme.colors.icon.warning
|
||||
},
|
||||
isEnabled = !isClickDisabled,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue