Updated on 2026-08-14
This commit is contained in:
commit
64fab92d06
231 changed files with 5317 additions and 1797 deletions
|
|
@ -35,6 +35,8 @@ dependencies {
|
|||
implementation(projects.domain.txhistory)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.appTheme)
|
||||
implementation(projects.domain.appTheme.models)
|
||||
|
||||
implementation(project(":common"))
|
||||
implementation(project(":core:analytics"))
|
||||
|
|
@ -48,13 +50,15 @@ dependencies {
|
|||
implementation(project(":libs:crypto"))
|
||||
implementation(project(":libs:auth"))
|
||||
|
||||
implementation(project(":data:source:preferences"))
|
||||
implementation(projects.data.appCurrency)
|
||||
implementation(projects.data.appTheme)
|
||||
implementation(projects.data.card)
|
||||
implementation(projects.data.common)
|
||||
implementation(projects.data.settings)
|
||||
implementation(projects.data.source.preferences)
|
||||
implementation(projects.data.tokens)
|
||||
implementation(projects.data.txhistory)
|
||||
implementation(projects.data.appCurrency)
|
||||
implementation(projects.data.wallets)
|
||||
|
||||
/** Features */
|
||||
implementation(project(":features:onboarding"))
|
||||
|
|
@ -86,6 +90,7 @@ dependencies {
|
|||
implementation(deps.lifecycle.runtime.ktx)
|
||||
implementation(deps.lifecycle.common.java8)
|
||||
implementation(deps.lifecycle.viewModel.ktx)
|
||||
implementation(deps.lifecycle.compose)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.constraintLayout)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.tangem.domain.DomainLayer
|
|||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.WalletManagersRepository
|
||||
import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
|
||||
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
|
||||
|
|
@ -42,6 +43,7 @@ import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
|
|||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
import com.tangem.tap.common.log.TangemLogCollector
|
||||
import com.tangem.tap.common.log.TimberFormatStrategy
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.appReducer
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -165,6 +167,9 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
@Inject
|
||||
lateinit var appCurrencyRepository: AppCurrencyRepository
|
||||
|
||||
@Inject
|
||||
lateinit var walletManagersFacade: WalletManagersFacade
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
|
@ -184,12 +189,13 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
tokenDetailsFeatureToggles = tokenDetailsFeatureToggles,
|
||||
scanCardProcessor = scanCardProcessor,
|
||||
appCurrencyRepository = appCurrencyRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Logger.addLogAdapter(AndroidLogAdapter())
|
||||
Logger.addLogAdapter(AndroidLogAdapter(TimberFormatStrategy()))
|
||||
Timber.plant(
|
||||
object : Timber.DebugTree() {
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.tap.common.apptheme
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
|
||||
internal object MutableAppThemeModeHolder : AppThemeModeHolder {
|
||||
|
||||
override val appThemeMode: MutableState<AppThemeMode> = mutableStateOf(AppThemeMode.DEFAULT)
|
||||
|
||||
var value: AppThemeMode
|
||||
set(value) {
|
||||
appThemeMode.value = value
|
||||
}
|
||||
get() = appThemeMode.value
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ fun WalletManager?.getAddressData(): WalletDataModel.AddressData? {
|
|||
}
|
||||
|
||||
fun <T> WalletManager.Companion.stub(): T {
|
||||
val wallet = Wallet(Blockchain.Unknown, setOf(), Wallet.PublicKey(byteArrayOf(), null, null), setOf())
|
||||
val wallet = Wallet(Blockchain.Unknown, setOf(), Wallet.PublicKey(byteArrayOf(), null), setOf())
|
||||
return object : WalletManager(wallet) {
|
||||
override val currentHost: String = ""
|
||||
override suspend fun update() {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.tap.common.log
|
||||
|
||||
import com.orhanobut.logger.FormatStrategy
|
||||
import com.orhanobut.logger.LogStrategy
|
||||
import com.orhanobut.logger.LogcatLogStrategy
|
||||
|
||||
class TimberFormatStrategy : FormatStrategy {
|
||||
|
||||
private val logStrategy: LogStrategy = LogcatLogStrategy()
|
||||
|
||||
override fun log(priority: Int, tag: String?, message: String) {
|
||||
logTopBorder(priority, tag)
|
||||
val bytes = message.toByteArray()
|
||||
val length = bytes.size
|
||||
if (length <= CHUNK_SIZE) {
|
||||
logContent(priority, tag, message)
|
||||
logBottomBorder(priority, tag)
|
||||
return
|
||||
}
|
||||
var i = 0
|
||||
while (i < length) {
|
||||
val count = (length - i).coerceAtMost(CHUNK_SIZE)
|
||||
// create a new String with system's default charset (which is UTF-8 for Android)
|
||||
logContent(priority, tag, String(bytes, i, count))
|
||||
i += CHUNK_SIZE
|
||||
}
|
||||
logBottomBorder(priority, tag)
|
||||
}
|
||||
|
||||
private fun logTopBorder(logType: Int, tag: String?) {
|
||||
logChunk(logType, tag, TOP_BORDER)
|
||||
}
|
||||
|
||||
private fun logBottomBorder(logType: Int, tag: String?) {
|
||||
logChunk(logType, tag, BOTTOM_BORDER)
|
||||
}
|
||||
|
||||
private fun logContent(logType: Int, tag: String?, chunk: String) {
|
||||
chunk.split(System.lineSeparator()).forEach { line ->
|
||||
logChunk(logType, tag, "$HORIZONTAL_LINE $line")
|
||||
}
|
||||
}
|
||||
|
||||
private fun logChunk(priority: Int, tag: String?, chunk: String) {
|
||||
logStrategy.log(priority, tag, chunk)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
/**
|
||||
* Android's max limit for a log entry is ~4076 bytes,
|
||||
* so 4000 bytes is used as chunk size since default charset
|
||||
* is UTF-8
|
||||
*/
|
||||
private const val CHUNK_SIZE = 4000
|
||||
|
||||
const val TOP_LEFT_CORNER = "┌"
|
||||
const val BOTTOM_LEFT_CORNER = "└"
|
||||
const val HORIZONTAL_LINE = "│"
|
||||
const val DOUBLE_DIVIDER = "────────────────────────────────────────────────────────"
|
||||
const val TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER
|
||||
const val BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.core.navigation.NavigationStateHolder
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import dagger.Binds
|
||||
|
|
@ -20,4 +21,8 @@ internal interface AppStateHolderModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindsNavigationStateHolder(appStateHolder: AppStateHolder): NavigationStateHolder
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindsReduxStateHolder(appStateHolder: AppStateHolder): ReduxStateHolder
|
||||
}
|
||||
18
app/src/main/java/com/tangem/tap/di/ThemeModule.kt
Normal file
18
app/src/main/java/com/tangem/tap/di/ThemeModule.kt
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
internal object ThemeModule {
|
||||
|
||||
@Provides
|
||||
fun provideAppThemeModeHolder(): AppThemeModeHolder {
|
||||
return MutableAppThemeModeHolder
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.apptheme.ChangeAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ViewModelComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(ViewModelComponent::class)
|
||||
internal object AppThemeDomainModule {
|
||||
|
||||
@Provides
|
||||
fun provideGetAppThemeModeUpdatesUseCase(appThemeModeRepository: AppThemeModeRepository): GetAppThemeModeUseCase {
|
||||
return GetAppThemeModeUseCase(appThemeModeRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideChangeAppThemeModeUseCase(appThemeModeRepository: AppThemeModeRepository): ChangeAppThemeModeUseCase {
|
||||
return ChangeAppThemeModeUseCase(appThemeModeRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -51,10 +51,9 @@ internal object TokensDomainModule {
|
|||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideToggleTokenListGroupingUseCase(
|
||||
networksRepository: NetworksRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ToggleTokenListGroupingUseCase {
|
||||
return ToggleTokenListGroupingUseCase(networksRepository, dispatchers)
|
||||
return ToggleTokenListGroupingUseCase(dispatchers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -71,4 +70,12 @@ internal object TokensDomainModule {
|
|||
): ApplyTokenListSortingUseCase {
|
||||
return ApplyTokenListSortingUseCase(currenciesRepository, dispatchers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideGetCryptoCurrencyActionsUseCase(
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCryptoCurrencyActionsUseCase {
|
||||
return GetCryptoCurrencyActionsUseCase(dispatchers)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -48,4 +49,22 @@ internal object WalletsDomainModule {
|
|||
fun providesSelectWalletUseCase(walletsStateHolder: WalletsStateHolder): SelectWalletUseCase {
|
||||
return SelectWalletUseCase(walletsStateHolder = walletsStateHolder)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun providesUpdateWalletUseCase(walletsStateHolder: WalletsStateHolder): UpdateWalletUseCase {
|
||||
return UpdateWalletUseCase(walletsStateHolder = walletsStateHolder)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun providesDeleteWalletUseCase(walletsStateHolder: WalletsStateHolder): DeleteWalletUseCase {
|
||||
return DeleteWalletUseCase(walletsStateHolder = walletsStateHolder)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun providesShouldSaveUserWalletsUseCase(walletsRepository: WalletsRepository): ShouldSaveUserWalletsUseCase {
|
||||
return ShouldSaveUserWalletsUseCase(walletsRepository = walletsRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.tap.domain.settings
|
||||
|
||||
import com.tangem.domain.settings.repositories.LegacySettingsRepository
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
|
||||
internal class DefaultLegacySettingsRepository(
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
) : LegacySettingsRepository {
|
||||
|
||||
override fun canUseBiometry(): Boolean = tangemSdkManager.canUseBiometry
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.domain.tasks.product
|
||||
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
|
|
@ -224,7 +225,8 @@ private class ScanWalletProcessor(
|
|||
walletData = session.environment.walletData,
|
||||
primaryCard = primaryCard,
|
||||
)
|
||||
val derivations = collectDerivations(card, config, scanResponse.derivationStyleProvider)
|
||||
val derivations =
|
||||
collectDerivations(card, config, scanResponse.derivationStyleProvider)
|
||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
||||
callback(CompletionResult.Success(scanResponse))
|
||||
return@launch
|
||||
|
|
@ -249,60 +251,101 @@ private class ScanWalletProcessor(
|
|||
val userTokensRepository = userTokensRepository ?: return emptyList()
|
||||
val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(card)
|
||||
.toMutableList()
|
||||
.ifEmpty {
|
||||
mutableListOf(
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
),
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
),
|
||||
)
|
||||
}
|
||||
.ifEmpty { getDefaultBlockchains(derivationStyleProvider) }
|
||||
|
||||
if (card.settings.isHDWalletAllowed) {
|
||||
blockchainsToDerive.addAll(
|
||||
listOf(
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
),
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.EthereumTestnet,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
),
|
||||
),
|
||||
)
|
||||
blockchainsToDerive += getEthereumBlockchains(derivationStyleProvider)
|
||||
}
|
||||
if (additionalBlockchainsToDerive != null) {
|
||||
blockchainsToDerive.addAll(
|
||||
additionalBlockchainsToDerive.map {
|
||||
BlockchainNetwork(
|
||||
blockchain = it,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
additionalBlockchainsToDerive?.let {
|
||||
blockchainsToDerive += getAdditionalBlockchainToDerive(derivationStyleProvider, it)
|
||||
}
|
||||
|
||||
// we should generate second key for cardano
|
||||
// because cardano address generation for wallet2 requires keys from 2 derivations
|
||||
// https://developers.cardano.org/docs/get-started/cardano-serialization-lib/generating-keys/
|
||||
val secondCardanoNetwork = blockchainsToDerive
|
||||
.find { it.blockchain == Blockchain.Cardano }
|
||||
?.let { getCardanoSecondNetwork(it) }
|
||||
secondCardanoNetwork?.let { blockchainsToDerive.add(it) }
|
||||
|
||||
// pay attention to this
|
||||
if (!card.useOldStyleDerivation) {
|
||||
blockchainsToDerive.removeAll(
|
||||
listOf(
|
||||
Blockchain.BSC, Blockchain.BSCTestnet,
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet,
|
||||
Blockchain.RSK,
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet,
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet,
|
||||
).map {
|
||||
BlockchainNetwork(
|
||||
blockchain = it,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
)
|
||||
},
|
||||
removeUnnecessaryBlockchains(blockchainsToDerive, derivationStyleProvider)
|
||||
}
|
||||
|
||||
return blockchainsToDerive.distinct()
|
||||
}
|
||||
|
||||
private fun getDefaultBlockchains(
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
): MutableList<BlockchainNetwork> {
|
||||
return mutableListOf(
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
),
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getEthereumBlockchains(derivationStyleProvider: DerivationStyleProvider): List<BlockchainNetwork> {
|
||||
return listOf(
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
),
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.EthereumTestnet,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAdditionalBlockchainToDerive(
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
collection: Collection<Blockchain>,
|
||||
): List<BlockchainNetwork> {
|
||||
return collection.map {
|
||||
BlockchainNetwork(
|
||||
blockchain = it,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
return blockchainsToDerive.distinct()
|
||||
}
|
||||
|
||||
private fun getCardanoSecondNetwork(cardanoBlockchainNetwork: BlockchainNetwork): BlockchainNetwork? {
|
||||
val cardanoStandardDerivation = cardanoBlockchainNetwork.derivationPath?.let { DerivationPath(it) }
|
||||
?: return null
|
||||
val cardanoPatchedDerivation = CardanoUtils.extendedDerivationPath(cardanoStandardDerivation)
|
||||
return BlockchainNetwork(
|
||||
blockchain = Blockchain.Cardano,
|
||||
derivationPath = cardanoPatchedDerivation.rawPath,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun removeUnnecessaryBlockchains(
|
||||
blockchainsToDerive: MutableList<BlockchainNetwork>,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
) {
|
||||
blockchainsToDerive.removeAll(
|
||||
listOf(
|
||||
Blockchain.BSC, Blockchain.BSCTestnet,
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet,
|
||||
Blockchain.RSK,
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet,
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet,
|
||||
).map {
|
||||
BlockchainNetwork(
|
||||
blockchain = it,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun collectDerivations(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.demo
|
|||
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
|
|
@ -21,8 +22,8 @@ object DemoHelper {
|
|||
|
||||
private val disabledActionFeatures = listOf(
|
||||
WalletConnectAction.StartWalletConnect::class.java,
|
||||
WalletAction.TradeCryptoAction.Buy::class.java,
|
||||
WalletAction.TradeCryptoAction.Sell::class.java,
|
||||
TradeCryptoAction.Buy::class.java,
|
||||
TradeCryptoAction.Sell::class.java,
|
||||
BackupAction.StartBackup::class.java,
|
||||
WalletAction.ExploreAddress::class.java,
|
||||
DetailsAction.ResetToFactory.Start::class.java,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import android.webkit.WebView
|
|||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ class SellCurrencyIntentHandler : IntentHandler {
|
|||
|
||||
Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
|
||||
store.dispatchWithMain(
|
||||
WalletAction.TradeCryptoAction.SendCrypto(
|
||||
TradeCryptoAction.SendCrypto(
|
||||
currencyId = currency,
|
||||
amount = amount,
|
||||
destinationAddress = destinationAddress,
|
||||
|
|
|
|||
|
|
@ -103,13 +103,16 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
tvBody.isVisible = false
|
||||
|
||||
btnMainAction.text = ""
|
||||
btnMainAction.icon = null
|
||||
btnAlternativeAction.text = ""
|
||||
btnAlternativeAction.icon = null
|
||||
tvHeader.text = ""
|
||||
tvBody.text = ""
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(OnboardingNoteAction.CreateWallet)
|
||||
|
|
@ -133,6 +136,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
if (state.isBuyAllowed) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.TopUp)
|
||||
}
|
||||
|
|
@ -144,6 +148,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
}
|
||||
} else {
|
||||
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
|
||||
}
|
||||
|
|
@ -179,6 +184,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
|
||||
private fun setupDoneState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
store.dispatch(OnboardingNoteAction.Done)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
|
|||
|
||||
private fun setupCreateWalletState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(OnboardingOtherCardsAction.CreateWallet)
|
||||
|
|
@ -92,6 +93,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
|
|||
|
||||
private fun setupDoneState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
store.dispatch(OnboardingOtherCardsAction.Done)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.util.TypedValue
|
|||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionInflater
|
||||
|
|
@ -15,7 +16,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.ShareElement
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -175,10 +176,12 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
}
|
||||
|
||||
private fun setupWelcomeState(state: TwinCardsState) {
|
||||
setupWelcomeState(state) { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet)) }
|
||||
setupWelcomeState(state) {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWelcomeState(state: TwinCardsState, mainAction: VoidCallback) =
|
||||
private fun setupWelcomeState(state: TwinCardsState, onMainButtonClick: VoidCallback) =
|
||||
with(mainBinding.onboardingActionContainer) {
|
||||
twinsWidget.toWelcome(false)
|
||||
|
||||
|
|
@ -192,7 +195,12 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
)
|
||||
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.setOnClickListener { mainAction() }
|
||||
btnMainAction.icon = when (state.currentStep) {
|
||||
is TwinCardsStep.WelcomeOnly -> null
|
||||
is TwinCardsStep.Welcome -> AppCompatResources.getDrawable(requireContext(), R.drawable.ic_tangem_24)
|
||||
else -> null
|
||||
}
|
||||
btnMainAction.setOnClickListener { onMainButtonClick() }
|
||||
}
|
||||
|
||||
private fun setupWarningState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
|
|
@ -210,6 +218,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
}
|
||||
btnMainAction.isEnabled = state.userWasUnderstandIfWalletRecreate
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
|
||||
}
|
||||
|
|
@ -251,6 +260,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(
|
||||
|
|
@ -274,6 +284,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
|
||||
btnMainAction.text =
|
||||
getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchSecondStep(
|
||||
|
|
@ -298,6 +309,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchThirdStep(
|
||||
|
|
@ -343,6 +355,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
}
|
||||
} else {
|
||||
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
|
||||
}
|
||||
|
|
@ -367,6 +380,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
|
||||
private fun setupDoneState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.Confetti.Hide)
|
||||
store.dispatch(TwinCardsAction.Done)
|
||||
|
|
|
|||
|
|
@ -204,6 +204,8 @@ class OnboardingWalletFragment :
|
|||
|
||||
private fun setupCreateWalletState() = with(binding) {
|
||||
layoutButtonsCommon.btnWalletMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(OnboardingWalletAction.CreateWallet)
|
||||
|
|
@ -243,6 +245,7 @@ class OnboardingWalletFragment :
|
|||
|
||||
with(layoutButtonsCommon) {
|
||||
btnWalletMainAction.text = getText(R.string.onboarding_button_backup_now)
|
||||
btnWalletMainAction.icon = null
|
||||
btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
|
||||
|
||||
btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
|
||||
|
|
@ -261,6 +264,7 @@ class OnboardingWalletFragment :
|
|||
|
||||
with(layoutButtonsCommon) {
|
||||
btnWalletMainAction.text = getString(R.string.onboarding_button_scan_origin_card)
|
||||
btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnWalletAlternativeAction.hide()
|
||||
btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
|
||||
}
|
||||
|
|
@ -277,6 +281,7 @@ class OnboardingWalletFragment :
|
|||
layoutButtonsAddCards.root.show()
|
||||
layoutButtonsCommon.root.hide()
|
||||
layoutButtonsAddCards.btnAddCard.text = getText(R.string.onboarding_button_add_backup_card)
|
||||
layoutButtonsAddCards.btnAddCard.setIconResource(R.drawable.ic_tangem_24)
|
||||
if (state.backupCardsNumber < state.maxBackupCards) {
|
||||
layoutButtonsAddCards.btnAddCard.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) }
|
||||
} else {
|
||||
|
|
@ -358,6 +363,7 @@ class OnboardingWalletFragment :
|
|||
state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) },
|
||||
)
|
||||
layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_backup_origin)
|
||||
layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
|
||||
|
||||
animator.showWritePrimaryCard(state)
|
||||
|
|
@ -390,6 +396,7 @@ class OnboardingWalletFragment :
|
|||
R.string.onboarding_button_backup_card_format,
|
||||
cardNumber,
|
||||
)
|
||||
layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
|
||||
store.dispatch(BackupAction.WriteBackupCard(cardNumber))
|
||||
}
|
||||
|
|
@ -408,6 +415,7 @@ class OnboardingWalletFragment :
|
|||
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
|
||||
layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_continue_wallet)
|
||||
layoutButtonsCommon.btnWalletMainAction.icon = null
|
||||
layoutButtonsCommon.btnWalletAlternativeAction.hide()
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
|
|
|
|||
|
|
@ -6,24 +6,27 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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
|
||||
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWalletScreenState>() {
|
||||
override val expandedHeightFraction: Float = .98f
|
||||
|
||||
private val viewModel by viewModels<SaveWalletViewModel>()
|
||||
|
|
@ -34,12 +37,8 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWa
|
|||
}
|
||||
|
||||
@Composable
|
||||
override fun provideState(): State<SaveWalletScreenState> {
|
||||
return viewModel.state.collectAsState()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(state: SaveWalletScreenState, modifier: Modifier) {
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
val enrollBiometricsDialog by rememberUpdatedState(newValue = state.enrollBiometricsDialog)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
|||
import com.tangem.domain.common.extensions.minimalAmount
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
|
|
@ -38,7 +39,6 @@ import com.tangem.tap.features.send.redux.*
|
|||
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
|
||||
import com.tangem.tap.features.send.redux.states.*
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -254,7 +254,7 @@ private fun sendTransaction(
|
|||
),
|
||||
)
|
||||
Analytics.sendSelectedCurrencyEvent(mainCurrencyType)
|
||||
dispatch(WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId))
|
||||
dispatch(TradeCryptoAction.FinishSelling(externalTransactionData.transactionId))
|
||||
} else {
|
||||
Analytics.send(
|
||||
Basic.TransactionSent(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.google.android.material.textfield.TextInputEditText
|
|||
import com.tangem.Message
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.sdk.extensions.hideSoftKeyboard
|
||||
import com.tangem.tap.common.KeyboardObserver
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
|
|
@ -39,7 +40,6 @@ import com.tangem.tap.features.send.redux.FeeActionUi.*
|
|||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -337,9 +337,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
if (externalTransactionData == null) {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
} else {
|
||||
store.dispatch(
|
||||
WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId),
|
||||
)
|
||||
store.dispatch(TradeCryptoAction.FinishSelling(externalTransactionData.transactionId))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,24 @@ import androidx.activity.viewModels
|
|||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.fragments.ComposeActivity
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeActivity
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.common.analytics.events.Chat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class SprinklrActivity : ComposeActivity() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
internal class SprinklrActivity : ComposeActivity<SprinklrScreenState>() {
|
||||
private val viewModel by viewModels<SprinklrViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -25,12 +35,14 @@ internal class SprinklrActivity : ComposeActivity<SprinklrScreenState>() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
override fun provideState(): State<SprinklrScreenState> {
|
||||
return viewModel.state.collectAsState()
|
||||
}
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
val systemBarsColor = TangemTheme.colors.background.primary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(systemBarsColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(state: SprinklrScreenState, modifier: Modifier) {
|
||||
BackHandler(onBack = state.onNavigateBack)
|
||||
SprinklrScreenContent(
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -7,17 +7,14 @@ import androidx.compose.animation.fadeOut
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.FabPosition
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -32,9 +29,7 @@ import androidx.compose.ui.unit.TextUnit
|
|||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.items
|
||||
import androidx.paging.compose.*
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -134,8 +129,11 @@ private fun TokensListContent(
|
|||
item { DifferentAddressesWarning() }
|
||||
}
|
||||
|
||||
items(items = tokens, key = TokenItemState::composedId) {
|
||||
it?.let { TokenItem(model = it) }
|
||||
tokens.itemKey(TokenItemState::composedId)
|
||||
tokens.itemContentType(TokenItemState::composedId)
|
||||
|
||||
items(items = tokens.itemSnapshotList.items, key = TokenItemState::composedId) {
|
||||
TokenItem(model = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.tap.features.wallet.models.Currency
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class CryptoCurrencyConverter : Converter<Currency, CryptoCurrency> {
|
||||
internal class CryptoCurrencyConverter : Converter<Currency, CryptoCurrency> {
|
||||
|
||||
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,16 @@ import androidx.core.os.bundleOf
|
|||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.extensions.toCoinId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
|
|
@ -27,6 +31,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
|
|||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -35,19 +40,27 @@ import kotlinx.serialization.json.Json
|
|||
import com.tangem.feature.swap.domain.models.domain.Currency as SwapCurrency
|
||||
|
||||
class TradeCryptoMiddleware {
|
||||
fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) {
|
||||
fun handle(state: () -> AppState?, action: TradeCryptoAction) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
||||
when (action) {
|
||||
is WalletAction.TradeCryptoAction.Buy -> proceedBuyAction(state, action)
|
||||
is WalletAction.TradeCryptoAction.Sell -> proceedSellAction()
|
||||
is WalletAction.TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action)
|
||||
is WalletAction.TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||
is WalletAction.TradeCryptoAction.Swap -> openSwap()
|
||||
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
|
||||
is TradeCryptoAction.Sell -> proceedSellAction()
|
||||
is TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action)
|
||||
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||
is TradeCryptoAction.Swap -> {
|
||||
openSwap(currency = store.state.walletState.selectedWalletData?.currency?.toSwapCurrency())
|
||||
}
|
||||
is TradeCryptoAction.New.Buy -> proceedNewBuyAction(state, action)
|
||||
TradeCryptoAction.New.Send -> store.dispatch(WalletAction.Send())
|
||||
is TradeCryptoAction.New.Sell -> proceedNewSellAction(action)
|
||||
is TradeCryptoAction.New.Swap -> {
|
||||
openSwap(currency = action.cryptoCurrency.toSwapCurrency())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedBuyAction(state: () -> AppState?, action: WalletAction.TradeCryptoAction.Buy) {
|
||||
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
val currency = chooseAppropriateCurrency(store.state.walletState) ?: return
|
||||
|
||||
|
|
@ -75,7 +88,7 @@ class TradeCryptoMiddleware {
|
|||
buyErc20TestnetTokens(
|
||||
card = card,
|
||||
walletManager = walletManager,
|
||||
token = currency.token,
|
||||
destinationAddress = currency.token.contractAddress,
|
||||
)
|
||||
}
|
||||
return
|
||||
|
|
@ -93,6 +106,56 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun proceedNewBuyAction(state: () -> AppState?, action: TradeCryptoAction.New.Buy) {
|
||||
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress?.defaultAddress ?: return
|
||||
|
||||
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog())
|
||||
return
|
||||
}
|
||||
|
||||
val status = action.cryptoCurrencyStatus
|
||||
val currency = status.currency
|
||||
val blockchain = Blockchain.fromId(currency.network.id.value)
|
||||
if (currency is CryptoCurrency.Token && currency.network.isTestnet) {
|
||||
scope.launch {
|
||||
val walletManager = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
.getOrCreateWalletManager(
|
||||
userWallet = action.userWallet,
|
||||
blockchain = blockchain,
|
||||
derivationPath = blockchain.derivationPath(
|
||||
style = action.userWallet.scanResponse.derivationStyleProvider.getDerivationStyle(),
|
||||
),
|
||||
)
|
||||
|
||||
if (walletManager !is EthereumWalletManager) {
|
||||
store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum")
|
||||
return@launch
|
||||
}
|
||||
|
||||
buyErc20TestnetTokens(
|
||||
card = action.userWallet.scanResponse.card,
|
||||
walletManager = walletManager,
|
||||
destinationAddress = currency.contractAddress,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Buy,
|
||||
blockchain = blockchain,
|
||||
cryptoCurrencyName = currency.symbol,
|
||||
fiatCurrencyName = action.appCurrencyCode,
|
||||
walletAddress = networkAddress,
|
||||
)?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedSellAction() {
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
val currency = chooseAppropriateCurrency(store.state.walletState) ?: return
|
||||
|
|
@ -115,6 +178,22 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun proceedNewSellAction(action: TradeCryptoAction.New.Sell) {
|
||||
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress?.defaultAddress ?: return
|
||||
val currency = action.cryptoCurrencyStatus.currency
|
||||
|
||||
store.state.globalState.exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Sell,
|
||||
blockchain = Blockchain.fromId(currency.network.id.value),
|
||||
cryptoCurrencyName = currency.symbol,
|
||||
fiatCurrencyName = action.appCurrencyCode,
|
||||
walletAddress = networkAddress,
|
||||
)?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Withdraw.ScreenOpened())
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseAppropriateCurrency(walletState: WalletState): Currency? {
|
||||
return if (walletState.primaryTokenData == null) {
|
||||
walletState.selectedWalletData?.currency
|
||||
|
|
@ -126,7 +205,7 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
|
||||
private fun preconfigureAndOpenSendScreen(action: TradeCryptoAction.SendCrypto) {
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
|
||||
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency)))
|
||||
|
|
@ -160,16 +239,44 @@ class TradeCryptoMiddleware {
|
|||
)?.let { store.dispatchOpenUrl(it) }
|
||||
}
|
||||
|
||||
private fun openSwap() {
|
||||
val currency = store.state.walletState.selectedWalletData?.currency?.toSwapCurrency()
|
||||
val bundle =
|
||||
bundleOf(
|
||||
SwapFragment.CURRENCY_BUNDLE_KEY to Json.encodeToString(currency),
|
||||
SwapFragment.DERIVATION_PATH to store.state.walletState.selectedWalletData?.currency?.derivationPath,
|
||||
)
|
||||
private fun openSwap(currency: SwapCurrency?) {
|
||||
val bundle = bundleOf(
|
||||
SwapFragment.CURRENCY_BUNDLE_KEY to Json.encodeToString(currency),
|
||||
SwapFragment.DERIVATION_PATH to store.state.walletState.selectedWalletData?.currency?.derivationPath,
|
||||
)
|
||||
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle))
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.toSwapCurrency(): SwapCurrency {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
|
||||
return when (this) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
SwapCurrency.NativeToken(
|
||||
id = blockchain.toCoinId(),
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
networkId = blockchain.toNetworkId(),
|
||||
// no need to set logoUrl for blockchain cause
|
||||
// error when form url with coinId, coinId of eth and arbitrum the same
|
||||
logoUrl = "",
|
||||
)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
SwapCurrency.NonNativeToken(
|
||||
id = id.value,
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
networkId = blockchain.toNetworkId(),
|
||||
logoUrl = getIconUrl(id.value),
|
||||
contractAddress = contractAddress,
|
||||
decimalCount = decimals,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Currency.toSwapCurrency(): SwapCurrency {
|
||||
return when (this) {
|
||||
is Currency.Blockchain -> {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.userwallets.GetCardImageUseCase
|
||||
import com.tangem.domain.wallets.legacy.lockIfLockable
|
||||
import com.tangem.tap.*
|
||||
|
|
@ -87,7 +88,7 @@ class WalletMiddleware {
|
|||
val walletState = store.state.walletState
|
||||
|
||||
when (action) {
|
||||
is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action)
|
||||
is TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action)
|
||||
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
|
||||
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState)
|
||||
is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
error = null,
|
||||
)
|
||||
}
|
||||
is WalletAction.TradeCryptoAction -> return newState
|
||||
is WalletAction.AppCurrencyAction -> {
|
||||
newState = appCurrencyReducer.reduce(action, newState)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
|
|
@ -276,9 +277,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), SafeSt
|
|||
) {
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
binding.rowButtons.apply {
|
||||
onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) }
|
||||
onSellClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
|
||||
onSwapClick = { store.dispatch(WalletAction.TradeCryptoAction.Swap) }
|
||||
onBuyClick = { store.dispatch(TradeCryptoAction.Buy()) }
|
||||
onSellClick = { store.dispatch(TradeCryptoAction.Sell) }
|
||||
onSwapClick = { store.dispatch(TradeCryptoAction.Swap) }
|
||||
onTradeClick = {
|
||||
store.dispatch(
|
||||
WalletAction.DialogAction.ChooseTradeActionDialog(
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.badoo.mvicore.modelWatcher
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.OneTouchClickListener
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
|
|
@ -39,15 +40,15 @@ class ChooseTradeActionBottomSheetDialog(
|
|||
|
||||
dialogBtnBuy.setOnClickListener {
|
||||
dismiss()
|
||||
store.dispatch(WalletAction.TradeCryptoAction.Buy())
|
||||
store.dispatch(TradeCryptoAction.Buy())
|
||||
}
|
||||
dialogBtnSell.setOnClickListener {
|
||||
dismiss()
|
||||
store.dispatch(WalletAction.TradeCryptoAction.Sell)
|
||||
store.dispatch(TradeCryptoAction.Sell)
|
||||
}
|
||||
dialogBtnSwap.setOnClickListener {
|
||||
dismiss()
|
||||
store.dispatch(WalletAction.TradeCryptoAction.Swap)
|
||||
store.dispatch(TradeCryptoAction.Swap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import android.view.LayoutInflater
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding
|
||||
|
|
@ -40,7 +40,7 @@ class RussianCardholdersWarningBottomSheetDialog(
|
|||
if (dialogData != null) {
|
||||
store.dispatchOpenUrl(dialogData.topUpUrl)
|
||||
} else {
|
||||
store.dispatch(WalletAction.TradeCryptoAction.Buy(checkUserLocation = false))
|
||||
store.dispatch(TradeCryptoAction.Buy(checkUserLocation = false))
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
|
|
@ -151,9 +152,9 @@ class SingleWalletView : WalletView() {
|
|||
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
binding?.rowButtons?.apply {
|
||||
onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) }
|
||||
onSellClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
|
||||
onSwapClick = { store.dispatch(WalletAction.TradeCryptoAction.Swap) }
|
||||
onBuyClick = { store.dispatch(TradeCryptoAction.Buy()) }
|
||||
onSellClick = { store.dispatch(TradeCryptoAction.Sell) }
|
||||
onSwapClick = { store.dispatch(TradeCryptoAction.Swap) }
|
||||
onTradeClick = {
|
||||
store.dispatch(
|
||||
WalletAction.DialogAction.ChooseTradeActionDialog(
|
||||
|
|
|
|||
|
|
@ -7,35 +7,32 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
|
||||
import com.tangem.core.ui.components.wallets.RenameWalletDialogContent
|
||||
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.common.analytics.events.MyWallets
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.tap.features.walletSelector.ui.components.BiometricsDisabledWarningContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.BiometricsLockoutWarningContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.KeyInvalidatedWarningContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.RemoveWalletDialogContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.*
|
||||
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
|
||||
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<WalletSelectorScreenState>() {
|
||||
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
private val viewModel by viewModels<WalletSelectorViewModel>()
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
|
@ -45,10 +42,8 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
|
|||
}
|
||||
|
||||
@Composable
|
||||
override fun provideState(): State<WalletSelectorScreenState> = viewModel.state.collectAsState()
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(state: WalletSelectorScreenState, modifier: Modifier) {
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
val dialog by rememberUpdatedState(newValue = state.dialog)
|
||||
|
|
@ -90,7 +85,13 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
|
|||
if (dialog == null) return
|
||||
when (dialog) {
|
||||
is DialogModel.RemoveWalletDialog -> RemoveWalletDialogContent(dialog)
|
||||
is DialogModel.RenameWalletDialog -> RenameWalletDialogContent(dialog)
|
||||
is DialogModel.RenameWalletDialog -> {
|
||||
RenameWalletDialogContent(
|
||||
name = dialog.currentName,
|
||||
onConfirm = dialog.onConfirm,
|
||||
onDismiss = dialog.onDismiss,
|
||||
)
|
||||
}
|
||||
is WarningModel.BiometricsLockoutWarning -> BiometricsLockoutWarningContent(dialog)
|
||||
is WarningModel.KeyInvalidatedWarning -> KeyInvalidatedWarningContent(dialog)
|
||||
is WarningModel.BiometricsDisabledWarning -> BiometricsDisabledWarningContent(dialog)
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
package com.tangem.tap.features.walletSelector.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.AdditionalTextInputDialogParams
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.TextInputDialog
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun RenameWalletDialogContent(dialog: DialogModel.RenameWalletDialog) {
|
||||
var value by remember {
|
||||
mutableStateOf(TextFieldValue(text = dialog.currentName))
|
||||
}
|
||||
|
||||
TextInputDialog(
|
||||
fieldValue = value,
|
||||
confirmButton = DialogButton(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
enabled = value.text.isNotEmpty() && value.text != dialog.currentName,
|
||||
onClick = { dialog.onConfirm(value.text) },
|
||||
),
|
||||
onDismissDialog = dialog.onDismiss,
|
||||
onValueChange = { newValue ->
|
||||
value = newValue
|
||||
},
|
||||
title = stringResource(R.string.user_wallet_list_rename_popup_title),
|
||||
dismissButton = DialogButton(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = dialog.onDismiss,
|
||||
),
|
||||
textFieldParams = AdditionalTextInputDialogParams(
|
||||
label = stringResource(R.string.user_wallet_list_rename_popup_placeholder),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun RenameWalletDialogContentSample(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
RenameWalletDialogContent(dialog = DialogModel.RenameWalletDialog("", {}, {}))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun RenameWalletDialogContentPreview_Light() {
|
||||
TangemTheme {
|
||||
RenameWalletDialogContentSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun RenameWalletDialogContentPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
RenameWalletDialogContentSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,35 +1,36 @@
|
|||
package com.tangem.tap.features.welcome.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.fragments.ComposeFragment
|
||||
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
|
||||
import com.tangem.tap.features.welcome.ui.components.WarningDialog
|
||||
import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class WelcomeFragment : ComposeFragment<WelcomeScreenState>() {
|
||||
@AndroidEntryPoint
|
||||
internal class WelcomeFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
private val viewModel by viewModels<WelcomeViewModel>()
|
||||
|
||||
|
|
@ -40,19 +41,15 @@ internal class WelcomeFragment : ComposeFragment<WelcomeScreenState>() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
override fun provideState(): State<WelcomeScreenState> {
|
||||
return viewModel.state.collectAsState()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(state: WelcomeScreenState, modifier: Modifier) {
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
val warning by rememberUpdatedState(newValue = state.warning)
|
||||
|
||||
val backgroundColor = colorResource(id = R.color.background_primary)
|
||||
val backgroundColor = TangemTheme.colors.background.primary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(color = backgroundColor)
|
||||
setSystemBarsColor(backgroundColor)
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
|
|
@ -61,7 +58,8 @@ internal class WelcomeFragment : ComposeFragment<WelcomeScreenState>() {
|
|||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.systemBarsPadding(),
|
||||
.systemBarsPadding()
|
||||
.background(backgroundColor),
|
||||
) {
|
||||
WelcomeScreenContent(
|
||||
showUnlockProgress = state.showUnlockWithBiometricsProgress,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
|||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
@ -87,17 +86,12 @@ class CurrencyExchangeManager(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, token: Token) {
|
||||
suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, destinationAddress: String) {
|
||||
walletManager.safeUpdate()
|
||||
|
||||
val amountToSend = Amount(walletManager.wallet.blockchain)
|
||||
val destinationAddress = token.contractAddress
|
||||
|
||||
val feeResult = walletManager.getFee(
|
||||
amountToSend,
|
||||
destinationAddress,
|
||||
) as? Result.Success ?: return
|
||||
|
||||
val feeResult = walletManager.getFee(amountToSend, destinationAddress) as? Result.Success ?: return
|
||||
val fee = when (val feeForTx = feeResult.data) {
|
||||
is TransactionFee.Choosable -> feeForTx.minimum
|
||||
is TransactionFee.Single -> feeForTx.normal
|
||||
|
|
@ -106,8 +100,6 @@ suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletMa
|
|||
val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO
|
||||
if (coinValue < fee.amount.value) return
|
||||
|
||||
val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress)
|
||||
|
||||
val signer = TangemSigner(
|
||||
card = card,
|
||||
tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk,
|
||||
|
|
@ -121,5 +113,13 @@ suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletMa
|
|||
),
|
||||
)
|
||||
}
|
||||
walletManager.send(transaction, signer)
|
||||
|
||||
walletManager.send(
|
||||
transactionData = walletManager.createTransaction(
|
||||
amount = amountToSend,
|
||||
fee = fee,
|
||||
destination = destinationAddress,
|
||||
),
|
||||
signer = signer,
|
||||
)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.navigation.NavigationAction
|
|||
import com.tangem.core.navigation.NavigationStateHolder
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
|
|
@ -14,6 +15,7 @@ import com.tangem.tap.domain.walletStores.WalletStoresManager
|
|||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Store
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -21,7 +23,7 @@ import javax.inject.Inject
|
|||
* Holds objects from old modules, that missing in DI graph.
|
||||
* Object sets manually to use in new modules and [AppStateHolder] proxies its to DI.
|
||||
*/
|
||||
class AppStateHolder @Inject constructor() : WalletsStateHolder, NavigationStateHolder {
|
||||
class AppStateHolder @Inject constructor() : WalletsStateHolder, NavigationStateHolder, ReduxStateHolder {
|
||||
|
||||
override var userWalletsListManager: UserWalletsListManager? = null
|
||||
set(value) {
|
||||
|
|
@ -50,4 +52,8 @@ class AppStateHolder @Inject constructor() : WalletsStateHolder, NavigationState
|
|||
override fun navigate(action: NavigationAction) {
|
||||
mainStore?.dispatch(action)
|
||||
}
|
||||
|
||||
override fun dispatch(action: Action) {
|
||||
mainStore?.dispatch(action)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
|||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
|
||||
|
|
@ -33,6 +34,7 @@ data class DaggerGraphState(
|
|||
val scanCardProcessor: ScanCardProcessor? = null,
|
||||
val cardSdkConfigRepository: CardSdkConfigRepository? = null,
|
||||
val appCurrencyRepository: AppCurrencyRepository? = null,
|
||||
val walletManagersFacade: WalletManagersFacade? = null,
|
||||
) : StateType {
|
||||
|
||||
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_main_action"
|
||||
style="@style/TapPrimaryButton"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
tools:icon="@drawable/ic_tangem_24"
|
||||
tools:text="@string/welcome_unlock_card" />
|
||||
|
||||
<ProgressBar
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSend"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
style="@style/TapPrimaryIconButtonLegacy"
|
||||
android:layout_width="match_parent"
|
||||
android:fontFamily="@font/saira_semi_condensed_regular"
|
||||
android:text="@string/common_send"
|
||||
|
|
|
|||
|
|
@ -7,14 +7,15 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_add_card"
|
||||
style="@style/TapSecondaryButton"
|
||||
style="@style/TapSecondaryIconButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="92dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/btn_continue"
|
||||
app:layout_constraintStart_toStartOf="@id/btn_continue"
|
||||
tools:text="+ Add backup card" />
|
||||
tools:icon="@drawable/ic_tangem_24"
|
||||
tools:text="Add backup card" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_continue"
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_wallet_main_action"
|
||||
style="@style/TapPrimaryButton"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
android:layout_width="204dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/home_bottom_fl_scan_card_container_margin_bottom"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:icon="@drawable/ic_tangem_24"
|
||||
tools:text="Main Action" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_main_action"
|
||||
style="@style/TapPrimaryButton"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
tools:icon="@drawable/ic_tangem_24"
|
||||
tools:text="@string/welcome_unlock_card" />
|
||||
|
||||
<ProgressBar
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_trade"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
style="@style/TapPrimaryIconButtonLegacy"
|
||||
android:text="@string/wallet_button_actions"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_arrows_up_down"
|
||||
|
|
@ -24,21 +24,21 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_buy"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
style="@style/TapPrimaryIconButtonLegacy"
|
||||
android:text="@string/common_buy"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_arrow_up" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_sell"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
style="@style/TapPrimaryIconButtonLegacy"
|
||||
android:text="@string/common_sell"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_arrow_down" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_swap"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
style="@style/TapPrimaryIconButtonLegacy"
|
||||
android:text="@string/swapping_swap_action"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_arrows_up_down"
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_send"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
style="@style/TapPrimaryIconButtonLegacy"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/common_send"
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="BaseAppTheme">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="BaseAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
|
||||
<style name="BaseAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar" />
|
||||
|
||||
<style name="AppTheme" parent="BaseAppTheme">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorSecondary">@color/colorSecondary</item>
|
||||
<item name="android:textColor">@color/darkGray6</item>
|
||||
|
|
@ -10,8 +11,6 @@
|
|||
<item name="materialCardViewStyle">@style/Widget.AppTheme.CardView</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme" parent="BaseAppTheme" />
|
||||
|
||||
<style name="SplashTheme" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/background_splash</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/inset_splash</item>
|
||||
|
|
@ -46,38 +45,48 @@
|
|||
<item name="android:minHeight">48dp</item>
|
||||
<item name="android:insetTop">0dp</item>
|
||||
<item name="android:insetBottom">0dp</item>
|
||||
<item name="cornerRadius">14dp</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textStyle">normal</item>
|
||||
<item name="android:fontFamily">@font/roboto_medium</item>
|
||||
<item name="android:fontFamily">@font/roboto_regular</item>
|
||||
<item name="android:letterSpacing">0</item>
|
||||
<item name="lineHeight">24sp</item>
|
||||
<item name="cornerRadius">@dimen/btn_corner_radius_large</item>
|
||||
<item name="fontWeight">500</item>
|
||||
<item name="lineHeight">16sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TapPrimaryButton" parent="BaseTapButton">
|
||||
<item name="android:backgroundTint">@color/selector_btn_primary</item>
|
||||
<item name="android:textColor">@color/selector_btn_text_primary</item>
|
||||
<item name="android:textColor">@color/selector_btn_content_primary</item>
|
||||
</style>
|
||||
|
||||
<style name="TapSecondaryButton" parent="BaseTapButton">
|
||||
<item name="android:backgroundTint">@color/selector_btn_secondary</item>
|
||||
<item name="android:textColor">@color/selector_btn_text_secondary</item>
|
||||
<item name="android:textColor">@color/selector_btn_content_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="TapTextButton" parent="BaseTapButton">
|
||||
<item name="android:minHeight">40dp</item>
|
||||
<item name="cornerRadius">@dimen/btn_corner_radius</item>
|
||||
<item name="android:backgroundTint">@android:color/transparent</item>
|
||||
<item name="android:textColor">@color/selector_btn_text_secondary</item>
|
||||
<item name="android:textColor">@color/selector_btn_content_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="TapPrimaryIconButton" parent="TapPrimaryButton">
|
||||
<item name="iconTint">@color/selector_btn_content_primary</item>
|
||||
<item name="iconGravity">textEnd</item>
|
||||
</style>
|
||||
|
||||
<style name="TapPrimaryIconButtonLegacy" parent="TapPrimaryButton">
|
||||
<item name="android:gravity">start|center_vertical</item>
|
||||
<item name="iconTint">@color/selector_btn_text_primary</item>
|
||||
<item name="iconTint">@color/selector_btn_content_primary</item>
|
||||
<item name="iconGravity">end</item>
|
||||
</style>
|
||||
|
||||
<style name="TapSecondaryIconButton" parent="TapSecondaryButton">
|
||||
<item name="iconTint">@color/selector_btn_content_secondary</item>
|
||||
<item name="iconGravity">textEnd</item>
|
||||
</style>
|
||||
|
||||
<style name="TapChip" parent="@style/Widget.MaterialComponents.Chip.Choice">
|
||||
<item name="chipBackgroundColor">@color/selector_chip_background</item>
|
||||
<item name="chipStrokeColor">@color/selector_chip_stroke</item>
|
||||
|
|
|
|||
|
|
@ -37,5 +37,8 @@ fun createNetworkLoggingInterceptor(): Interceptor {
|
|||
return LoggingInterceptor.Builder()
|
||||
.setLevel(Level.BODY)
|
||||
.log(Log.VERBOSE)
|
||||
.tag(NETWORK_LOGS_TAG)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private const val NETWORK_LOGS_TAG = "NetworkLogs"
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.datasource.local.appcurrency
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
// TODO: Will be implemented in [REDACTED_TASK_KEY] task
|
||||
internal class MockSelectedAppCurrencyStore : SelectedAppCurrencyStore {
|
||||
|
||||
override fun get(): Flow<CurrenciesResponse.Currency> {
|
||||
return flowOf(
|
||||
CurrenciesResponse.Currency(
|
||||
id = "usd",
|
||||
code = "USD",
|
||||
name = "US Dollar",
|
||||
unit = "$",
|
||||
type = "fiat",
|
||||
rateBTC = "",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun store(item: CurrenciesResponse.Currency) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
|
@ -8,4 +8,6 @@ interface SelectedAppCurrencyStore {
|
|||
fun get(): Flow<CurrenciesResponse.Currency>
|
||||
|
||||
suspend fun store(item: CurrenciesResponse.Currency)
|
||||
|
||||
suspend fun isEmpty(): Boolean
|
||||
}
|
||||
|
|
@ -7,4 +7,9 @@ import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
|||
|
||||
internal class DefaultSelectedAppCurrencyStore(
|
||||
dataStore: StringKeyDataStore<CurrenciesResponse.Currency>,
|
||||
) : SelectedAppCurrencyStore, KeylessDataStoreDecorator<CurrenciesResponse.Currency>(dataStore)
|
||||
) : SelectedAppCurrencyStore, KeylessDataStoreDecorator<CurrenciesResponse.Currency>(dataStore) {
|
||||
|
||||
override suspend fun isEmpty(): Boolean {
|
||||
return getSyncOrNull() == null
|
||||
}
|
||||
}
|
||||
|
|
@ -10,15 +10,15 @@ internal abstract class KeylessDataStoreDecorator<Value : Any>(
|
|||
return STRING_KEY
|
||||
}
|
||||
|
||||
fun get(): Flow<Value> {
|
||||
open fun get(): Flow<Value> {
|
||||
return get(Unit)
|
||||
}
|
||||
|
||||
suspend fun getSyncOrNull(): Value? {
|
||||
open suspend fun getSyncOrNull(): Value? {
|
||||
return getSyncOrNull(Unit)
|
||||
}
|
||||
|
||||
suspend fun store(item: Value) {
|
||||
open suspend fun store(item: Value) {
|
||||
store(Unit, item)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@
|
|||
<string name="common_receive">Получить</string>
|
||||
<string name="common_reject">Отклонить</string>
|
||||
<string name="common_reload">Перезагрузить</string>
|
||||
<string name="common_rename">Переименовать</string>
|
||||
<string name="common_reset">Сбросить</string>
|
||||
<string name="common_save_changes">Сохранить изменения</string>
|
||||
<string name="common_search">Искать</string>
|
||||
|
|
@ -116,6 +117,7 @@
|
|||
<string name="common_transactions">Транзакции</string>
|
||||
<string name="common_transfer">Перевод</string>
|
||||
<string name="common_understand">Я понял</string>
|
||||
<string name="common_unlock_needed">Необходима разблокировка</string>
|
||||
<string name="common_unreachable">Недоступно</string>
|
||||
<string name="common_yes">Да</string>
|
||||
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
|
||||
|
|
@ -457,6 +459,7 @@
|
|||
<string name="transaction_history_contract_address">контракт: %s</string>
|
||||
<string name="transaction_history_empty_transactions">У вас еще нет транзакций</string>
|
||||
<string name="transaction_history_error_failed_to_load">Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.</string>
|
||||
<string name="transaction_history_multiple_addresses">Несколько адресов</string>
|
||||
<string name="transaction_history_not_supported_description">История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе.</string>
|
||||
<string name="transaction_history_transaction_from_address">от: %s</string>
|
||||
<string name="transaction_history_transaction_to_address">на: %s</string>
|
||||
|
|
@ -472,6 +475,8 @@
|
|||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="twins_recreate_warning">Это действие необратимо. У вас не будет доступа к старому кошельку.</string>
|
||||
<string name="twins_scan_twin_with_number">Приложите twin-карту с номером %s и не убирайте до окончания операции</string>
|
||||
<string name="unlock_wallet_description_full">Используйте %s или отсканируйте карту, чтобы получить доступ к своему кошельку</string>
|
||||
<string name="unlock_wallet_description_short">Используйте %s или отсканируйте карту</string>
|
||||
<string name="user_wallet_list_add_button">Добавить новый кошелек</string>
|
||||
<string name="user_wallet_list_delete_prompt">Вы уверены, что хотите удалить этот кошелек?</string>
|
||||
<string name="user_wallet_list_editing_count">%d выбрано</string>
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@
|
|||
<string name="common_ok">OK</string>
|
||||
<string name="common_origin_card">主卡片</string>
|
||||
<string name="common_reject">拒絕</string>
|
||||
<string name="common_rename">重新命名</string>
|
||||
<string name="common_reset">重置</string>
|
||||
<string name="common_save_changes">保存設置</string>
|
||||
<string name="common_search">搜索</string>
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@
|
|||
<string name="common_receive">Receive</string>
|
||||
<string name="common_reject">Reject</string>
|
||||
<string name="common_reload">Reload</string>
|
||||
<string name="common_rename">Rename</string>
|
||||
<string name="common_reset">Reset</string>
|
||||
<string name="common_save_changes">Save changes</string>
|
||||
<string name="common_search">Search</string>
|
||||
|
|
@ -115,6 +116,7 @@
|
|||
<string name="common_transactions">Transactions</string>
|
||||
<string name="common_transfer">Transfer</string>
|
||||
<string name="common_understand">I understand</string>
|
||||
<string name="common_unlock_needed">Unlock needed</string>
|
||||
<string name="common_unreachable">Unreachable</string>
|
||||
<string name="common_yes">Yes</string>
|
||||
<string name="contract_address_copied_message">Contract address copied!</string>
|
||||
|
|
@ -448,6 +450,7 @@
|
|||
<string name="transaction_history_contract_address">contract: %s</string>
|
||||
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
|
||||
<string name="transaction_history_error_failed_to_load">Failed to load transaction history.\nClick on reload button to update the information.</string>
|
||||
<string name="transaction_history_multiple_addresses">Multiple addresses</string>
|
||||
<string name="transaction_history_not_supported_description">Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer.</string>
|
||||
<string name="transaction_history_transaction_from_address">from: %s</string>
|
||||
<string name="transaction_history_transaction_to_address">to: %s</string>
|
||||
|
|
@ -463,6 +466,8 @@
|
|||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="twins_recreate_warning">This action is irreversible. You will not have access to the old wallet.</string>
|
||||
<string name="twins_scan_twin_with_number">Tap the twin card with number %s and do not remove until the end of the operation</string>
|
||||
<string name="unlock_wallet_description_full">Use %s or scan a card to have an access to your wallet</string>
|
||||
<string name="unlock_wallet_description_short">Use %s or scan a card</string>
|
||||
<string name="user_wallet_list_add_button">Add new wallet</string>
|
||||
<string name="user_wallet_list_delete_prompt">Are you sure you want to delete this wallet?</string>
|
||||
<string name="user_wallet_list_editing_count">%d selected</string>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,13 @@ plugins {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.appTheme.models)
|
||||
|
||||
/** Project - Core */
|
||||
implementation(projects.core.res)
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
|
|
@ -22,6 +29,4 @@ dependencies {
|
|||
implementation(deps.material)
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
||||
implementation(project(":core:res"))
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=281-248&mode=design&t=bXqehWPHyATKcZEW-4)
|
||||
* */
|
||||
@Composable
|
||||
fun SimpleSettingsRow(
|
||||
title: String,
|
||||
@DrawableRes icon: Int,
|
||||
onItemsClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
subtitle: String? = null,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.height(TangemTheme.dimens.size56)
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = {
|
||||
if (enabled) {
|
||||
onItemsClick()
|
||||
}
|
||||
},
|
||||
),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val textColor: Color by animateColorAsState(
|
||||
targetValue = if (enabled) {
|
||||
TangemTheme.colors.text.primary1
|
||||
} else {
|
||||
TangemTheme.colors.text.secondary
|
||||
},
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing20),
|
||||
tint = textColor,
|
||||
)
|
||||
Column(modifier = Modifier.padding(end = TangemTheme.dimens.spacing20)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = textColor,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = !subtitle.isNullOrEmpty(),
|
||||
) {
|
||||
Text(
|
||||
text = subtitle ?: "",
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ fun HorizontalActionChips(
|
|||
) {
|
||||
items(
|
||||
items = buttons,
|
||||
key = { config -> "${config.text.hashCode()} ${config.iconResId}" },
|
||||
key = { config -> config.text.hashCode() },
|
||||
itemContent = { ActionButton(config = it) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.core.ui.components.buttons.actions
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -7,6 +8,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -78,43 +80,52 @@ private fun Button(
|
|||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.button.secondary,
|
||||
) {
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled,
|
||||
label = "Update background color",
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.heightIn(min = TangemTheme.dimens.size36)
|
||||
.clip(shape)
|
||||
.background(
|
||||
color = if (config.enabled) color else TangemTheme.colors.button.disabled,
|
||||
shape = shape,
|
||||
)
|
||||
.background(color = backgroundColor, shape = shape)
|
||||
.clickable(enabled = config.enabled, onClick = config.onClick)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing24,
|
||||
)
|
||||
.padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing24)
|
||||
.padding(vertical = TangemTheme.dimens.spacing8),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = config.iconResId),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(size = TangemTheme.dimens.size20),
|
||||
tint = when {
|
||||
val iconTint by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.icon.informative
|
||||
config.dimContent -> TangemTheme.colors.icon.secondary
|
||||
else -> TangemTheme.colors.icon.primary1
|
||||
},
|
||||
label = "Update tint color",
|
||||
)
|
||||
|
||||
Icon(
|
||||
painter = painterResource(id = config.iconResId),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(size = TangemTheme.dimens.size20),
|
||||
tint = iconTint,
|
||||
)
|
||||
|
||||
SpacerW8()
|
||||
|
||||
Text(
|
||||
text = config.text.resolveReference(),
|
||||
color = when {
|
||||
val textColor by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.text.disabled
|
||||
config.dimContent -> TangemTheme.colors.text.secondary
|
||||
else -> TangemTheme.colors.text.primary1
|
||||
},
|
||||
label = "Update text color",
|
||||
)
|
||||
|
||||
Text(
|
||||
text = config.text.resolveReference(),
|
||||
color = textColor,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.button,
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ private fun LoadingContent() {
|
|||
RectangleShimmer(
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.size158,
|
||||
height = TangemTheme.dimens.size20,
|
||||
height = TangemTheme.dimens.size18,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.Image
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -22,6 +23,8 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import java.util.UUID
|
||||
|
||||
|
|
@ -158,6 +161,18 @@ private fun Icon(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
is TransactionState.Loading -> {
|
||||
CircleShimmer(modifier = modifier.size(TangemTheme.dimens.size40))
|
||||
}
|
||||
is TransactionState.Locked -> {
|
||||
Box(modifier = modifier.size(TangemTheme.dimens.size40)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -206,6 +221,11 @@ private fun Title(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
modifier = modifier.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12),
|
||||
)
|
||||
}
|
||||
is TransactionState.Locked -> {
|
||||
LockedContent(
|
||||
modifier = modifier.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -219,7 +239,7 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
is TransactionState.Send,
|
||||
-> stringResource(
|
||||
id = R.string.transaction_history_transaction_to_address,
|
||||
state.address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
is TransactionState.Receiving,
|
||||
is TransactionState.Receive,
|
||||
|
|
@ -227,13 +247,13 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
is TransactionState.Approved,
|
||||
-> stringResource(
|
||||
id = R.string.transaction_history_transaction_from_address,
|
||||
state.address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
is TransactionState.Swapping,
|
||||
is TransactionState.Swapped,
|
||||
-> stringResource(
|
||||
id = R.string.transaction_history_contract_address,
|
||||
state.address,
|
||||
state.address.resolveReference(),
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
|
|
@ -247,6 +267,11 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
modifier = modifier.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12),
|
||||
)
|
||||
}
|
||||
is TransactionState.Locked -> {
|
||||
LockedContent(
|
||||
modifier = modifier.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -267,6 +292,11 @@ private fun Amount(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
|
||||
)
|
||||
}
|
||||
is TransactionState.Locked -> {
|
||||
LockedContent(
|
||||
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,9 +317,24 @@ private fun Timestamp(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
|
||||
)
|
||||
}
|
||||
is TransactionState.Locked -> {
|
||||
LockedContent(
|
||||
modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LockedContent(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.background(
|
||||
color = TangemTheme.colors.field.primary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius6),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_TransactionItem_LightTheme(
|
||||
|
|
@ -314,49 +359,49 @@ private class TransactionItemStateProvider : CollectionPreviewParameterProvider<
|
|||
collection = listOf(
|
||||
TransactionState.Sending(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = "33BddS...ga2B",
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "-0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
),
|
||||
TransactionState.Receiving(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = "33BddS...ga2B",
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
),
|
||||
TransactionState.Approving(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = "33BddS...ga2B",
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
),
|
||||
TransactionState.Swapping(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = "33BddS...ga2B",
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
),
|
||||
TransactionState.Send(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = "33BddS...ga2B",
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "-0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
),
|
||||
TransactionState.Receive(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = "33BddS...ga2B",
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
),
|
||||
TransactionState.Approved(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = "33BddS...ga2B",
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
),
|
||||
TransactionState.Swapped(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
address = "33BddS...ga2B",
|
||||
address = TextReference.Str("33BddS...ga2B"),
|
||||
amount = "+0.500913 BTC",
|
||||
timestamp = "8:41",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.itemsIndexed
|
||||
import androidx.paging.compose.itemContentType
|
||||
import androidx.paging.compose.itemKey
|
||||
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionBlock
|
||||
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionsBlockState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
|
|
@ -26,7 +28,7 @@ fun LazyListScope.txHistoryItems(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state) {
|
||||
is TxHistoryState.ContentState -> {
|
||||
is TxHistoryState.Content -> {
|
||||
contentItems(
|
||||
txHistoryItems = requireNotNull(txHistoryItems),
|
||||
modifier = modifier,
|
||||
|
|
@ -58,8 +60,18 @@ private fun LazyListScope.contentItems(
|
|||
txHistoryItems: LazyPagingItems<TxHistoryState.TxHistoryItemState>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
txHistoryItems.itemKey { item ->
|
||||
when (item) {
|
||||
is TxHistoryState.TxHistoryItemState.GroupTitle -> item.title
|
||||
is TxHistoryState.TxHistoryItemState.Title -> item.onExploreClick.hashCode()
|
||||
is TxHistoryState.TxHistoryItemState.Transaction -> item.state.txHash
|
||||
}
|
||||
}
|
||||
|
||||
txHistoryItems.itemContentType { it::class.java }
|
||||
|
||||
itemsIndexed(
|
||||
items = txHistoryItems,
|
||||
items = txHistoryItems.itemSnapshotList.items,
|
||||
key = { _, item ->
|
||||
when (item) {
|
||||
is TxHistoryState.TxHistoryItemState.GroupTitle -> item.title
|
||||
|
|
@ -68,8 +80,6 @@ private fun LazyListScope.contentItems(
|
|||
}
|
||||
},
|
||||
) { index, item ->
|
||||
if (item == null) return@itemsIndexed
|
||||
|
||||
TxHistoryListItem(
|
||||
state = item,
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.core.ui.components.transactions.intents
|
||||
|
||||
interface TxHistoryClickIntents {
|
||||
|
||||
fun onBuyClick()
|
||||
|
||||
fun onReloadClick()
|
||||
|
||||
fun onExploreClick()
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.core.ui.components.transactions.state
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
/**
|
||||
* Transaction component state
|
||||
*
|
||||
|
|
@ -20,14 +22,14 @@ sealed interface TransactionState {
|
|||
*/
|
||||
sealed class Content(
|
||||
override val txHash: String,
|
||||
open val address: String,
|
||||
open val address: TextReference,
|
||||
open val amount: String,
|
||||
open val timestamp: String,
|
||||
) : TransactionState {
|
||||
|
||||
fun copySealed(
|
||||
txHash: String = this.txHash,
|
||||
address: String = this.address,
|
||||
address: TextReference = this.address,
|
||||
amount: String = this.amount,
|
||||
timestamp: String = this.timestamp,
|
||||
): Content {
|
||||
|
|
@ -54,7 +56,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
sealed class ProcessedTransactionContent(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : Content(txHash, address, amount, timestamp)
|
||||
|
|
@ -69,7 +71,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
sealed class CompletedTransactionContent(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : Content(txHash, address, amount, timestamp)
|
||||
|
|
@ -84,7 +86,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
data class Sending(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : ProcessedTransactionContent(txHash, address, amount, timestamp)
|
||||
|
|
@ -99,7 +101,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
data class Receiving(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : ProcessedTransactionContent(txHash, address, amount, timestamp)
|
||||
|
|
@ -114,7 +116,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
data class Approving(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : ProcessedTransactionContent(txHash, address, amount, timestamp)
|
||||
|
|
@ -129,7 +131,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
data class Swapping(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : ProcessedTransactionContent(txHash, address, amount, timestamp)
|
||||
|
|
@ -144,7 +146,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
data class Send(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : CompletedTransactionContent(txHash, address, amount, timestamp)
|
||||
|
|
@ -159,7 +161,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
data class Receive(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : CompletedTransactionContent(txHash, address, amount, timestamp)
|
||||
|
|
@ -174,7 +176,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
data class Approved(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : CompletedTransactionContent(txHash, address, amount, timestamp)
|
||||
|
|
@ -189,7 +191,7 @@ sealed interface TransactionState {
|
|||
*/
|
||||
data class Swapped(
|
||||
override val txHash: String,
|
||||
override val address: String,
|
||||
override val address: TextReference,
|
||||
override val amount: String,
|
||||
override val timestamp: String,
|
||||
) : CompletedTransactionContent(txHash, address, amount, timestamp)
|
||||
|
|
@ -200,4 +202,11 @@ sealed interface TransactionState {
|
|||
* @property txHash transaction hash
|
||||
*/
|
||||
data class Loading(override val txHash: String) : TransactionState
|
||||
|
||||
/**
|
||||
* Locked state
|
||||
*
|
||||
* @property txHash transaction hash
|
||||
*/
|
||||
data class Locked(override val txHash: String) : TransactionState
|
||||
}
|
||||
|
|
@ -1,77 +1,17 @@
|
|||
package com.tangem.core.ui.components.transactions.state
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.core.ui.components.wallet.WalletLockedContentState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
/**
|
||||
* Wallet transaction history state
|
||||
*/
|
||||
/** Wallet transaction history state */
|
||||
sealed interface TxHistoryState {
|
||||
|
||||
/**
|
||||
* Wallet transaction history state with content
|
||||
*
|
||||
* @property items content items
|
||||
* @property contentItems content items
|
||||
*/
|
||||
sealed class ContentState(open val items: Flow<PagingData<TxHistoryItemState>>) : TxHistoryState
|
||||
|
||||
/**
|
||||
* Loading state
|
||||
*
|
||||
* @property onExploreClick lambda be invoke when explore button was clicked
|
||||
*/
|
||||
data class Loading(val onExploreClick: () -> Unit) : ContentState(
|
||||
items = flowOf(
|
||||
PagingData.from(
|
||||
listOf(
|
||||
TxHistoryItemState.Title(onExploreClick = onExploreClick),
|
||||
TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Wallet transaction history state with loading transactions
|
||||
*
|
||||
* @property itemsCount count of loading transactions
|
||||
*/
|
||||
data class ContentWithLoadingItems(val itemsCount: Int) : ContentState(
|
||||
items = flowOf(
|
||||
value = PagingData.from(
|
||||
data = buildList(capacity = itemsCount) {
|
||||
add(TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)))
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Wallet transaction history state with content
|
||||
*
|
||||
* @property items content items
|
||||
*/
|
||||
data class Content(override val items: Flow<PagingData<TxHistoryItemState>>) : ContentState(items)
|
||||
|
||||
/**
|
||||
* Locked state
|
||||
*
|
||||
* @property onExploreClick lambda be invoke when explore button was clicked
|
||||
*/
|
||||
data class Locked(val onExploreClick: () -> Unit) :
|
||||
ContentState(
|
||||
items = flowOf(
|
||||
PagingData.from(
|
||||
listOf(
|
||||
TxHistoryItemState.Title(onExploreClick = onExploreClick),
|
||||
TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
WalletLockedContentState
|
||||
data class Content(val contentItems: MutableStateFlow<PagingData<TxHistoryItemState>>) : TxHistoryState
|
||||
|
||||
/**
|
||||
* Empty state
|
||||
|
|
@ -119,7 +59,18 @@ sealed interface TxHistoryState {
|
|||
data class Transaction(val state: TransactionState) : TxHistoryItemState
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val LOADING_TX_HASH = "LOADING_TX_HASH"
|
||||
companion object {
|
||||
private const val LOADING_TX_HASH = "LOADING_TX_HASH"
|
||||
|
||||
fun getDefaultLoadingTransactions(onExploreClick: () -> Unit): PagingData<TxHistoryItemState> {
|
||||
return PagingData.from(
|
||||
data = listOf(
|
||||
TxHistoryItemState.Title(onExploreClick = onExploreClick),
|
||||
TxHistoryItemState.Transaction(
|
||||
state = TransactionState.Loading(txHash = LOADING_TX_HASH),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package com.tangem.core.ui.components.wallet
|
||||
|
||||
/**
|
||||
* Wallet locked content state.
|
||||
* It allows to divide the locked content of multi-currency and single-currency wallets.
|
||||
*/
|
||||
interface WalletLockedContentState
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.core.ui.components.wallets
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.AdditionalTextInputDialogParams
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.TextInputDialog
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Rename a wallet dialog
|
||||
*
|
||||
* @param name wallet name
|
||||
* @param onConfirm lambda be invoked when Confirm button is clicked
|
||||
* @param onDismiss lambda be invoked when dialog is dismissed
|
||||
*/
|
||||
@Composable
|
||||
fun RenameWalletDialogContent(name: String, onConfirm: (newName: String) -> Unit, onDismiss: () -> Unit) {
|
||||
var value by remember { mutableStateOf(TextFieldValue(text = name)) }
|
||||
|
||||
TextInputDialog(
|
||||
fieldValue = value,
|
||||
confirmButton = DialogButton(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
enabled = value.text.isNotEmpty() && value.text != name,
|
||||
onClick = { onConfirm(value.text) },
|
||||
),
|
||||
onDismissDialog = onDismiss,
|
||||
onValueChange = { value = it },
|
||||
title = stringResource(R.string.user_wallet_list_rename_popup_title),
|
||||
dismissButton = DialogButton(title = stringResource(id = R.string.common_cancel), onClick = onDismiss),
|
||||
textFieldParams = AdditionalTextInputDialogParams(
|
||||
label = stringResource(R.string.user_wallet_list_rename_popup_placeholder),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun RenameWalletDialogContentPreview_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
RenameWalletDialogContent(name = "", onConfirm = {}, onDismiss = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun RenameWalletDialogContentPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
RenameWalletDialogContent(name = "", onConfirm = {}, onDismiss = {})
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.core.ui.event
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.NonRestartableComposable
|
||||
|
||||
/**
|
||||
* A Composable function that reacts to a given [StateEvent], executing the provided action only once when the event
|
||||
* is triggered.
|
||||
*
|
||||
* @param event The [StateEvent] to listen to.
|
||||
* @param onTrigger The action to execute when the event is triggered.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun EventEffect(event: StateEvent, onTrigger: suspend () -> Unit) {
|
||||
LaunchedEffect(event) {
|
||||
if (event is StateEvent.Triggered) {
|
||||
onTrigger()
|
||||
event.consume()
|
||||
}
|
||||
}
|
||||
}
|
||||
51
core/ui/src/main/java/com/tangem/core/ui/event/StateEvent.kt
Normal file
51
core/ui/src/main/java/com/tangem/core/ui/event/StateEvent.kt
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.core.ui.event
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
/**
|
||||
* Represents compose state event, which can be consumed or triggered.
|
||||
*
|
||||
* This is especially useful for handling one-off UI events like showing snack bars or navigation which should not be
|
||||
* re-triggered on recompositions or state changes.
|
||||
*/
|
||||
@Immutable
|
||||
sealed class StateEvent {
|
||||
|
||||
/** Defines the action to be executed when the event is consumed. */
|
||||
protected abstract val onConsume: () -> Unit
|
||||
|
||||
/**
|
||||
* Represents an already consumed state event.
|
||||
* Events of this type will not trigger any further actions.
|
||||
*/
|
||||
object Consumed : StateEvent() {
|
||||
override val onConsume: () -> Unit = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a state event that has been triggered but not yet consumed.
|
||||
*
|
||||
* @property onConsume The action to be executed when the event is consumed.
|
||||
*/
|
||||
data class Triggered(override val onConsume: () -> Unit) : StateEvent()
|
||||
|
||||
/**
|
||||
* Consumes the event, triggering any associated action.
|
||||
*/
|
||||
fun consume() {
|
||||
onConsume()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [StateEvent.Triggered] instance.
|
||||
*
|
||||
* @param onConsume The action to be executed when the event is consumed.
|
||||
* @return A triggered state event.
|
||||
*/
|
||||
fun triggered(onConsume: () -> Unit): StateEvent.Triggered = StateEvent.Triggered(onConsume)
|
||||
|
||||
/**
|
||||
* Represents a statically defined [StateEvent.Consumed] event.
|
||||
*/
|
||||
val consumed: StateEvent.Consumed = StateEvent.Consumed
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.core.ui.extensions
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
|
||||
/**
|
||||
* Retrieves the resource ID for the network badge of a [CryptoCurrency].
|
||||
*
|
||||
* This property provides a way to fetch the appropriate drawable resource ID
|
||||
* for the network badge of a given cryptocurrency. For coins, this will typically
|
||||
* return null as they do not have network badges, while tokens will fetch the icon
|
||||
* based on their associated network ID.
|
||||
*
|
||||
* @return Drawable resource ID for the network badge or null if the cryptocurrency is a coin.
|
||||
*/
|
||||
@get:DrawableRes
|
||||
val CryptoCurrency.networkBadgeIconResId: Int?
|
||||
get() = when (this) {
|
||||
is CryptoCurrency.Coin -> null
|
||||
is CryptoCurrency.Token -> getActiveIconRes(network.id.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the resource ID for the icon of a [CryptoCurrency].
|
||||
*
|
||||
* This property provides a way to fetch the appropriate drawable resource ID
|
||||
* for the icon of a given cryptocurrency.
|
||||
*
|
||||
* @return Drawable resource ID for the cryptocurrency icon.
|
||||
*/
|
||||
@get:DrawableRes
|
||||
val CryptoCurrency.iconResId: Int
|
||||
get() = when (this) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val rawCoinId = id.rawCurrencyId
|
||||
|
||||
if (rawCoinId != null) {
|
||||
getActiveIconResByCoinId(rawCoinId, network.id.value)
|
||||
} else {
|
||||
R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
is CryptoCurrency.Token -> R.drawable.ic_alert_24
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.ui.fragments
|
||||
package com.tangem.core.ui.extensions
|
||||
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.ColorRes
|
||||
|
|
@ -20,12 +20,19 @@ sealed interface TextReference {
|
|||
* Text resource id
|
||||
*
|
||||
* @property id resource id
|
||||
* @property formatArgs arguments
|
||||
*
|
||||
* Impossible to use [kotlinx.collections.immutable.ImmutableList] because [Any] is unstable.
|
||||
* @property formatArgs arguments. Impossible to use [kotlinx.collections.immutable.ImmutableList] because [Any] is
|
||||
* unstable.
|
||||
*/
|
||||
data class Res(@StringRes val id: Int, val formatArgs: WrappedList<Any> = WrappedList(emptyList())) : TextReference
|
||||
|
||||
/**
|
||||
* Plural resource id
|
||||
*
|
||||
* @property id resource id
|
||||
* @property count count
|
||||
* @property formatArgs arguments. Impossible to use [kotlinx.collections.immutable.ImmutableList] because [Any] is
|
||||
* unstable.
|
||||
*/
|
||||
data class PluralRes(@PluralsRes val id: Int, val count: Int, val formatArgs: WrappedList<Any>) : TextReference
|
||||
|
||||
/**
|
||||
|
|
@ -34,9 +41,16 @@ sealed interface TextReference {
|
|||
* @property value value
|
||||
*/
|
||||
data class Str(val value: String) : TextReference
|
||||
|
||||
/**
|
||||
* Combined reference. It concatenates all [refs].
|
||||
*
|
||||
* @see [TextReference.plus] method
|
||||
*/
|
||||
data class Combined(val refs: WrappedList<TextReference>) : TextReference
|
||||
}
|
||||
|
||||
/** Get text */
|
||||
/** Resolve [TextReference] as [String] */
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun TextReference.resolveReference(): String {
|
||||
|
|
@ -44,5 +58,23 @@ fun TextReference.resolveReference(): String {
|
|||
is TextReference.Res -> stringResource(id, *formatArgs.toTypedArray())
|
||||
is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray())
|
||||
is TextReference.Str -> value
|
||||
is TextReference.Combined -> {
|
||||
buildString {
|
||||
refs.forEach {
|
||||
append(it.resolveReference())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Concatenate [this] reference with [ref] */
|
||||
operator fun TextReference.plus(ref: TextReference): TextReference {
|
||||
return when (this) {
|
||||
is TextReference.Combined -> copy(refs = (refs.data + ref).toWrappedList())
|
||||
is TextReference.PluralRes,
|
||||
is TextReference.Res,
|
||||
is TextReference.Str,
|
||||
-> TextReference.Combined(refs = wrappedList(this, ref))
|
||||
}
|
||||
}
|
||||
|
|
@ -7,4 +7,8 @@ import androidx.compose.runtime.Immutable
|
|||
*/
|
||||
@JvmInline
|
||||
@Immutable
|
||||
value class WrappedList<T>(val data: List<T>) : List<T> by data
|
||||
value class WrappedList<T>(val data: List<T>) : List<T> by data
|
||||
|
||||
fun <T> List<T>.toWrappedList(): WrappedList<T> = WrappedList(data = this)
|
||||
|
||||
fun <T> wrappedList(vararg elements: T): WrappedList<T> = WrappedList(data = listOf(*elements))
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.core.ui.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
abstract class ComposeActivity<ScreenState> : AppCompatActivity(), ComposeScreen<ScreenState> {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(createComposeView(context = this))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.core.ui.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
abstract class ComposeFragment<ScreenState> : Fragment(), ComposeScreen<ScreenState> {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
return createComposeView(inflater.context)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package com.tangem.core.ui.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
internal interface ComposeScreen<ScreenState> {
|
||||
fun createComposeView(context: Context): View {
|
||||
return ComposeView(context).apply {
|
||||
setContent {
|
||||
TangemTheme {
|
||||
val backgroundColor = TangemTheme.colors.background.primary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(
|
||||
color = backgroundColor,
|
||||
)
|
||||
}
|
||||
|
||||
ScreenContent(
|
||||
state = provideState().value,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = backgroundColor),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
fun provideState(): State<ScreenState>
|
||||
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
fun ScreenContent(state: ScreenState, modifier: Modifier)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.core.ui.screen
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
|
||||
/**
|
||||
* An abstract base class for activities that use Compose for UI rendering.
|
||||
* Extends [ComponentActivity] and implements [ComposeScreen] interface.
|
||||
*/
|
||||
abstract class ComposeActivity : ComponentActivity(), ComposeScreen {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(createComposeView(context = this))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.core.ui.fragments
|
||||
package com.tangem.core.ui.screen
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -10,20 +9,46 @@ import androidx.annotation.FloatRange
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
abstract class ComposeBottomSheetFragment<ScreenState> : BottomSheetDialogFragment(), ComposeScreen<ScreenState> {
|
||||
/**
|
||||
* An abstract base class for bottom sheet dialogs that use Compose for UI rendering.
|
||||
* Extends [BottomSheetDialogFragment] and implements [ComposeScreen] interface.
|
||||
*/
|
||||
abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment(), ComposeScreen {
|
||||
|
||||
/**
|
||||
* The initial state of the bottom sheet. Default is [BottomSheetBehavior.STATE_EXPANDED].
|
||||
*/
|
||||
open val initialBottomSheetState = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
/**
|
||||
* The fraction of the screen height that the bottom sheet should take when expanded.
|
||||
* Default is `null`, indicating that the height will be determined by the content.
|
||||
*/
|
||||
@FloatRange(from = 0.0, to = 1.0)
|
||||
open val expandedHeightFraction: Float? = null
|
||||
|
||||
override val screenModifier: Modifier
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = Modifier
|
||||
.fillMaxWidth()
|
||||
.let {
|
||||
if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it
|
||||
}
|
||||
.background(
|
||||
color = TangemTheme.colors.background.plain,
|
||||
shape = TangemTheme.shapes.bottomSheet,
|
||||
)
|
||||
|
||||
override fun getTheme(): Int = R.style.AppTheme_TransparentBottomSheetDialog
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
|
|
@ -40,25 +65,4 @@ abstract class ComposeBottomSheetFragment<ScreenState> : BottomSheetDialogFragme
|
|||
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun createComposeView(context: Context): View {
|
||||
return ComposeView(context).apply {
|
||||
setContent {
|
||||
TangemTheme {
|
||||
ScreenContent(
|
||||
state = provideState().value,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.let {
|
||||
if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it
|
||||
}
|
||||
.background(
|
||||
color = TangemTheme.colors.background.plain,
|
||||
shape = TangemTheme.shapes.bottomSheet,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.core.ui.screen
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
/**
|
||||
* An abstract base class for fragments that use Compose for UI rendering.
|
||||
* Extends [Fragment] and implements [ComposeScreen] interface.
|
||||
*/
|
||||
abstract class ComposeFragment : Fragment(), ComposeScreen {
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
val isTransitionsInflated = TransitionInflater.from(requireContext()).inflateTransitions()
|
||||
|
||||
return createComposeView(inflater.context).also {
|
||||
it.isTransitionGroup = isTransitionsInflated
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates transitions for the fragment. Override this method to customize
|
||||
* enter and exit transitions for the fragment.
|
||||
*
|
||||
* @return `true` if transitions were inflated; `false` otherwise.
|
||||
*/
|
||||
protected open fun TransitionInflater.inflateTransitions(): Boolean {
|
||||
enterTransition = inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflateTransition(R.transition.fade)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.core.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
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.res.TangemTheme
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
|
||||
/**
|
||||
* Interface representing a Compose screen with common theming and content composition properties.
|
||||
*
|
||||
* This interface defines properties and functions that allow a Compose screen to manage its theme,
|
||||
* background color, and screen modifier. It also provides a composable function to define the content
|
||||
* of the screen.
|
||||
*/
|
||||
internal interface ComposeScreen {
|
||||
|
||||
/**
|
||||
* The holder for managing the current application theme mode.
|
||||
*/
|
||||
val appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
/**
|
||||
* The screen modifier.
|
||||
*/
|
||||
val screenModifier: Modifier
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = Modifier.fillMaxSize()
|
||||
|
||||
/**
|
||||
* Composable function to define the content of the screen.
|
||||
*
|
||||
* @param modifier The modifier to apply to the screen content.
|
||||
*/
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
fun ScreenContent(modifier: Modifier)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [ComposeView] with the defined content for the Compose screen.
|
||||
*
|
||||
* @param context The context.
|
||||
* @return A [ComposeView] instance with the defined screen content.
|
||||
*/
|
||||
internal fun ComposeScreen.createComposeView(context: Context): ComposeView {
|
||||
return ComposeView(context).apply {
|
||||
setContent {
|
||||
val appThemeMode by appThemeModeHolder.appThemeMode
|
||||
|
||||
TangemTheme(isDark = shouldUseDarkTheme(appThemeMode)) {
|
||||
ScreenContent(modifier = screenModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the dark theme should be used based on the given [AppThemeMode].
|
||||
*
|
||||
* @param appThemeMode The application theme mode.
|
||||
* @return `true` if the dark theme should be used, `false` otherwise.
|
||||
*/
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun shouldUseDarkTheme(appThemeMode: AppThemeMode): Boolean {
|
||||
return when (appThemeMode) {
|
||||
AppThemeMode.FORCE_DARK -> true
|
||||
AppThemeMode.FORCE_LIGHT -> false
|
||||
AppThemeMode.FOLLOW_SYSTEM -> isSystemInDarkTheme()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.core.ui.theme
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
|
||||
/**
|
||||
* Representing a holder for the application theme mode.
|
||||
*/
|
||||
@Stable
|
||||
interface AppThemeModeHolder {
|
||||
|
||||
/**
|
||||
* A [State] representing the current application theme mode.
|
||||
*/
|
||||
val appThemeMode: State<AppThemeMode>
|
||||
}
|
||||
|
|
@ -12,22 +12,14 @@ object BigDecimalFormatter {
|
|||
|
||||
private const val TEMP_CURRENCY_CODE = "USD"
|
||||
|
||||
fun formatCryptoAmount(
|
||||
cryptoAmount: BigDecimal,
|
||||
cryptoCurrency: String,
|
||||
decimals: Int,
|
||||
locale: Locale = Locale.getDefault(),
|
||||
): String {
|
||||
val formatterCurrency = getCurrency(cryptoCurrency)
|
||||
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
|
||||
currency = formatterCurrency
|
||||
fun formatCryptoAmount(cryptoAmount: BigDecimal, cryptoCurrency: String, decimals: Int): String {
|
||||
val formatter = NumberFormat.getNumberInstance().apply {
|
||||
maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8)
|
||||
minimumFractionDigits = 2
|
||||
roundingMode = RoundingMode.DOWN
|
||||
}
|
||||
|
||||
return formatter.format(cryptoAmount)
|
||||
.replace(formatterCurrency.getSymbol(locale), cryptoCurrency)
|
||||
return formatter.format(cryptoAmount) + "\u2009$cryptoCurrency"
|
||||
}
|
||||
|
||||
fun formatFiatAmount(
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
|
@ -10,9 +10,9 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEmpty
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.joda.time.Duration
|
||||
import timber.log.Timber
|
||||
|
|
@ -27,11 +27,18 @@ internal class DefaultAppCurrencyRepository(
|
|||
|
||||
private val appCurrencyConverter = AppCurrencyConverter()
|
||||
|
||||
override fun getSelectedAppCurrency(): Flow<AppCurrency> {
|
||||
return selectedAppCurrencyStore.get()
|
||||
.onEmpty { fetchDefaultAppCurrency() }
|
||||
.map(appCurrencyConverter::convert)
|
||||
.flowOn(dispatchers.io)
|
||||
override fun getSelectedAppCurrency(): Flow<AppCurrency> = channelFlow {
|
||||
launch(dispatchers.io) {
|
||||
selectedAppCurrencyStore.get()
|
||||
.map(appCurrencyConverter::convert)
|
||||
.collect(::send)
|
||||
}
|
||||
|
||||
launch(dispatchers.io) {
|
||||
if (selectedAppCurrencyStore.isEmpty()) {
|
||||
fetchDefaultAppCurrency()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableAppCurrencies(): List<AppCurrency> {
|
||||
|
|
|
|||
1
data/app-theme/.gitignore
vendored
Normal file
1
data/app-theme/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
34
data/app-theme/build.gradle.kts
Normal file
34
data/app-theme/build.gradle.kts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.apptheme"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.appTheme)
|
||||
implementation(projects.domain.appTheme.models)
|
||||
|
||||
/** Project - Data */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Project - Utils */
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.core)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Other */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.jodatime)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.data.apptheme
|
||||
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
internal class MockAppThemeModeRepository : AppThemeModeRepository {
|
||||
|
||||
private val appThemeModeFlow = MutableStateFlow(AppThemeMode.DEFAULT)
|
||||
|
||||
override fun getAppThemeMode(): Flow<AppThemeMode> {
|
||||
return appThemeModeFlow
|
||||
}
|
||||
|
||||
override suspend fun changeAppThemeMode(mode: AppThemeMode) {
|
||||
appThemeModeFlow.value = mode
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.data.apptheme.di
|
||||
|
||||
import com.tangem.data.apptheme.MockAppThemeModeRepository
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
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 AppThemeModeDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppThemeModeRepository(): AppThemeModeRepository {
|
||||
return MockAppThemeModeRepository()
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,8 @@ internal class DefaultSettingsRepository(
|
|||
preferencesDataSource.appRatingLaunchObserver.isReadyToShow()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun shouldShowSaveUserWalletScreen(): Boolean {
|
||||
return withContext(dispatchers.io) { preferencesDataSource.shouldShowSaveUserWalletScreen }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -164,7 +164,7 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
tangemTechApi.saveUserTokens(userWallet.walletId.stringValue, response)
|
||||
} else {
|
||||
throw error
|
||||
Timber.e(error, "Unable to fetch currencies for: ${userWallet.walletId}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ internal class DefaultNetworksRepository(
|
|||
private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, networkId: Network.ID) {
|
||||
val currencies = getCurrencies(userWalletId)
|
||||
.asSequence()
|
||||
.filter { it.networkId == networkId }
|
||||
.filter { it.network.id == networkId }
|
||||
|
||||
val result = walletManagersFacade.update(
|
||||
userWalletId = userWalletId,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.data.tokens.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token as SdkToken
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import timber.log.Timber
|
||||
import com.tangem.blockchain.common.Token as SdkToken
|
||||
|
||||
// FIXME: Make internal
|
||||
class CryptoCurrencyFactory {
|
||||
|
||||
fun createToken(
|
||||
|
|
@ -19,9 +20,10 @@ class CryptoCurrencyFactory {
|
|||
}
|
||||
|
||||
val id = getTokenId(blockchain, sdkToken)
|
||||
|
||||
return CryptoCurrency.Token(
|
||||
id = id,
|
||||
networkId = getNetworkId(blockchain),
|
||||
network = getNetwork(blockchain) ?: return null,
|
||||
name = sdkToken.name,
|
||||
symbol = sdkToken.symbol,
|
||||
iconUrl = getTokenIconUrl(blockchain, sdkToken),
|
||||
|
|
@ -29,8 +31,6 @@ class CryptoCurrencyFactory {
|
|||
isCustom = isCustomToken(id),
|
||||
contractAddress = sdkToken.contractAddress,
|
||||
derivationPath = getDerivationPath(blockchain, derivationStyleProvider),
|
||||
blockchainName = blockchain.fullName,
|
||||
standardType = getTokenStandardType(blockchain, sdkToken),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ class CryptoCurrencyFactory {
|
|||
|
||||
return CryptoCurrency.Coin(
|
||||
id = getCoinId(blockchain),
|
||||
networkId = getNetworkId(blockchain),
|
||||
network = getNetwork(blockchain) ?: return null,
|
||||
name = blockchain.fullName,
|
||||
symbol = blockchain.currency,
|
||||
iconUrl = getCoinIconUrl(blockchain),
|
||||
|
|
|
|||
|
|
@ -3,22 +3,13 @@ package com.tangem.data.tokens.utils
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.utils.converter.Converter
|
||||
import timber.log.Timber
|
||||
|
||||
internal class NetworkConverter : Converter<Network.ID, Network?> {
|
||||
|
||||
override fun convert(value: Network.ID): Network? {
|
||||
val blockchain = Blockchain.fromId(value.value)
|
||||
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to convert Unknown blockchain to the domain network model")
|
||||
return null
|
||||
}
|
||||
|
||||
return Network(
|
||||
id = value,
|
||||
name = blockchain.fullName,
|
||||
)
|
||||
return getNetwork(blockchain)
|
||||
}
|
||||
|
||||
override fun convertList(input: Collection<Network.ID>): List<Network> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.data.tokens.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import timber.log.Timber
|
||||
|
||||
internal fun getNetwork(blockchain: Blockchain): Network? {
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to convert Unknown blockchain to the domain network model")
|
||||
return null
|
||||
}
|
||||
|
||||
return Network(
|
||||
id = Network.ID(blockchain.id),
|
||||
name = blockchain.fullName,
|
||||
isTestnet = blockchain.isTestnet(),
|
||||
standardType = getNetworkStandardType(blockchain),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNetworkStandardType(blockchain: Blockchain): Network.StandardType {
|
||||
return when (blockchain) {
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> Network.StandardType.ERC20
|
||||
Blockchain.BSC, Blockchain.BSCTestnet -> Network.StandardType.BEP20
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> Network.StandardType.BEP2
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> Network.StandardType.TRC20
|
||||
else -> Network.StandardType.Unspecified(blockchain.name)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
package com.tangem.data.tokens.utils
|
||||
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.model.PendingTransaction
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction
|
||||
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class NetworkStatusFactory {
|
||||
|
|
@ -19,10 +23,18 @@ internal class NetworkStatusFactory {
|
|||
value = when (result) {
|
||||
is UpdateWalletManagerResult.MissedDerivation -> NetworkStatus.MissedDerivation
|
||||
is UpdateWalletManagerResult.Unreachable -> NetworkStatus.Unreachable
|
||||
is UpdateWalletManagerResult.NoAccount -> NetworkStatus.NoAccount(result.amountToCreateAccount)
|
||||
is UpdateWalletManagerResult.NoAccount -> NetworkStatus.NoAccount(
|
||||
address = getNetworkAddress(result.defaultAddress, result.addresses),
|
||||
amountToCreateAccount = result.amountToCreateAccount,
|
||||
)
|
||||
is UpdateWalletManagerResult.Verified -> NetworkStatus.Verified(
|
||||
amounts = formatAmounts(result.tokensAmounts, currencies),
|
||||
hasTransactionsInProgress = result.hasTransactionsInProgress,
|
||||
address = getNetworkAddress(result.defaultAddress, result.addresses),
|
||||
amounts = formatAmounts(result.currenciesAmounts, currencies),
|
||||
pendingTransactions = formatTransactions(
|
||||
networksAddresses = result.addresses,
|
||||
transactions = result.currentTransactions,
|
||||
currencies = currencies,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -39,13 +51,91 @@ internal class NetworkStatusFactory {
|
|||
is CryptoCurrencyAmount.Coin -> currencies.singleOrNull { it is CryptoCurrency.Coin }
|
||||
is CryptoCurrencyAmount.Token -> currencies.firstOrNull {
|
||||
it is CryptoCurrency.Token &&
|
||||
it.id.rawCurrencyId == amount.id &&
|
||||
it.id.rawCurrencyId == amount.tokenId &&
|
||||
it.contractAddress == amount.tokenContractAddress
|
||||
}
|
||||
}
|
||||
|
||||
currency?.id?.let { it to amount.value }
|
||||
if (currency == null) {
|
||||
Timber.e("Unable to find cryptocurrency for amount: $amount")
|
||||
null
|
||||
} else {
|
||||
currency.id to amount.value
|
||||
}
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
private fun formatTransactions(
|
||||
networksAddresses: Set<String>,
|
||||
transactions: Set<CryptoCurrencyTransaction>,
|
||||
currencies: Set<CryptoCurrency>,
|
||||
): Map<CryptoCurrency.ID, Set<PendingTransaction>> {
|
||||
if (transactions.isEmpty()) return emptyMap()
|
||||
|
||||
return currencies
|
||||
.asSequence()
|
||||
.map { currency ->
|
||||
val currencyTransactions = when (currency) {
|
||||
is CryptoCurrency.Coin -> transactions.filterTo(hashSetOf()) { transaction ->
|
||||
transaction is CryptoCurrencyTransaction.Coin
|
||||
}
|
||||
is CryptoCurrency.Token -> transactions.filterTo(hashSetOf()) { transaction ->
|
||||
transaction is CryptoCurrencyTransaction.Token &&
|
||||
transaction.tokenId == currency.id.rawCurrencyId &&
|
||||
transaction.tokenContractAddress == currency.contractAddress
|
||||
}
|
||||
}
|
||||
|
||||
currency.id to createCurrentTransactions(networksAddresses, currencyTransactions)
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
private fun createCurrentTransactions(
|
||||
networksAddresses: Set<String>,
|
||||
transactions: Set<CryptoCurrencyTransaction>,
|
||||
): Set<PendingTransaction> {
|
||||
return transactions.mapNotNullTo(hashSetOf()) { createCurrentTransaction(networksAddresses, it) }
|
||||
}
|
||||
|
||||
private fun createCurrentTransaction(
|
||||
networksAddresses: Set<String>,
|
||||
transaction: CryptoCurrencyTransaction,
|
||||
): PendingTransaction? {
|
||||
val direction = when {
|
||||
transaction.toAddress in networksAddresses -> PendingTransaction.Direction.Incoming(
|
||||
fromAddress = transaction.fromAddress,
|
||||
)
|
||||
transaction.fromAddress in networksAddresses -> PendingTransaction.Direction.Outgoing(
|
||||
toAddress = transaction.toAddress,
|
||||
)
|
||||
else -> {
|
||||
Timber.e(
|
||||
"""
|
||||
Unable to find transaction direction
|
||||
|- To address: ${transaction.toAddress}
|
||||
|- From address: ${transaction.fromAddress}
|
||||
|- Network addresses: $networksAddresses
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return PendingTransaction(
|
||||
amount = transaction.amount,
|
||||
direction = direction,
|
||||
sentAt = transaction.sentAt,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNetworkAddress(defaultAddress: String, availableAddresses: Set<String>): NetworkAddress {
|
||||
return if (availableAddresses.size != 1) {
|
||||
NetworkAddress.Selectable(defaultAddress, availableAddresses)
|
||||
} else {
|
||||
NetworkAddress.Single(defaultAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,10 +59,10 @@ internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun createCoin(blockchain: Blockchain, responseToken: UserTokensResponse.Token): CryptoCurrency.Coin {
|
||||
private fun createCoin(blockchain: Blockchain, responseToken: UserTokensResponse.Token): CryptoCurrency.Coin? {
|
||||
return CryptoCurrency.Coin(
|
||||
id = getCoinId(blockchain),
|
||||
networkId = getNetworkId(blockchain),
|
||||
network = getNetwork(blockchain) ?: return null,
|
||||
name = responseToken.name,
|
||||
symbol = responseToken.symbol,
|
||||
decimals = responseToken.decimals,
|
||||
|
|
@ -71,12 +71,12 @@ internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createToken(blockchain: Blockchain, sdkToken: Token, derivationPath: String?): CryptoCurrency.Token {
|
||||
private fun createToken(blockchain: Blockchain, sdkToken: Token, derivationPath: String?): CryptoCurrency.Token? {
|
||||
val id = getTokenId(blockchain, sdkToken)
|
||||
|
||||
return CryptoCurrency.Token(
|
||||
id = id,
|
||||
networkId = getNetworkId(blockchain),
|
||||
network = getNetwork(blockchain) ?: return null,
|
||||
name = sdkToken.name,
|
||||
symbol = sdkToken.symbol,
|
||||
decimals = sdkToken.decimals,
|
||||
|
|
@ -84,8 +84,6 @@ internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
|
|||
iconUrl = getTokenIconUrl(blockchain, sdkToken),
|
||||
contractAddress = sdkToken.contractAddress,
|
||||
isCustom = isCustomToken(id),
|
||||
blockchainName = blockchain.fullName,
|
||||
standardType = getTokenStandardType(blockchain, sdkToken),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import com.tangem.blockchain.common.IconsUtil
|
|||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.toCoinId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency.ID
|
||||
import com.tangem.domain.tokens.models.Network
|
||||
import com.tangem.blockchain.common.Token as SdkToken
|
||||
|
|
@ -31,12 +30,6 @@ internal fun getBlockchain(networkId: Network.ID): Blockchain {
|
|||
return Blockchain.fromId(networkId.value)
|
||||
}
|
||||
|
||||
internal fun getNetworkId(blockchain: Blockchain): Network.ID {
|
||||
val value = blockchain.id
|
||||
|
||||
return Network.ID(value)
|
||||
}
|
||||
|
||||
internal fun getCoinId(blockchain: Blockchain): ID {
|
||||
return getTokenOrCoinId(blockchain, token = null)
|
||||
}
|
||||
|
|
@ -45,16 +38,6 @@ internal fun getTokenId(blockchain: Blockchain, token: SdkToken): ID {
|
|||
return getTokenOrCoinId(blockchain, token)
|
||||
}
|
||||
|
||||
internal fun getTokenStandardType(blockchain: Blockchain, token: SdkToken): CryptoCurrency.StandardType {
|
||||
return when (blockchain) {
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> CryptoCurrency.StandardType.ERC20
|
||||
Blockchain.BSC, Blockchain.BSCTestnet -> CryptoCurrency.StandardType.BEP20
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> CryptoCurrency.StandardType.BEP2
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> CryptoCurrency.StandardType.TRC20
|
||||
else -> CryptoCurrency.StandardType.Unspecified(token.name)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getTokenIconUrl(blockchain: Blockchain, token: SdkToken): String? {
|
||||
val tokenId = token.id
|
||||
|
||||
|
|
@ -83,7 +66,7 @@ private fun getTokenOrCoinId(blockchain: Blockchain, token: SdkToken?): ID {
|
|||
else -> TOKEN_ID_PREFIX to CurrencyIdSuffix(rawId = sdkTokenId)
|
||||
}
|
||||
|
||||
return ID(prefix, getNetworkId(blockchain), suffix)
|
||||
return ID(prefix, Network.ID(blockchain.id), suffix)
|
||||
}
|
||||
|
||||
private fun getTokenIconUrlFromDefaultHost(tokenId: String): String {
|
||||
|
|
|
|||
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