Updated on 2026-08-14
This commit is contained in:
commit
0e9ddada09
92 changed files with 2244 additions and 682 deletions
|
|
@ -10,7 +10,7 @@ import com.tangem.core.navigation.url.UrlOpener
|
|||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
|
|
@ -76,7 +76,7 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getBalanceHidingRepository(): BalanceHidingRepository
|
||||
|
||||
fun getUserTokensStore(): UserTokensStore
|
||||
fun getAppPreferencesStore(): AppPreferencesStore
|
||||
|
||||
fun getGetAppThemeModeUseCase(): GetAppThemeModeUseCase
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import com.tangem.datasource.config.ConfigManager
|
|||
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
|
|
@ -126,8 +126,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val balanceHidingRepository: BalanceHidingRepository
|
||||
get() = entryPoint.getBalanceHidingRepository()
|
||||
|
||||
private val userTokensStore: UserTokensStore
|
||||
get() = entryPoint.getUserTokensStore()
|
||||
private val appPreferencesStore: AppPreferencesStore
|
||||
get() = entryPoint.getAppPreferencesStore()
|
||||
|
||||
val getAppThemeModeUseCase: GetAppThemeModeUseCase
|
||||
get() = entryPoint.getGetAppThemeModeUseCase()
|
||||
|
|
@ -228,7 +228,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
}
|
||||
|
||||
derivationsFinder = DerivationsFinder(
|
||||
newTokensStore = userTokensStore,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
)
|
||||
appStateHolder.mainStore = store
|
||||
|
|
|
|||
|
|
@ -158,8 +158,9 @@ internal object TokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
): FetchCurrencyStatusUseCase {
|
||||
return FetchCurrencyStatusUseCase(currenciesRepository, networksRepository, quotesRepository)
|
||||
return FetchCurrencyStatusUseCase(currenciesRepository, networksRepository, quotesRepository, stakingRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
@ -22,7 +25,7 @@ internal data class BlockchainToDerive(
|
|||
|
||||
// FIXME: May be move to DI, currently unnecessary
|
||||
internal class DerivationsFinder(
|
||||
private val newTokensStore: UserTokensStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
|
|
@ -64,7 +67,9 @@ internal class DerivationsFinder(
|
|||
}
|
||||
|
||||
private suspend fun getBlockchains(userWalletId: UserWalletId): MutableSet<BlockchainToDerive> {
|
||||
val responseTokens = newTokensStore.getSyncOrNull(userWalletId)
|
||||
val responseTokens = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
|
||||
)
|
||||
?.tokens
|
||||
?: return hashSetOf()
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.getBackupCardsCount
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
|
|
@ -173,13 +174,7 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
userWalletId = userWalletId,
|
||||
cardId = card.cardId,
|
||||
isActiveBackupStatus = card.backupStatus?.isActive == true,
|
||||
backupCardsCount = when (val status = card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount
|
||||
is CardDTO.BackupStatus.CardLinked,
|
||||
CardDTO.BackupStatus.NoBackup,
|
||||
null,
|
||||
-> 0
|
||||
},
|
||||
backupCardsCount = scanResponse.getBackupCardsCount() ?: 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ dependencies {
|
|||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.navigation)
|
||||
implementation(deps.compose.navigation.hilt)
|
||||
implementation(deps.compose.coil)
|
||||
|
||||
/** Deps */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
package com.tangem.common.ui.userwallet
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.coil.RotationTransformation
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun UserWalletItem(state: UserWalletItemUM, modifier: Modifier = Modifier) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
onClick = state.onClick,
|
||||
enabled = state.isEnabled,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size68)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
CardImage(imageUrl = state.imageUrl)
|
||||
NameAndInfo(
|
||||
modifier = Modifier.weight(1f),
|
||||
name = state.name,
|
||||
information = state.information,
|
||||
)
|
||||
|
||||
when (state.endIcon) {
|
||||
UserWalletItemUM.EndIcon.None -> {}
|
||||
UserWalletItemUM.EndIcon.Arrow -> {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
UserWalletItemUM.EndIcon.Checkmark -> {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_check_24),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NameAndInfo(name: TextReference, information: TextReference, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.heightIn(min = TangemTheme.dimens.size40),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Text(
|
||||
text = name.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
AnimatedContent(
|
||||
targetState = information.resolveReference(),
|
||||
label = "User wallet information",
|
||||
) { information ->
|
||||
Text(
|
||||
text = information,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CardImage(imageUrl: String, modifier: Modifier = Modifier) {
|
||||
val imageModifier = modifier
|
||||
.width(TangemTheme.dimens.size24)
|
||||
.height(TangemTheme.dimens.size36)
|
||||
.clip(TangemTheme.shapes.roundedCornersSmall)
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = imageModifier,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.transformations(RotationTransformation(angle = 90f))
|
||||
.size(
|
||||
width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() },
|
||||
height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() },
|
||||
)
|
||||
.data(imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(enable = false)
|
||||
.build(),
|
||||
loading = {
|
||||
RectangleShimmer(
|
||||
modifier = imageModifier,
|
||||
radius = TangemTheme.dimens.size2,
|
||||
)
|
||||
},
|
||||
error = {
|
||||
Image(
|
||||
modifier = imageModifier,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
val list = persistentListOf(
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_1".encodeToByteArray()),
|
||||
name = stringReference("My Wallet"),
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_2".encodeToByteArray()),
|
||||
name = stringReference("Old wallet"),
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
endIcon = UserWalletItemUM.EndIcon.Arrow,
|
||||
),
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_3".encodeToByteArray()),
|
||||
name = stringReference("Multi Card"),
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
imageUrl = "",
|
||||
isEnabled = false,
|
||||
endIcon = UserWalletItemUM.EndIcon.Checkmark,
|
||||
onClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
Column {
|
||||
list.fastForEach { userWalletItemUM ->
|
||||
UserWalletItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = userWalletItemUM,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInformation(cardCount: Int, totalBalance: String): TextReference {
|
||||
val t1 = TextReference.PluralRes(
|
||||
id = R.plurals.card_label_card_count,
|
||||
count = cardCount,
|
||||
formatArgs = wrappedList(cardCount),
|
||||
)
|
||||
val divider = stringReference(value = " • ")
|
||||
val t2 = stringReference(totalBalance)
|
||||
|
||||
return TextReference.Combined(wrappedList(t1, divider, t2))
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.common.ui.userwallet.state
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import javax.annotation.concurrent.Immutable
|
||||
|
||||
@Immutable
|
||||
data class UserWalletItemUM(
|
||||
val id: UserWalletId,
|
||||
val name: TextReference,
|
||||
val information: TextReference,
|
||||
val imageUrl: String,
|
||||
val isEnabled: Boolean,
|
||||
val endIcon: EndIcon = EndIcon.None,
|
||||
val onClick: () -> Unit,
|
||||
) {
|
||||
enum class EndIcon {
|
||||
None,
|
||||
Arrow,
|
||||
Checkmark,
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ interface StakeKitApi {
|
|||
@POST("yields/balances")
|
||||
suspend fun getMultipleYieldBalances(
|
||||
@Body body: List<YieldBalanceRequestBody>,
|
||||
): ApiResponse<List<YieldBalanceWrapperDTO>>
|
||||
): ApiResponse<Set<YieldBalanceWrapperDTO>>
|
||||
|
||||
@POST("yields/{integrationId}/balances")
|
||||
suspend fun getSingleYieldBalance(
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.AppPreferencesUserTokensStore
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.token.UserTokensStoreMigrationRunner
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
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 UserTokensStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserTokensStore(
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
userTokensStoreMigrationRunner: UserTokensStoreMigrationRunner,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserTokensStore {
|
||||
return AppPreferencesUserTokensStore(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
userTokensStoreMigrationRunner = userTokensStoreMigrationRunner,
|
||||
userWalletsStore = userWalletsStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObject
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Implementation of [UserTokensStore] that based on [appPreferencesStore]
|
||||
*
|
||||
* @property appPreferencesStore application preference store
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AppPreferencesUserTokensStore(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val userTokensStoreMigrationRunner: UserTokensStoreMigrationRunner,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : UserTokensStore {
|
||||
|
||||
init {
|
||||
runUserTokensMigrations()
|
||||
}
|
||||
|
||||
override fun get(key: UserWalletId): Flow<UserTokensResponse> {
|
||||
return appPreferencesStore
|
||||
.getObject<UserTokensResponse>(PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue))
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(key: UserWalletId): UserTokensResponse? {
|
||||
return appPreferencesStore.getObjectSyncOrNull(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun store(key: UserWalletId, value: UserTokensResponse) {
|
||||
appPreferencesStore.storeObject(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue),
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: delete in 5.15 (Mobile Sprint 161) [REDACTED_JIRA]
|
||||
private fun runUserTokensMigrations() {
|
||||
userWalletsStore.userWallets
|
||||
.filter { it.isNotEmpty() }
|
||||
.take(1)
|
||||
.onEach { userWallets ->
|
||||
userTokensStoreMigrationRunner.run(ids = userWallets.map { it.walletId.stringValue })
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(CoroutineScope(dispatchers.io))
|
||||
}
|
||||
}
|
||||
|
|
@ -3,49 +3,53 @@ package com.tangem.datasource.local.token
|
|||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
internal class DefaultStakingBalanceStore(
|
||||
private val dataStore: StringKeyDataStore<List<YieldBalanceWrapperDTO>>,
|
||||
private val dataStore: StringKeyDataStore<Set<YieldBalanceWrapperDTO>>,
|
||||
) : StakingBalanceStore {
|
||||
|
||||
override fun get(): Flow<List<YieldBalanceWrapperDTO>> {
|
||||
return dataStore.get(STAKING_BALANCE_KEY)
|
||||
private val mutex = Mutex()
|
||||
|
||||
override fun get(userWalletId: UserWalletId): Flow<Set<YieldBalanceWrapperDTO>> {
|
||||
return dataStore.get(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(): List<YieldBalanceWrapperDTO>? {
|
||||
return dataStore.getSyncOrNull(STAKING_BALANCE_KEY)
|
||||
override suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalanceWrapperDTO>? {
|
||||
return dataStore.getSyncOrNull(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
override suspend fun store(items: List<YieldBalanceWrapperDTO>) {
|
||||
return dataStore.store(STAKING_BALANCE_KEY, items)
|
||||
override suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>) {
|
||||
mutex.withLock {
|
||||
dataStore.store(userWalletId.stringValue, items)
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(integrationId: String): Flow<List<BalanceDTO>> {
|
||||
return dataStore.get(STAKING_BALANCE_KEY)
|
||||
override fun get(userWalletId: UserWalletId, integrationId: String): Flow<List<BalanceDTO>> {
|
||||
return dataStore.get(userWalletId.stringValue)
|
||||
.map { balances ->
|
||||
balances.filter { it.integrationId == integrationId }
|
||||
.flatMap { it.balances }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(integrationId: String): List<BalanceDTO>? {
|
||||
return dataStore.getSyncOrNull(STAKING_BALANCE_KEY)
|
||||
override suspend fun getSyncOrNull(userWalletId: UserWalletId, integrationId: String): List<BalanceDTO>? {
|
||||
return dataStore.getSyncOrNull(userWalletId.stringValue)
|
||||
?.firstOrNull { it.integrationId == integrationId }?.balances
|
||||
}
|
||||
|
||||
override suspend fun store(integrationId: String, item: YieldBalanceWrapperDTO) {
|
||||
val balances = dataStore.getSyncOrNull(STAKING_BALANCE_KEY)
|
||||
?.toMutableList()
|
||||
?.addOrReplace(item) { item.integrationId == integrationId }
|
||||
?: listOf(item)
|
||||
override suspend fun store(userWalletId: UserWalletId, integrationId: String, item: YieldBalanceWrapperDTO) {
|
||||
mutex.withLock {
|
||||
val balances = dataStore.getSyncOrNull(userWalletId.stringValue)
|
||||
?.addOrReplace(item) { it.integrationId == integrationId }
|
||||
?: setOf(item)
|
||||
|
||||
return dataStore.store(STAKING_BALANCE_KEY, balances)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STAKING_BALANCE_KEY = "STAKING_BALANCE_KEY"
|
||||
dataStore.store(userWalletId.stringValue, balances)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,19 +2,20 @@ package com.tangem.datasource.local.token
|
|||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface StakingBalanceStore {
|
||||
|
||||
fun get(): Flow<List<YieldBalanceWrapperDTO>>
|
||||
fun get(userWalletId: UserWalletId): Flow<Set<YieldBalanceWrapperDTO>>
|
||||
|
||||
suspend fun getSyncOrNull(): List<YieldBalanceWrapperDTO>?
|
||||
suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalanceWrapperDTO>?
|
||||
|
||||
suspend fun store(items: List<YieldBalanceWrapperDTO>)
|
||||
suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>)
|
||||
|
||||
fun get(integrationId: String): Flow<List<BalanceDTO>>
|
||||
fun get(userWalletId: UserWalletId, integrationId: String): Flow<List<BalanceDTO>>
|
||||
|
||||
suspend fun getSyncOrNull(integrationId: String): List<BalanceDTO>?
|
||||
suspend fun getSyncOrNull(userWalletId: UserWalletId, integrationId: String): List<BalanceDTO>?
|
||||
|
||||
suspend fun store(integrationId: String, item: YieldBalanceWrapperDTO)
|
||||
suspend fun store(userWalletId: UserWalletId, integrationId: String, item: YieldBalanceWrapperDTO)
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Deprecated(
|
||||
message = "Use AppPreferencesStore",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "AppPreferencesStore",
|
||||
imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"),
|
||||
),
|
||||
level = DeprecationLevel.WARNING,
|
||||
)
|
||||
interface UserTokensStore {
|
||||
|
||||
@Deprecated(
|
||||
message = "Use getObject",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "appPreferencesStore.getObject(userWalletId)",
|
||||
imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"),
|
||||
),
|
||||
level = DeprecationLevel.WARNING,
|
||||
)
|
||||
fun get(key: UserWalletId): Flow<UserTokensResponse>
|
||||
|
||||
@Deprecated(
|
||||
message = "Use getObjectSyncOrNull",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "appPreferencesStore.getObjectSyncOrNull(userWalletId)",
|
||||
imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"),
|
||||
),
|
||||
level = DeprecationLevel.WARNING,
|
||||
)
|
||||
suspend fun getSyncOrNull(key: UserWalletId): UserTokensResponse?
|
||||
|
||||
@Deprecated(
|
||||
message = "Use storeObject",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "appPreferencesStore.storeObject(userWalletId, response)",
|
||||
imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"),
|
||||
),
|
||||
level = DeprecationLevel.WARNING,
|
||||
)
|
||||
suspend fun store(key: UserWalletId, value: UserTokensResponse)
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import androidx.datastore.core.DataMigration
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
|
||||
/**
|
||||
* Migration of saving [UserTokensResponse] from file to [AppPreferencesStore]
|
||||
*
|
||||
* @param userWalletId user wallet id
|
||||
* @param moshi moshi
|
||||
* @property fileReader file reader
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class UserTokensStoreMigration(
|
||||
userWalletId: String,
|
||||
moshi: Moshi,
|
||||
private val fileReader: FileReader,
|
||||
) : DataMigration<AppPreferencesStore> {
|
||||
|
||||
private val legacyFileName = "user_tokens_$userWalletId"
|
||||
private val keyName = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId)
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private val adapter = moshi.adapter<UserTokensResponse>()
|
||||
|
||||
override suspend fun shouldMigrate(currentData: AppPreferencesStore): Boolean = true
|
||||
|
||||
override suspend fun migrate(currentData: AppPreferencesStore): AppPreferencesStore {
|
||||
val currentKey = currentData.getObjectSyncOrNull<UserTokensResponse>(key = keyName)
|
||||
|
||||
if (currentKey != null) return currentData
|
||||
|
||||
val value = runCatching {
|
||||
val json = fileReader.readFile(legacyFileName)
|
||||
adapter.fromJson(json)
|
||||
}.getOrNull()
|
||||
|
||||
if (value != null) {
|
||||
currentData.storeObject(key = keyName, value = value)
|
||||
}
|
||||
|
||||
return currentData
|
||||
}
|
||||
|
||||
override suspend fun cleanUp() {
|
||||
fileReader.removeFile(legacyFileName)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Runner that launch migrations of saving user tokens store
|
||||
*
|
||||
* @property appPreferencesStore application preference store
|
||||
* @property fileReader file reader
|
||||
* @property moshi moshi
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
class UserTokensStoreMigrationRunner @Inject constructor(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val fileReader: FileReader,
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend fun run(ids: List<String>) {
|
||||
ids.forEach { id ->
|
||||
coroutineScope { run(id) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun run(id: String) {
|
||||
withContext(dispatchers.io) {
|
||||
val migration = UserTokensStoreMigration(
|
||||
userWalletId = id,
|
||||
moshi = moshi,
|
||||
fileReader = fileReader,
|
||||
)
|
||||
|
||||
migration.migrate(appPreferencesStore)
|
||||
|
||||
migration.cleanUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -835,6 +835,8 @@
|
|||
<string name="wallet_settings_title">Wallet-Einstellungen</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">Verwende %s oder scanne eine Karte, um den Zugriff auf deine Wallet freizuschalten.</string>
|
||||
<string name="warning_approval_in_progress_message">Die Genehmigung des Swaps ist im Gange und wird in Kürze abgeschlossen sein.</string>
|
||||
<string name="warning_approval_in_progress_title">Genehmigung in Arbeit</string>
|
||||
<string name="warning_backup_errors_message">Es scheint, dass die Aktivierung der Karte nicht korrekt abgeschlossen wurde. Dies kann an einem Problem mit dem NFC-Modul deines Gerätes oder an einem falschen Tippen der Karte auf dein Gerät liegen. Bitte wende dich an unser Support-Team, um Unterstützung zu erhalten.</string>
|
||||
<string name="warning_backup_errors_title">Aktivierungsfehler</string>
|
||||
<string name="warning_beacon_chain_retirement_content">Laut den Entwicklern des BNB-Netzes wird die Unterstützung für den BEP-2-Standard im Juni 2024 enden. Um den Verlust von Vermögenswerten mit diesem Standard zu vermeiden, konvertiere bitte in den BEP-20 Standard. Nutze gerne unseren Swap-Service, um sie auf das BNB Smart Chain Netzwerk zu übertragen.</string>
|
||||
|
|
@ -852,8 +854,6 @@
|
|||
<string name="warning_existential_deposit_title">Netzwerk erfordert eine Mindesteinzahlung</string>
|
||||
<string name="warning_express_active_transaction_message">Der Swap wird nach Abschluss der Transaktion %s verfügbar sein.</string>
|
||||
<string name="warning_express_active_transaction_title">Du hast aktive Transaktion</string>
|
||||
<string name="warning_express_approval_in_progress_message">Die Genehmigung des Swaps ist im Gange und wird in Kürze abgeschlossen sein.</string>
|
||||
<string name="warning_express_approval_in_progress_title">Genehmigung in Arbeit</string>
|
||||
<string name="warning_express_dust_message">Der Mindestbetrag für den Tausch beträgt %1$s. Bitte stelle sicher, dass der Restsaldo nach dem Swap nicht unter %2$s liegt.</string>
|
||||
<string name="warning_express_no_exchangeable_coins_description">Du hast keine %s Coins in deiner Liste</string>
|
||||
<string name="warning_express_no_exchangeable_coins_title">Keine Token zum Tauschen verfügbar</string>
|
||||
|
|
|
|||
|
|
@ -823,6 +823,8 @@
|
|||
<string name="wallet_settings_title">ウォレット設定</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">%sを使用するか、カードをスキャンしてウォレットにアクセスしてください</string>
|
||||
<string name="warning_approval_in_progress_message">スワップの承認は現在進行中で、まもなく完了する予定です。</string>
|
||||
<string name="warning_approval_in_progress_title">承認が進行中</string>
|
||||
<string name="warning_backup_errors_message">カードのアクティベーションが正しく完了しませんでした。デバイスの NFCモジュールに問題があるか、カードをデバイスに正しくタップしていないことが原因かもしれません。サポートチームにお問い合わせください。</string>
|
||||
<string name="warning_backup_errors_title">アクティベーションに失敗しました</string>
|
||||
<string name="warning_beacon_chain_retirement_content">BNBネットワーク開発者によると、BEP-2規格のサポートは2024年6月に終了します。この規格の資産を失わないために、BEP-20規格に変換してください。BNBスマートチェーンネットワークへ移行するには、Tangemのスワップサービスをご利用ください。</string>
|
||||
|
|
@ -840,8 +842,6 @@
|
|||
<string name="warning_existential_deposit_title">ネットワークには最低残高が必要です</string>
|
||||
<string name="warning_express_active_transaction_message">スワップは、%s の取引完了後に利用可能となります。</string>
|
||||
<string name="warning_express_active_transaction_title">アクティブな取引があります</string>
|
||||
<string name="warning_express_approval_in_progress_message">スワップの承認は現在進行中で、まもなく完了する予定です。</string>
|
||||
<string name="warning_express_approval_in_progress_title">承認が進行中</string>
|
||||
<string name="warning_express_dust_message">最低のスワップ金額は%1$s です。スワップ後の残金が%2$s を下回らないようにしてください。</string>
|
||||
<string name="warning_express_no_exchangeable_coins_description">あなたのリストには、交換可能な %s トークンがありません。</string>
|
||||
<string name="warning_express_no_exchangeable_coins_title">スワップ可能なトークンがありません</string>
|
||||
|
|
|
|||
|
|
@ -859,8 +859,6 @@
|
|||
<string name="warning_existential_deposit_title">Для работы с сетью необходим депозит</string>
|
||||
<string name="warning_express_active_transaction_message">Обмен будет доступен после завершения %s транзакции</string>
|
||||
<string name="warning_express_active_transaction_title">У вас есть активная транзакция</string>
|
||||
<string name="warning_express_approval_in_progress_message">Разрешение обмена в процессе и будет скоро завершено</string>
|
||||
<string name="warning_express_approval_in_progress_title">Разрешение в процессе</string>
|
||||
<string name="warning_express_dust_message">Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s.</string>
|
||||
<string name="warning_express_no_exchangeable_coins_description">У вас в списке нет монет доступных для обмена с %s</string>
|
||||
<string name="warning_express_no_exchangeable_coins_title">Нет доступных для обмена токенов</string>
|
||||
|
|
|
|||
|
|
@ -844,6 +844,8 @@
|
|||
<string name="wallet_settings_title">Налаштування гаманця</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">Використовуйте %s або відскануйте картку, щоб розблокувати доступ до гаманця</string>
|
||||
<string name="warning_approval_in_progress_message">Затвердження обміну триває і незабаром буде завершено</string>
|
||||
<string name="warning_approval_in_progress_title">Затвердження в процесі</string>
|
||||
<string name="warning_backup_errors_message">Схоже, що активація картки була виконана неправильно. Це може бути пов\'язано з проблемою з модулем NFC вашого пристрою або неправильним прикладанням картки до пристрою. Зверніться за допомогою до нашої служби підтримки.</string>
|
||||
<string name="warning_backup_errors_title">Помилка активації</string>
|
||||
<string name="warning_beacon_chain_retirement_content">За рішенням розробників мережі BNB стандарт BEP-2 перестане підтримуватись у червні 2024 року. Щоб не втратити свої активи, їх необхідно конвертувати у стандарт BEP-20. Використовуйте функцію обміну, щоб перевести їх у мережу BNB Smart Chain.</string>
|
||||
|
|
@ -861,8 +863,6 @@
|
|||
<string name="warning_existential_deposit_title">Для роботи з мережею вимагається депозит</string>
|
||||
<string name="warning_express_active_transaction_message">Обмін буде доступний після завершення %s транзакції</string>
|
||||
<string name="warning_express_active_transaction_title">У вас є активна транзакція</string>
|
||||
<string name="warning_express_approval_in_progress_message">Затвердження обміну триває і незабаром буде завершено</string>
|
||||
<string name="warning_express_approval_in_progress_title">Затвердження в процесі</string>
|
||||
<string name="warning_express_dust_message">Мінімальна сума обміну становить - %1$s. Будь ласка, переконайтеся, що залишок на рахунку після обміну буде не менше за %2$s.</string>
|
||||
<string name="warning_express_no_exchangeable_coins_description">У вашому списку немає доступних монет для обміну %s</string>
|
||||
<string name="warning_express_no_exchangeable_coins_title">Немає доступних токенів для обміну</string>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import com.android.ide.common.resources.generateLocaleList
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.core.ui.coil
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Matrix
|
||||
import coil.size.Size
|
||||
import coil.transform.Transformation
|
||||
|
||||
class RotationTransformation(private val angle: Float) : Transformation {
|
||||
|
||||
override val cacheKey: String = "rotate:$angle"
|
||||
|
||||
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
|
||||
val matrix = Matrix().apply {
|
||||
val centerX = input.width / 2f
|
||||
val centerY = input.height / 2f
|
||||
|
||||
postRotate(angle, centerX, centerY)
|
||||
}
|
||||
|
||||
return Bitmap.createBitmap(input, 0, 0, input.width, input.height, matrix, true)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -101,7 +102,11 @@ fun TextShimmer(
|
|||
* Height and min width will be set automatically
|
||||
*/
|
||||
@Composable
|
||||
fun SmallButtonShimmer(modifier: Modifier = Modifier, withIcon: Boolean = false) {
|
||||
fun SmallButtonShimmer(
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = RoundedCornerShape(size = TangemTheme.dimens.radius16),
|
||||
withIcon: Boolean = false,
|
||||
) {
|
||||
PrimarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
text = stringReference("B"),
|
||||
|
|
@ -113,7 +118,7 @@ fun SmallButtonShimmer(modifier: Modifier = Modifier, withIcon: Boolean = false)
|
|||
},
|
||||
),
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius16))
|
||||
.clip(shape)
|
||||
.shimmer(LocalTangemShimmer.current),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
|
|
@ -29,6 +30,7 @@ class InformationBlockContentScope(val scope: BoxScope) : BoxScope by scope
|
|||
fun InformationBlock(
|
||||
title: @Composable BoxScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
contentHorizontalPadding: Dp = TangemTheme.dimens.spacing12,
|
||||
action: (@Composable BoxScope.() -> Unit)? = null,
|
||||
content: (@Composable InformationBlockContentScope.() -> Unit)? = null,
|
||||
) {
|
||||
|
|
@ -72,7 +74,7 @@ fun InformationBlock(
|
|||
if (content != null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12)
|
||||
.padding(horizontal = contentHorizontalPadding)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
val scope = InformationBlockContentScope(scope = this)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ data class SmallButtonConfig(
|
|||
val text: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
val icon: TangemButtonIconPosition = TangemButtonIconPosition.None,
|
||||
val enabled: Boolean = true,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -57,6 +58,7 @@ fun SecondarySmallButton(config: SmallButtonConfig, modifier: Modifier = Modifie
|
|||
SmallButton(config = config, isPrimary = false, modifier = modifier)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier: Modifier = Modifier) {
|
||||
val shape = RoundedCornerShape(size = TangemTheme.dimens.radius16)
|
||||
|
|
@ -77,7 +79,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
|||
color = backgroundColor,
|
||||
shape = shape,
|
||||
)
|
||||
.clickable(enabled = true, onClick = config.onClick)
|
||||
.clickable(enabled = config.enabled, onClick = config.onClick)
|
||||
.padding(
|
||||
paddingValues = when (config.icon) {
|
||||
is TangemButtonIconPosition.None -> PaddingValues(
|
||||
|
|
@ -100,7 +102,11 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
|||
iconPosition = config.icon,
|
||||
text = {
|
||||
val textColor by animateColorAsState(
|
||||
targetValue = if (isPrimary) TangemTheme.colors.text.primary2 else TangemTheme.colors.text.primary1,
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.text.disabled
|
||||
isPrimary -> TangemTheme.colors.text.primary2
|
||||
else -> TangemTheme.colors.text.primary1
|
||||
},
|
||||
label = "Update text color",
|
||||
)
|
||||
|
||||
|
|
@ -116,7 +122,11 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
|||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = iconResId),
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
tint = if (config.enabled) {
|
||||
TangemTheme.colors.icon.secondary
|
||||
} else {
|
||||
TangemTheme.colors.icon.inactive
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
|
|
@ -174,5 +184,12 @@ private fun ButtonsSample() {
|
|||
icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
|
||||
),
|
||||
)
|
||||
SecondarySmallButton(
|
||||
config = config.copy(
|
||||
text = TextReference.Str(value = "Add token"),
|
||||
icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
|
||||
enabled = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke
|
|||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.*
|
||||
|
|
@ -68,6 +69,7 @@ private class ChildArrowScope(
|
|||
|
||||
@Composable
|
||||
fun ChildArrow(childHeight: Dp, isLastChild: Boolean) {
|
||||
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
||||
val figureWidth = TangemTheme.dimens.size40
|
||||
|
||||
val strokeColor = TangemTheme.colors.stroke.secondary
|
||||
|
|
@ -86,18 +88,31 @@ fun ChildArrow(childHeight: Dp, isLastChild: Boolean) {
|
|||
)
|
||||
val arrowHeadRectDp = DpRect(
|
||||
origin = DpOffset(
|
||||
x = figureWidth - arrowHeadSize.width,
|
||||
x = if (isLtr) {
|
||||
figureWidth - arrowHeadSize.width
|
||||
} else {
|
||||
0.dp
|
||||
},
|
||||
y = figureRectDp.size.center.y - arrowHeadSize.center.y,
|
||||
),
|
||||
size = arrowHeadSize,
|
||||
)
|
||||
|
||||
val curvedArrowRectDp = DpRect(
|
||||
top = figureRectDp.top,
|
||||
left = TangemTheme.dimens.size18,
|
||||
right = figureRectDp.right - arrowHeadRectDp.width,
|
||||
bottom = figureRectDp.size.center.y,
|
||||
)
|
||||
val curvedArrowRectDp = if (isLtr) {
|
||||
DpRect(
|
||||
top = figureRectDp.top,
|
||||
left = TangemTheme.dimens.size18,
|
||||
right = figureRectDp.right - arrowHeadRectDp.width,
|
||||
bottom = figureRectDp.size.center.y,
|
||||
)
|
||||
} else {
|
||||
DpRect(
|
||||
top = figureRectDp.top,
|
||||
left = arrowHeadRectDp.width,
|
||||
right = TangemTheme.dimens.size18 + arrowHeadRectDp.width,
|
||||
bottom = figureRectDp.size.center.y,
|
||||
)
|
||||
}
|
||||
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
|
|
@ -114,20 +129,26 @@ fun ChildArrow(childHeight: Dp, isLastChild: Boolean) {
|
|||
drawScope = this,
|
||||
)
|
||||
|
||||
scope.drawCurveArrow()
|
||||
scope.drawArrowHead()
|
||||
scope.drawCurveArrow(isLtr)
|
||||
scope.drawArrowHead(isLtr)
|
||||
|
||||
if (!isLastChild) {
|
||||
scope.drawArrowLine()
|
||||
scope.drawArrowLine(isLtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChildArrowScope.drawArrowHead() {
|
||||
private fun ChildArrowScope.drawArrowHead(isLtr: Boolean) {
|
||||
val arrowHeadPath = Path().apply {
|
||||
moveTo(arrowHeadRect.centerRight)
|
||||
lineTo(arrowHeadRect.topLeft)
|
||||
lineTo(arrowHeadRect.bottomLeft)
|
||||
if (isLtr) {
|
||||
moveTo(arrowHeadRect.centerRight)
|
||||
lineTo(arrowHeadRect.topLeft)
|
||||
lineTo(arrowHeadRect.bottomLeft)
|
||||
} else {
|
||||
moveTo(arrowHeadRect.centerLeft)
|
||||
lineTo(arrowHeadRect.topRight)
|
||||
lineTo(arrowHeadRect.bottomRight)
|
||||
}
|
||||
close()
|
||||
}
|
||||
val paint = Paint().apply {
|
||||
|
|
@ -143,13 +164,21 @@ private fun ChildArrowScope.drawArrowHead() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun ChildArrowScope.drawCurveArrow() {
|
||||
private fun ChildArrowScope.drawCurveArrow(isLtr: Boolean) {
|
||||
val curveArrowPath = Path().apply {
|
||||
moveTo(curvedArrowRect.topLeft)
|
||||
quadraticBezierTo(
|
||||
control = curvedArrowRect.bottomLeft,
|
||||
end = curvedArrowRect.bottomRight,
|
||||
)
|
||||
if (isLtr) {
|
||||
moveTo(curvedArrowRect.topLeft)
|
||||
quadraticBezierTo(
|
||||
control = curvedArrowRect.bottomLeft,
|
||||
end = curvedArrowRect.bottomRight,
|
||||
)
|
||||
} else {
|
||||
moveTo(curvedArrowRect.topRight)
|
||||
quadraticBezierTo(
|
||||
control = curvedArrowRect.bottomRight,
|
||||
end = curvedArrowRect.bottomLeft,
|
||||
)
|
||||
}
|
||||
}
|
||||
drawPath(
|
||||
path = curveArrowPath,
|
||||
|
|
@ -158,11 +187,20 @@ private fun ChildArrowScope.drawCurveArrow() {
|
|||
)
|
||||
}
|
||||
|
||||
private fun ChildArrowScope.drawArrowLine() {
|
||||
drawLine(
|
||||
color = strokeColor,
|
||||
start = curvedArrowRect.topLeft,
|
||||
end = Offset(curvedArrowRect.left, figureRect.bottom),
|
||||
strokeWidth = arrowStrokeWidth,
|
||||
)
|
||||
private fun ChildArrowScope.drawArrowLine(isLtr: Boolean) {
|
||||
if (isLtr) {
|
||||
drawLine(
|
||||
color = strokeColor,
|
||||
start = curvedArrowRect.topLeft,
|
||||
end = Offset(curvedArrowRect.left, figureRect.bottom),
|
||||
strokeWidth = arrowStrokeWidth,
|
||||
)
|
||||
} else {
|
||||
drawLine(
|
||||
color = strokeColor,
|
||||
start = curvedArrowRect.topRight,
|
||||
end = Offset(curvedArrowRect.right, figureRect.bottom),
|
||||
strokeWidth = arrowStrokeWidth,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,8 +29,9 @@ fun BlockchainRow(model: BlockchainRowUM, action: @Composable BoxScope.() -> Uni
|
|||
modifier = modifier
|
||||
.heightIn(min = TangemTheme.dimens.size52)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
horizontal = TangemTheme.dimens.spacing8,
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing8,
|
||||
start = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
icon = {
|
||||
RowIcon(
|
||||
|
|
@ -125,7 +126,7 @@ private fun Preview_BlockchainRow(@PreviewParameter(BlockchainRowParameterProvid
|
|||
BlockchainRow(
|
||||
model = state,
|
||||
action = {
|
||||
TangemSwitch(onCheckedChange = { /* [REDACTED_TODO_COMMENT]*/ }, checked = true)
|
||||
TangemSwitch(onCheckedChange = { }, checked = true)
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import com.tangem.core.ui.windowsize.rememberWindowSizePreview
|
||||
|
||||
@Composable
|
||||
|
|
@ -14,12 +16,14 @@ fun TangemThemePreview(
|
|||
typography: TangemTypography = TangemTheme.typography,
|
||||
dimens: TangemDimens = TangemTheme.dimens,
|
||||
alwaysShowBottomSheets: Boolean = true,
|
||||
rtl: Boolean = false,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val isDarkTheme = isDark ?: isSystemInDarkTheme()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalBottomSheetAlwaysVisible provides alwaysShowBottomSheets,
|
||||
LocalLayoutDirection provides if (rtl) LayoutDirection.Rtl else LayoutDirection.Ltr,
|
||||
) {
|
||||
BoxWithConstraints {
|
||||
TangemTheme(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="36dp"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="36">
|
||||
<path
|
||||
android:fillColor="#C9C9CA"
|
||||
android:pathData="M22,2L22,34A2,2 0,0 1,20 36L2,36A2,2 0,0 1,0 34L0,2A2,2 0,0 1,2 0L20,0A2,2 0,0 1,22 2z" />
|
||||
|
||||
<path
|
||||
android:fillColor="#A1A1A1"
|
||||
android:pathData="M10.92,22.14H10.96C11.52,22.14 11.85,21.83 11.85,21.35C11.85,20.84 11.48,20.55 10.95,20.55H10.91C10.36,20.55 10.03,20.89 10.03,21.32C10.03,21.79 10.36,22.14 10.92,22.14ZM9.29,19.89V20.58C9,20.63 8.83,20.85 8.83,21.3C8.83,21.82 9.09,22.11 9.62,22.11H10.05C9.77,21.95 9.51,21.59 9.51,21.16C9.51,20.42 10.06,19.86 10.9,19.86H10.94C11.76,19.86 12.38,20.42 12.38,21.17C12.38,21.65 12.16,21.95 11.87,22.11H12.32V22.79H9.61C8.75,22.78 8.32,22.21 8.32,21.3C8.32,20.39 8.73,19.97 9.29,19.89ZM11.17,7.82V9.23H9.41C9.06,9.23 8.88,9.23 8.75,9.16C8.63,9.1 8.54,9 8.48,8.89C8.41,8.75 8.41,8.58 8.41,8.23V7.82H11.17ZM13.69,8.23V6C13.69,5.65 13.69,5.48 13.62,5.34C13.56,5.22 13.47,5.13 13.35,5.07C13.21,5 13.04,5 12.69,5H12.31V9.23H12.69H12.69C13.04,9.23 13.21,9.23 13.35,9.16C13.47,9.1 13.56,9 13.62,8.89C13.69,8.75 13.69,8.58 13.69,8.23ZM11.17,5V6.41H8.41V6C8.41,5.65 8.41,5.48 8.48,5.34C8.54,5.22 8.63,5.13 8.75,5.07C8.88,5 9.06,5 9.41,5L11.17,5ZM10.21,11.96H11.81V11.57H12.32V11.96H12.96L12.96,12.64H12.32V13.28H11.81V12.64H10.27C10.01,12.64 9.89,12.76 9.89,12.98C9.89,13.11 9.91,13.21 9.95,13.31H9.41C9.37,13.2 9.34,13.05 9.34,12.85C9.34,12.27 9.65,11.96 10.21,11.96ZM12.32,17.44V16.76H9.39V17.44H11.11C11.58,17.44 11.81,17.74 11.81,18.12C11.81,18.53 11.61,18.71 11.17,18.71H9.39L9.39,19.38H11.23C12.04,19.38 12.38,18.97 12.38,18.38C12.38,17.9 12.14,17.58 11.85,17.44H12.32ZM11.88,24.74C11.88,25.16 11.66,25.42 11.16,25.45V23.99C11.61,24.06 11.88,24.33 11.88,24.74ZM10.87,23.29H10.82C9.9,23.29 9.33,23.91 9.33,24.77C9.33,25.52 9.67,26.02 10.29,26.11V25.46C10,25.41 9.84,25.19 9.84,24.79C9.84,24.28 10.15,24 10.7,23.98V26.12H10.9C11.95,26.12 12.38,25.47 12.38,24.74C12.38,23.91 11.77,23.29 10.87,23.29ZM12.32,26.64V27.32H11.87C12.14,27.46 12.38,27.78 12.38,28.21C12.38,28.59 12.22,28.89 11.85,29.04C12.22,29.26 12.38,29.66 12.38,30.03C12.38,30.56 12.05,31 11.24,31H9.39V30.32H11.2C11.63,30.32 11.81,30.14 11.81,29.8C11.81,29.47 11.59,29.16 11.14,29.16H9.39V28.48H11.2C11.63,28.48 11.81,28.29 11.81,27.96C11.81,27.63 11.59,27.32 11.14,27.32H9.39V26.64H12.32ZM10.7,15.52H10.41C10.04,15.52 9.82,15.22 9.82,14.8C9.82,14.47 9.98,14.33 10.23,14.33C10.59,14.33 10.7,14.66 10.7,15.18V15.52ZM11.13,15.16C11.13,14.32 10.88,13.66 10.2,13.66C9.59,13.66 9.33,14.1 9.33,14.64C9.33,15.09 9.5,15.34 9.75,15.53H9.39V16.2H11.31C12.11,16.2 12.38,15.68 12.38,15.03C12.38,14.38 12.09,13.83 11.41,13.78V14.43C11.7,14.47 11.87,14.64 11.87,14.99C11.87,15.39 11.67,15.52 11.28,15.52H11.13V15.16Z" />
|
||||
</vector>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.feedback.converters
|
||||
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.common.util.getBackupCardsCount
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -20,10 +21,7 @@ internal object CardInfoConverter : Converter<ScanResponse, CardInfo> {
|
|||
CardInfo(
|
||||
userWalletId = createUserWalletId(scanResponse = value),
|
||||
cardId = card.cardId,
|
||||
cardsCount = when (val status = value.card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount.toString()
|
||||
else -> "0"
|
||||
},
|
||||
cardsCount = value.getBackupCardsCount()?.toString() ?: "0",
|
||||
firmwareVersion = card.firmwareVersion.stringValue,
|
||||
cardBlockchain = walletData?.blockchain,
|
||||
signedHashesList = card.wallets.map {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
|||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -261,25 +260,27 @@ internal class DefaultStakingRepository(
|
|||
|
||||
override suspend fun fetchSingleYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
address: CryptoCurrencyAddress,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
|
||||
|
||||
val cryptoCurrency = address.cryptoCurrency
|
||||
val integrationId = integrationIdMap[cryptoCurrency.id.getIntegrationKey()] ?: return@withContext
|
||||
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val requestBody = getBalanceRequestData(address.address, integrationId)
|
||||
val requestBody = getBalanceRequestData(address, integrationId)
|
||||
val result = stakeKitApi.getSingleYieldBalance(
|
||||
integrationId = requestBody.integrationId,
|
||||
body = requestBody,
|
||||
).getOrThrow()
|
||||
|
||||
stakingBalanceStore.store(
|
||||
userWalletId,
|
||||
requestBody.integrationId,
|
||||
YieldBalanceWrapperDTO(
|
||||
balances = result,
|
||||
|
|
@ -292,15 +293,15 @@ internal class DefaultStakingRepository(
|
|||
|
||||
override fun getSingleYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
address: CryptoCurrencyAddress,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Flow<YieldBalance> = channelFlow {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
||||
send(YieldBalance.Empty)
|
||||
} else {
|
||||
launch(dispatchers.io) {
|
||||
val integrationId = integrationIdMap[address.cryptoCurrency.id.getIntegrationKey()]
|
||||
val integrationId = integrationIdMap[cryptoCurrency.id.getIntegrationKey()]
|
||||
?: error("Could not get integrationId")
|
||||
stakingBalanceStore.get(integrationId)
|
||||
stakingBalanceStore.get(userWalletId, integrationId)
|
||||
.collectLatest {
|
||||
send(
|
||||
yieldBalanceConverter.convert(
|
||||
|
|
@ -316,7 +317,7 @@ internal class DefaultStakingRepository(
|
|||
withContext(dispatchers.io) {
|
||||
fetchSingleYieldBalance(
|
||||
userWalletId,
|
||||
address,
|
||||
cryptoCurrency,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -324,16 +325,18 @@ internal class DefaultStakingRepository(
|
|||
|
||||
override suspend fun getSingleYieldBalanceSync(
|
||||
userWalletId: UserWalletId,
|
||||
address: CryptoCurrencyAddress,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): YieldBalance = withContext(dispatchers.io) {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
||||
YieldBalance.Empty
|
||||
} else {
|
||||
fetchSingleYieldBalance(userWalletId, address)
|
||||
fetchSingleYieldBalance(userWalletId, cryptoCurrency)
|
||||
|
||||
val integrationId = integrationIdMap[address.cryptoCurrency.id.getIntegrationKey()]
|
||||
val integrationId = integrationIdMap[cryptoCurrency.id.getIntegrationKey()]
|
||||
?: error("Could not get integrationId")
|
||||
val result = stakingBalanceStore.getSyncOrNull(integrationId) ?: return@withContext YieldBalance.Error
|
||||
val result = stakingBalanceStore.getSyncOrNull(userWalletId, integrationId)
|
||||
?: return@withContext YieldBalance.Error
|
||||
|
||||
yieldBalanceConverter.convert(
|
||||
YieldBalanceConverter.Data(
|
||||
balance = result,
|
||||
|
|
@ -345,7 +348,7 @@ internal class DefaultStakingRepository(
|
|||
|
||||
override suspend fun fetchMultiYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
|
||||
|
|
@ -357,23 +360,23 @@ internal class DefaultStakingRepository(
|
|||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val result = stakeKitApi.getMultipleYieldBalances(
|
||||
addresses
|
||||
.mapNotNull { networkAddress ->
|
||||
val cryptoCurrency = networkAddress.cryptoCurrency
|
||||
val integrationId = integrationIdMap[cryptoCurrency.id.getIntegrationKey()]
|
||||
val availableCurrencies = cryptoCurrencies
|
||||
.mapNotNull { currency ->
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, currency.network)
|
||||
val integrationId = integrationIdMap[currency.id.getIntegrationKey()]
|
||||
|
||||
if (integrationId != null) {
|
||||
networkAddress.address to integrationId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (integrationId != null && address != null) {
|
||||
address to integrationId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
.distinct()
|
||||
.map { getBalanceRequestData(it.first, it.second) },
|
||||
).getOrThrow()
|
||||
}
|
||||
.distinct()
|
||||
.map { getBalanceRequestData(it.first, it.second) }
|
||||
.ifEmpty { return@invokeOnExpire }
|
||||
val result = stakeKitApi.getMultipleYieldBalances(availableCurrencies).getOrThrow()
|
||||
|
||||
stakingBalanceStore.store(result)
|
||||
stakingBalanceStore.store(userWalletId, result)
|
||||
},
|
||||
)
|
||||
} finally {
|
||||
|
|
@ -385,20 +388,20 @@ internal class DefaultStakingRepository(
|
|||
|
||||
override fun getMultiYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Flow<YieldBalanceList> = channelFlow {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
||||
send(YieldBalanceList.Empty)
|
||||
} else {
|
||||
launch(dispatchers.io) {
|
||||
stakingBalanceStore.get()
|
||||
stakingBalanceStore.get(userWalletId)
|
||||
.collectLatest { send(yieldBalanceListConverter.convert(it)) }
|
||||
}
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchMultiYieldBalance(
|
||||
userWalletId,
|
||||
addresses,
|
||||
cryptoCurrencies,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -406,14 +409,14 @@ internal class DefaultStakingRepository(
|
|||
|
||||
override fun getMultiYieldBalanceLce(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): LceFlow<Throwable, YieldBalanceList> = lceFlow {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
||||
send(YieldBalanceList.Empty)
|
||||
} else {
|
||||
launch(dispatchers.io) {
|
||||
combine(
|
||||
stakingBalanceStore.get(),
|
||||
stakingBalanceStore.get(userWalletId),
|
||||
isYieldBalanceFetching.map { it.getOrElse(userWalletId) { false } },
|
||||
) { result, isFetching ->
|
||||
val balances = yieldBalanceListConverter.convert(result)
|
||||
|
|
@ -422,7 +425,7 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
withContext(dispatchers.io) {
|
||||
catch(
|
||||
block = { fetchMultiYieldBalance(userWalletId, addresses, refresh = false) },
|
||||
block = { fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false) },
|
||||
catch = { raise(it) },
|
||||
)
|
||||
}
|
||||
|
|
@ -431,13 +434,13 @@ internal class DefaultStakingRepository(
|
|||
|
||||
override suspend fun getMultiYieldBalanceSync(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): YieldBalanceList = withContext(dispatchers.io) {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
||||
YieldBalanceList.Empty
|
||||
} else {
|
||||
fetchMultiYieldBalance(userWalletId, addresses)
|
||||
val result = stakingBalanceStore.getSyncOrNull() ?: return@withContext YieldBalanceList.Error
|
||||
fetchMultiYieldBalance(userWalletId, cryptoCurrencies)
|
||||
val result = stakingBalanceStore.getSyncOrNull(userWalletId) ?: return@withContext YieldBalanceList.Error
|
||||
yieldBalanceListConverter.convert(result)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrap
|
|||
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class YieldBalanceListConverter : Converter<List<YieldBalanceWrapperDTO>, YieldBalanceList> {
|
||||
internal class YieldBalanceListConverter : Converter<Set<YieldBalanceWrapperDTO>, YieldBalanceList> {
|
||||
|
||||
internal val converter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
YieldBalanceConverter()
|
||||
}
|
||||
|
||||
override fun convert(value: List<YieldBalanceWrapperDTO>): YieldBalanceList {
|
||||
override fun convert(value: Set<YieldBalanceWrapperDTO>): YieldBalanceList {
|
||||
return if (value.isEmpty()) {
|
||||
YieldBalanceList.Empty
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.datasource.local.network.NetworksStatusesStore
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.quote.QuotesStore
|
||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.tokens.repository.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -28,7 +27,7 @@ internal object TokensDataModule {
|
|||
fun provideCurrenciesRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
tangemExpressApi: TangemExpressApi,
|
||||
userTokensStore: UserTokensStore,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
expressAssetsStore: ExpressAssetsStore,
|
||||
|
|
@ -38,7 +37,7 @@ internal object TokensDataModule {
|
|||
return DefaultCurrenciesRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
tangemExpressApi = tangemExpressApi,
|
||||
userTokensStore = userTokensStore,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
userWalletsStore = userWalletsStore,
|
||||
expressAssetsStore = expressAssetsStore,
|
||||
|
|
@ -71,7 +70,7 @@ internal object TokensDataModule {
|
|||
networksStatusesStore: NetworksStatusesStore,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
userTokensStore: UserTokensStore,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): NetworksRepository {
|
||||
|
|
@ -79,7 +78,7 @@ internal object TokensDataModule {
|
|||
networksStatusesStore = networksStatusesStore,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
userWalletsStore = userWalletsStore,
|
||||
userTokensStore = userTokensStore,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
cacheRegistry = cacheRegistry,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,8 +18,12 @@ import com.tangem.datasource.api.express.models.request.AssetsRequestBody
|
|||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObject
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.common.util.hasDerivation
|
||||
|
|
@ -46,11 +50,11 @@ import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency
|
|||
internal class DefaultCurrenciesRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val tangemExpressApi: TangemExpressApi,
|
||||
private val userTokensStore: UserTokensStore,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val expressAssetsStore: ExpressAssetsStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : CurrenciesRepository {
|
||||
|
||||
|
|
@ -86,7 +90,7 @@ internal class DefaultCurrenciesRepository(
|
|||
override suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
return withContext(dispatchers.io) {
|
||||
val savedCurrencies = requireNotNull(
|
||||
value = userTokensStore.getSyncOrNull(userWalletId),
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = { "Saved tokens empty. Can not perform add currencies action" },
|
||||
)
|
||||
|
||||
|
|
@ -141,7 +145,7 @@ internal class DefaultCurrenciesRepository(
|
|||
override suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) =
|
||||
withContext(dispatchers.io) {
|
||||
val savedCurrencies = requireNotNull(
|
||||
value = userTokensStore.getSyncOrNull(userWalletId),
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = { "Saved tokens empty. Can not perform remove currency action" },
|
||||
)
|
||||
|
||||
|
|
@ -163,7 +167,7 @@ internal class DefaultCurrenciesRepository(
|
|||
override suspend fun removeCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
return withContext(dispatchers.io) {
|
||||
val savedCurrencies = requireNotNull(
|
||||
value = userTokensStore.getSyncOrNull(userWalletId),
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = { "Saved tokens empty. Can not perform remove currencies action" },
|
||||
)
|
||||
|
||||
|
|
@ -276,9 +280,12 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
fetchTokensIfCacheExpired(userWallet, refresh)
|
||||
|
||||
val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
val storedTokens = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWallet.walletId),
|
||||
lazyMessage = {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
},
|
||||
)
|
||||
|
||||
responseCurrenciesFactory.createCurrencies(storedTokens, userWallet.scanResponse)
|
||||
}
|
||||
|
|
@ -290,9 +297,12 @@ internal class DefaultCurrenciesRepository(
|
|||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
val response = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
},
|
||||
)
|
||||
|
||||
responseCurrenciesFactory.createCurrency(
|
||||
currencyId = id,
|
||||
|
|
@ -312,9 +322,12 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
fetchTokensIfCacheExpired(userWallet = userWallet, refresh = false)
|
||||
|
||||
val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
val storedTokens = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
},
|
||||
)
|
||||
val blockchain = Blockchain.fromId(networkId.value)
|
||||
val blockchainNetworkId = blockchain.toNetworkId()
|
||||
val coinId = blockchain.toCoinId()
|
||||
|
|
@ -335,7 +348,7 @@ internal class DefaultCurrenciesRepository(
|
|||
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
launch(dispatchers.io) {
|
||||
userTokensStore.get(userWalletId)
|
||||
getSavedUserTokensResponse(userWalletId)
|
||||
.map { it.group == UserTokensResponse.GroupType.NETWORK }
|
||||
.collect(::send)
|
||||
}
|
||||
|
|
@ -347,7 +360,7 @@ internal class DefaultCurrenciesRepository(
|
|||
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
launch(dispatchers.io) {
|
||||
userTokensStore.get(userWalletId)
|
||||
getSavedUserTokensResponse(userWalletId)
|
||||
.map { it.sort == UserTokensResponse.SortType.BALANCE }
|
||||
.collect(::send)
|
||||
}
|
||||
|
|
@ -461,9 +474,14 @@ internal class DefaultCurrenciesRepository(
|
|||
val userWallet = getUserWallet(userWalletId)
|
||||
fetchTokensIfCacheExpired(userWallet, refresh = false)
|
||||
|
||||
val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
val storedTokens = requireNotNull(
|
||||
value = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(userWallet.walletId.stringValue),
|
||||
),
|
||||
lazyMessage = {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
},
|
||||
)
|
||||
|
||||
return storedTokens.tokens.any {
|
||||
it.contractAddress != null &&
|
||||
|
|
@ -473,7 +491,7 @@ internal class DefaultCurrenciesRepository(
|
|||
}
|
||||
|
||||
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<List<CryptoCurrency>> {
|
||||
return userTokensStore.get(userWallet.walletId).map { storedTokens ->
|
||||
return getSavedUserTokensResponse(userWallet.walletId).map { storedTokens ->
|
||||
responseCurrenciesFactory.createCurrencies(
|
||||
response = storedTokens,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
|
|
@ -517,17 +535,26 @@ internal class DefaultCurrenciesRepository(
|
|||
.let { customTokensMerger.mergeIfPresented(userWalletId, response) }
|
||||
.let(userTokensBackwardCompatibility::applyCompatibilityAndGetUpdated)
|
||||
|
||||
userTokensStore.store(userWallet.walletId, compatibleUserTokensResponse)
|
||||
appPreferencesStore.storeObject(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = userWallet.walletId.stringValue),
|
||||
value = compatibleUserTokensResponse,
|
||||
)
|
||||
|
||||
fetchExchangeableUserMarketCoinsByIds(userWalletId, compatibleUserTokensResponse)
|
||||
}
|
||||
|
||||
private suspend fun checkIsEmptyDemoWallet(userWallet: UserWallet): Boolean {
|
||||
return demoConfig.isDemoCardId(userWallet.cardId) && userTokensStore.getSyncOrNull(userWallet.walletId) == null
|
||||
val response = getSavedUserTokensResponseSync(key = userWallet.walletId)
|
||||
|
||||
return demoConfig.isDemoCardId(userWallet.cardId) && response == null
|
||||
}
|
||||
|
||||
private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
|
||||
userTokensStore.store(userWalletId, compatibleUserTokensResponse)
|
||||
appPreferencesStore.storeObject(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
|
||||
value = compatibleUserTokensResponse,
|
||||
)
|
||||
|
||||
pushTokens(userWalletId, response)
|
||||
}
|
||||
|
|
@ -561,8 +588,9 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
private suspend fun handleFetchTokensError(userWallet: UserWallet, e: ApiResponseError): UserTokensResponse {
|
||||
val userWalletId = userWallet.walletId
|
||||
val response = userTokensStore.getSyncOrNull(userWalletId)
|
||||
?: createDefaultUserTokensResponse(userWallet)
|
||||
val response = appPreferencesStore.getObjectSyncOrNull(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
|
||||
) ?: createDefaultUserTokensResponse(userWallet)
|
||||
|
||||
if (e is ApiResponseError.HttpException && e.code == ApiResponseError.HttpException.Code.NOT_FOUND) {
|
||||
Timber.w(e, "Requested currencies could not be found in the remote store for: $userWalletId")
|
||||
|
|
@ -622,4 +650,16 @@ internal class DefaultCurrenciesRepository(
|
|||
}
|
||||
|
||||
private fun getTokensCacheKey(userWalletId: UserWalletId): String = "tokens_cache_key_${userWalletId.stringValue}"
|
||||
|
||||
private fun getSavedUserTokensResponse(key: UserWalletId): Flow<UserTokensResponse> {
|
||||
return appPreferencesStore
|
||||
.getObject<UserTokensResponse>(PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue))
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
private suspend fun getSavedUserTokensResponseSync(key: UserWalletId): UserTokensResponse? {
|
||||
return appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(key.stringValue),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,11 @@ import com.tangem.data.common.cache.CacheRegistry
|
|||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
|
||||
import com.tangem.data.tokens.utils.NetworkStatusFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.network.NetworksStatusesStore
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
|
|
@ -33,7 +36,7 @@ internal class DefaultNetworksRepository(
|
|||
private val networksStatusesStore: NetworksStatusesStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val userTokensStore: UserTokensStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : NetworksRepository {
|
||||
|
|
@ -300,9 +303,14 @@ internal class DefaultNetworksRepository(
|
|||
}
|
||||
|
||||
return if (userWallet.isMultiCurrency) {
|
||||
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
val response = requireNotNull(
|
||||
value = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
|
||||
),
|
||||
lazyMessage = {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
},
|
||||
)
|
||||
|
||||
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
|||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.configs.CardConfig
|
||||
import com.tangem.domain.common.configs.Wallet2CardConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
|
|
@ -32,8 +33,6 @@ val UserWallet.cardTypesResolver: CardTypesResolver
|
|||
get() = scanResponse.cardTypesResolver
|
||||
|
||||
fun ScanResponse.twinsIsTwinned(): Boolean = card.isTangemTwins && walletData != null && secondTwinPublicKey != null
|
||||
fun ScanResponse.supportsHdWallet(): Boolean = card.settings.isHDWalletAllowed
|
||||
fun ScanResponse.supportsBackup(): Boolean = card.settings.isBackupAllowed
|
||||
|
||||
fun ScanResponse.hasDerivation(blockchain: Blockchain, rawDerivationPath: String): Boolean {
|
||||
return hasDerivation(blockchain, DerivationPath(rawDerivationPath))
|
||||
|
|
@ -66,4 +65,37 @@ private fun ScanResponse.hasDerivation(curve: EllipticCurve, derivationPath: Der
|
|||
val extendedPublicKeysMap = derivedKeys[foundWallet.publicKey.toMapKey()] ?: return false
|
||||
val extendedPublicKey = extendedPublicKeysMap[derivationPath]
|
||||
return extendedPublicKey != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total cards count in wallets set for this [ScanResponse] card
|
||||
*
|
||||
* @return null if wallet is not multi-currency or total cards count
|
||||
*/
|
||||
fun ScanResponse.getCardsCount(): Int? {
|
||||
if (!cardTypesResolver.isMultiwalletAllowed()) return null
|
||||
|
||||
return when (val status = card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount + 1
|
||||
is CardDTO.BackupStatus.NoBackup,
|
||||
is CardDTO.BackupStatus.CardLinked,
|
||||
null, // Multi-currency wallet without backup function. Example, 4.12
|
||||
-> 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backup cards count for this [ScanResponse] card
|
||||
*
|
||||
* @return null if wallet is not multi-currency or total cards count
|
||||
*/
|
||||
fun ScanResponse.getBackupCardsCount(): Int? {
|
||||
return if (cardTypesResolver.isMultiwalletAllowed()) {
|
||||
when (val status = card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount
|
||||
else -> 0
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.common.util
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
/**
|
||||
* Get total cards count in wallets set for a card that was saved in [UserWallet]
|
||||
*
|
||||
* @return null if wallet is not multi-currency or total cards count
|
||||
*/
|
||||
fun UserWallet.getCardsCount(): Int? = scanResponse.getCardsCount()
|
||||
|
||||
/**
|
||||
* Get backup cards count for a card that was saved in [UserWallet]
|
||||
*
|
||||
* @return null if wallet is not multi-currency or total cards count
|
||||
*/
|
||||
fun UserWallet.getBackupCardsCount(): Int? = scanResponse.getBackupCardsCount()
|
||||
|
|
@ -376,15 +376,12 @@ class DefaultWalletManagersFacade(
|
|||
return walletManagersStore.getAllSync(userWalletId)
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Use NetworkAddress from CryptoCurrencyStatus",
|
||||
ReplaceWith("cryptoCurrencyStatus.value.networkAddress"),
|
||||
)
|
||||
override suspend fun getAddress(userWalletId: UserWalletId, network: Network): List<Address> {
|
||||
return getAddresses(userWalletId, network).sortedBy { it.type }
|
||||
override suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String? {
|
||||
return getAddresses(userWalletId, network)
|
||||
.firstOrNull { it.type == AddressType.Default }
|
||||
?.value
|
||||
}
|
||||
|
||||
@Deprecated("Use NetworkAddress from CryptoCurrencyStatus")
|
||||
override suspend fun getAddresses(userWalletId: UserWalletId, network: Network): Set<Address> {
|
||||
val manager = getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
|
|
|
|||
|
|
@ -117,20 +117,18 @@ interface WalletManagersFacade {
|
|||
suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager>
|
||||
|
||||
/**
|
||||
* Returns ordered list of addresses for selected wallet for given currency
|
||||
* Returns default network address for selected wallet in given network
|
||||
*
|
||||
* @param userWalletId selected wallet id
|
||||
* @param network network of currency
|
||||
*/
|
||||
@Deprecated("Use NetworkAddress from CryptoCurrencyStatus")
|
||||
suspend fun getAddress(userWalletId: UserWalletId, network: Network): List<Address>
|
||||
suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String?
|
||||
|
||||
/** Returns list of all addresses for all currencies in selected wallet
|
||||
*
|
||||
* @param userWalletId selected wallet id
|
||||
* @param network required to create wallet manager
|
||||
*/
|
||||
@Deprecated("Use NetworkAddress from CryptoCurrencyStatus")
|
||||
suspend fun getAddresses(userWalletId: UserWalletId, network: Network): Set<Address>
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import arrow.core.raise.either
|
|||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class FetchStakingYieldBalanceUseCase(
|
||||
|
|
@ -16,7 +16,7 @@ class FetchStakingYieldBalanceUseCase(
|
|||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
address: CryptoCurrencyAddress,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
refresh: Boolean = false,
|
||||
): Either<StakingError, Unit> {
|
||||
return either {
|
||||
|
|
@ -24,7 +24,7 @@ class FetchStakingYieldBalanceUseCase(
|
|||
block = {
|
||||
stakingRepository.fetchSingleYieldBalance(
|
||||
userWalletId = userWalletId,
|
||||
address = address,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
refresh = refresh,
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.domain.staking.model.stakekit.StakingError
|
|||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -20,11 +20,11 @@ class GetStakingYieldBalanceUseCase(
|
|||
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
address: CryptoCurrencyAddress,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): EitherFlow<StakingError, YieldBalance> {
|
||||
return stakingRepository.getSingleYieldBalanceFlow(
|
||||
userWalletId = userWalletId,
|
||||
address = address,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
).map<YieldBalance, Either<StakingError, YieldBalance>> { it.right() }
|
||||
.catch { emit(stakingErrorResolver.resolve(it).left()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
|||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -38,33 +37,33 @@ interface StakingRepository {
|
|||
|
||||
suspend fun fetchSingleYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
address: CryptoCurrencyAddress,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
refresh: Boolean = false,
|
||||
)
|
||||
|
||||
fun getSingleYieldBalanceFlow(userWalletId: UserWalletId, address: CryptoCurrencyAddress): Flow<YieldBalance>
|
||||
fun getSingleYieldBalanceFlow(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Flow<YieldBalance>
|
||||
|
||||
suspend fun getSingleYieldBalanceSync(userWalletId: UserWalletId, address: CryptoCurrencyAddress): YieldBalance
|
||||
suspend fun getSingleYieldBalanceSync(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): YieldBalance
|
||||
|
||||
suspend fun fetchMultiYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
refresh: Boolean = false,
|
||||
)
|
||||
|
||||
fun getMultiYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Flow<YieldBalanceList>
|
||||
|
||||
fun getMultiYieldBalanceLce(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): LceFlow<Throwable, YieldBalanceList>
|
||||
|
||||
suspend fun getMultiYieldBalanceSync(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): YieldBalanceList
|
||||
|
||||
suspend fun createAction(userWalletId: UserWalletId, network: Network, params: ActionParams): StakingAction
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class FetchCardTokenListUseCase(
|
|||
val yieldBalances = async {
|
||||
fetchYieldBalances(
|
||||
userWalletId = userWalletId,
|
||||
currencies = currencies,
|
||||
refresh = refresh,
|
||||
)
|
||||
}
|
||||
|
|
@ -77,10 +78,13 @@ class FetchCardTokenListUseCase(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchYieldBalances(userWalletId: UserWalletId, refresh: Boolean) {
|
||||
val networkAddresses = networksRepository.getNetworkAddresses(userWalletId)
|
||||
private suspend fun fetchYieldBalances(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: List<CryptoCurrency>,
|
||||
refresh: Boolean,
|
||||
) {
|
||||
catch(
|
||||
block = { stakingRepository.fetchMultiYieldBalance(userWalletId, networkAddresses, refresh) },
|
||||
block = { stakingRepository.fetchMultiYieldBalance(userWalletId, currencies, refresh) },
|
||||
catch = { /* Ignore error */ },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import arrow.core.Either
|
|||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -29,6 +30,7 @@ class FetchCurrencyStatusUseCase(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -80,8 +82,11 @@ class FetchCurrencyStatusUseCase(
|
|||
val fetchQuote = async {
|
||||
fetchQuote(currency.id, refresh)
|
||||
}
|
||||
val fetchStakingBalance = async {
|
||||
fetchStakingBalance(userWalletId, currency, refresh)
|
||||
}
|
||||
|
||||
awaitAll(fetchStatus, fetchQuote)
|
||||
awaitAll(fetchStatus, fetchQuote, fetchStakingBalance)
|
||||
}
|
||||
|
||||
private suspend fun Raise<CurrencyStatusError>.getCurrency(
|
||||
|
|
@ -122,4 +127,16 @@ class FetchCurrencyStatusUseCase(
|
|||
raise(CurrencyStatusError.DataError(it))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<CurrencyStatusError>.fetchStakingBalance(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
refresh: Boolean,
|
||||
) {
|
||||
catch(
|
||||
block = { stakingRepository.fetchSingleYieldBalance(userWalletId, cryptoCurrency, refresh) },
|
||||
) {
|
||||
raise(CurrencyStatusError.DataError(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,11 +69,10 @@ internal class CurrenciesStatusesLceOperations(
|
|||
|
||||
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
|
||||
val addresses = networksRepository.getNetworkAddresses(userWalletId)
|
||||
combine(
|
||||
getQuotes(currenciesIds),
|
||||
getNetworksStatuses(userWalletId, networks),
|
||||
getYieldBalances(userWalletId, addresses),
|
||||
getYieldBalances(userWalletId, nonEmptyCurrencies),
|
||||
) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances ->
|
||||
val statuses = createCurrenciesStatuses(
|
||||
currencies = nonEmptyCurrencies,
|
||||
|
|
@ -196,11 +195,11 @@ internal class CurrenciesStatusesLceOperations(
|
|||
|
||||
private fun getYieldBalances(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): LceFlow<TokenListError, YieldBalanceList> {
|
||||
return stakingRepository.getMultiYieldBalanceLce(
|
||||
userWalletId = userWalletId,
|
||||
addresses = addresses,
|
||||
cryptoCurrencies = cryptoCurrencies,
|
||||
).map { maybeBalances ->
|
||||
maybeBalances.mapError { TokenListError.DataError(it) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
// FIXME: Refactor - [REDACTED_JIRA]
|
||||
|
|
@ -35,7 +34,7 @@ internal class CurrenciesStatusesOperations(
|
|||
val quotes = quotesRepository.getQuotesSync(currenciesIds, false).right()
|
||||
val networkStatuses =
|
||||
networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right()
|
||||
val yieldBalances = getYieldBalancesSync()
|
||||
val yieldBalances = getYieldBalancesSync(nonEmptyCurrencies)
|
||||
|
||||
return createCurrenciesStatuses(nonEmptyCurrencies, quotes, networkStatuses, yieldBalances)
|
||||
},
|
||||
|
|
@ -147,7 +146,7 @@ internal class CurrenciesStatusesOperations(
|
|||
val currenciesFlow = combine(
|
||||
getQuotes(currenciesIds),
|
||||
getNetworksStatuses(networks),
|
||||
getYieldBalances(),
|
||||
getYieldBalances(nonEmptyCurrencies),
|
||||
) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances ->
|
||||
createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses, maybeYieldBalances)
|
||||
}
|
||||
|
|
@ -385,25 +384,23 @@ internal class CurrenciesStatusesOperations(
|
|||
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun getYieldBalances(): EitherFlow<Error, YieldBalanceList> {
|
||||
return networksRepository.getNetworkAddressesFlow(userWalletId).flatMapLatest { addresses ->
|
||||
stakingRepository.getMultiYieldBalanceFlow(
|
||||
userWalletId = userWalletId,
|
||||
addresses = addresses,
|
||||
).map<YieldBalanceList, Either<Error, YieldBalanceList>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
|
||||
}
|
||||
private fun getYieldBalances(cryptoCurrencies: List<CryptoCurrency>): Flow<Either<Error, YieldBalanceList>> {
|
||||
return stakingRepository.getMultiYieldBalanceFlow(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencies = cryptoCurrencies,
|
||||
).map<YieldBalanceList, Either<Error, YieldBalanceList>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
|
||||
}
|
||||
|
||||
private suspend fun getYieldBalancesSync(): Either<Error.EmptyYieldBalances, YieldBalanceList> {
|
||||
private suspend fun getYieldBalancesSync(
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Either<Error.EmptyYieldBalances, YieldBalanceList> {
|
||||
return catch(
|
||||
block = {
|
||||
val networkAddresses = networksRepository.getNetworkAddresses(userWalletId)
|
||||
stakingRepository.getMultiYieldBalanceSync(
|
||||
userWalletId,
|
||||
networkAddresses,
|
||||
cryptoCurrencies,
|
||||
).right()
|
||||
},
|
||||
catch = {
|
||||
|
|
@ -417,10 +414,9 @@ internal class CurrenciesStatusesOperations(
|
|||
): Either<Error.EmptyYieldBalances, YieldBalance> {
|
||||
return catch(
|
||||
block = {
|
||||
val address = networksRepository.getNetworkAddress(userWalletId, cryptoCurrency)
|
||||
stakingRepository.getSingleYieldBalanceSync(
|
||||
userWalletId,
|
||||
address,
|
||||
cryptoCurrency,
|
||||
).right()
|
||||
},
|
||||
catch = {
|
||||
|
|
@ -429,19 +425,13 @@ internal class CurrenciesStatusesOperations(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun getYieldBalance(cryptoCurrency: CryptoCurrency): EitherFlow<Error, YieldBalance> {
|
||||
return networksRepository.getNetworkAddressFlow(
|
||||
userWalletId,
|
||||
cryptoCurrency,
|
||||
).flatMapLatest { address ->
|
||||
stakingRepository.getSingleYieldBalanceFlow(
|
||||
userWalletId = userWalletId,
|
||||
address = address,
|
||||
).map<YieldBalance, Either<Error, YieldBalance>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
|
||||
}
|
||||
return stakingRepository.getSingleYieldBalanceFlow(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
).map<YieldBalance, Either<Error, YieldBalance>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
|
||||
}
|
||||
|
||||
private fun getIds(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
|||
import com.tangem.domain.staking.model.stakekit.transaction.*
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -118,7 +117,7 @@ class MockStakingRepository : StakingRepository {
|
|||
|
||||
override suspend fun fetchSingleYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
address: CryptoCurrencyAddress,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
refresh: Boolean,
|
||||
) {
|
||||
/* no-op */
|
||||
|
|
@ -126,19 +125,19 @@ class MockStakingRepository : StakingRepository {
|
|||
|
||||
override fun getSingleYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
address: CryptoCurrencyAddress,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Flow<YieldBalance> = channelFlow {
|
||||
send(YieldBalance.Error)
|
||||
}
|
||||
|
||||
override suspend fun getSingleYieldBalanceSync(
|
||||
userWalletId: UserWalletId,
|
||||
address: CryptoCurrencyAddress,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): YieldBalance = YieldBalance.Error
|
||||
|
||||
override suspend fun fetchMultiYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
refresh: Boolean,
|
||||
) {
|
||||
/* no-op */
|
||||
|
|
@ -146,7 +145,7 @@ class MockStakingRepository : StakingRepository {
|
|||
|
||||
override fun getMultiYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Flow<YieldBalanceList> = channelFlow {
|
||||
send(
|
||||
YieldBalanceList.Data(
|
||||
|
|
@ -157,7 +156,7 @@ class MockStakingRepository : StakingRepository {
|
|||
|
||||
override fun getMultiYieldBalanceLce(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): LceFlow<Throwable, YieldBalanceList> = lceFlow {
|
||||
send(
|
||||
YieldBalanceList.Data(
|
||||
|
|
@ -168,7 +167,7 @@ class MockStakingRepository : StakingRepository {
|
|||
|
||||
override suspend fun getMultiYieldBalanceSync(
|
||||
userWalletId: UserWalletId,
|
||||
addresses: List<CryptoCurrencyAddress>,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): YieldBalanceList = YieldBalanceList.Data(
|
||||
balances = listOf(YieldBalance.Error),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ dependencies {
|
|||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.common.ui)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.details.component.preview
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -17,7 +18,7 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
|
|||
|
||||
private val previewState = UserWalletListUM(
|
||||
userWallets = persistentListOf(
|
||||
UserWalletListUM.UserWalletUM(
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_1".encodeToByteArray()),
|
||||
name = stringReference("My Wallet"),
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
|
|
@ -25,7 +26,7 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
|
|||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
UserWalletListUM.UserWalletUM(
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_2".encodeToByteArray()),
|
||||
name = stringReference("Old wallet"),
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
|
|
@ -33,7 +34,7 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
|
|||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
UserWalletListUM.UserWalletUM(
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_3".encodeToByteArray()),
|
||||
name = stringReference("Multi Card"),
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
|
|
|
|||
|
|
@ -1,25 +1,14 @@
|
|||
package com.tangem.features.details.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal data class UserWalletListUM(
|
||||
val userWallets: ImmutableList<UserWalletUM>,
|
||||
val userWallets: ImmutableList<UserWalletItemUM>,
|
||||
val isWalletSavingInProgress: Boolean,
|
||||
val addNewWalletText: TextReference,
|
||||
val onAddNewWalletClick: () -> Unit,
|
||||
) {
|
||||
|
||||
@Immutable
|
||||
data class UserWalletUM(
|
||||
val id: UserWalletId,
|
||||
val name: TextReference,
|
||||
val information: TextReference,
|
||||
val imageUrl: String,
|
||||
val isEnabled: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.features.details.model
|
||||
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.utils.UserWalletSaver
|
||||
import com.tangem.features.details.utils.UserWalletsFetcher
|
||||
|
|
@ -48,7 +48,7 @@ internal class UserWalletListModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updateState(
|
||||
userWallets: ImmutableList<UserWalletUM>,
|
||||
userWallets: ImmutableList<UserWalletItemUM>,
|
||||
shouldSaveUserWallets: Boolean,
|
||||
isWalletSavingInProgress: Boolean,
|
||||
) = state.update { value ->
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.features.details.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -10,32 +9,25 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.common.ui.userwallet.UserWalletItem
|
||||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.ui.coil.RotationTransformation
|
||||
|
||||
@Composable
|
||||
internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = Modifier) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
) {
|
||||
state.userWallets.forEach { model ->
|
||||
key(model.id) {
|
||||
state.userWallets.forEach { state ->
|
||||
key(state.id) {
|
||||
UserWalletItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
model = model,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -47,96 +39,6 @@ internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = M
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UserWalletItem(model: UserWalletListUM.UserWalletUM, modifier: Modifier = Modifier) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
onClick = model.onClick,
|
||||
enabled = model.isEnabled,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size68)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Image(imageUrl = model.imageUrl)
|
||||
NameAndInfo(
|
||||
name = model.name,
|
||||
information = model.information,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NameAndInfo(name: TextReference, information: TextReference, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.heightIn(min = TangemTheme.dimens.size40),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Text(
|
||||
text = name.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
AnimatedContent(
|
||||
targetState = information.resolveReference(),
|
||||
label = "User wallet information",
|
||||
) { information ->
|
||||
Text(
|
||||
text = information,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Image(imageUrl: String, modifier: Modifier = Modifier) {
|
||||
val imageModifier = modifier
|
||||
.width(TangemTheme.dimens.size24)
|
||||
.height(TangemTheme.dimens.size36)
|
||||
.clip(TangemTheme.shapes.roundedCornersSmall)
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = imageModifier,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.transformations(RotationTransformation(angle = 90f))
|
||||
.size(
|
||||
width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() },
|
||||
height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() },
|
||||
)
|
||||
.data(imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(enable = false)
|
||||
.build(),
|
||||
loading = {
|
||||
RectangleShimmer(
|
||||
modifier = imageModifier,
|
||||
radius = TangemTheme.dimens.size2,
|
||||
)
|
||||
},
|
||||
error = {
|
||||
Image(
|
||||
modifier = imageModifier,
|
||||
painter = painterResource(id = R.drawable.img_card_wallet_2_gray_22_36),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddWalletButton(
|
||||
text: TextReference,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.utils.StringsSigns.STARS
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -19,7 +19,7 @@ internal fun List<UserWallet>.toUiModels(
|
|||
balances: Map<UserWalletId, TotalFiatBalance> = emptyMap(),
|
||||
isLoading: Boolean = true,
|
||||
isBalancesHidden: Boolean = false,
|
||||
): ImmutableList<UserWalletUM> = this.map { model ->
|
||||
): ImmutableList<UserWalletItemUM> = this.map { model ->
|
||||
val balance = balances[model.walletId]
|
||||
|
||||
model.toUiModel(
|
||||
|
|
@ -37,7 +37,7 @@ private fun UserWallet.toUiModel(
|
|||
isLoading: Boolean,
|
||||
isBalanceHidden: Boolean,
|
||||
onClick: () -> Unit,
|
||||
): UserWalletUM = UserWalletUM(
|
||||
): UserWalletItemUM = UserWalletItemUM(
|
||||
id = walletId,
|
||||
name = stringReference(name),
|
||||
information = getInfo(
|
||||
|
|
@ -59,7 +59,7 @@ private fun UserWallet.getInfo(
|
|||
): TextReference {
|
||||
val dividerRef = stringReference(value = " • ")
|
||||
|
||||
val cardCount = getCardCount()
|
||||
val cardCount = getCardsCount() ?: 1
|
||||
val cardCountRef = TextReference.PluralRes(
|
||||
id = R.plurals.card_label_card_count,
|
||||
count = cardCount,
|
||||
|
|
@ -99,12 +99,4 @@ private fun getBalanceInfo(
|
|||
} else {
|
||||
combinedReference(cardCountRef, dividerRef, stringReference(BigDecimalFormatter.EMPTY_BALANCE_SIGN))
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserWallet.getCardCount() = when (val status = scanResponse.card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount.inc()
|
||||
is CardDTO.BackupStatus.CardLinked -> status.cardCount.inc()
|
||||
is CardDTO.BackupStatus.NoBackup,
|
||||
null,
|
||||
-> 1
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.details.utils
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
|
|
@ -22,7 +23,6 @@ import com.tangem.domain.tokens.model.TotalFiatBalance
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -40,7 +40,7 @@ internal class UserWalletsFetcher @Inject constructor(
|
|||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val userWallets: Flow<ImmutableList<UserWalletUM>> = getWalletsUseCase().transformLatest { wallets ->
|
||||
val userWallets: Flow<ImmutableList<UserWalletItemUM>> = getWalletsUseCase().transformLatest { wallets ->
|
||||
emit(wallets.toUiModels(onClick = ::navigateToWalletSettings))
|
||||
|
||||
combine(
|
||||
|
|
@ -72,7 +72,7 @@ internal class UserWalletsFetcher @Inject constructor(
|
|||
maybeAppCurrency: Either<SelectedAppCurrencyError, AppCurrency>,
|
||||
maybeBalances: Lce<TokenListError, Map<UserWalletId, TotalFiatBalance>>,
|
||||
balanceHidingSettings: BalanceHidingSettings,
|
||||
): Lce<Error, ImmutableList<UserWalletUM>> = lce {
|
||||
): Lce<Error, ImmutableList<UserWalletItemUM>> = lce {
|
||||
val balances = withError(
|
||||
transform = { Error.UnableToGetBalances },
|
||||
block = { maybeBalances.bindOrNull().orEmpty() },
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ private fun NetworksList(networks: NetworksUM, currencyId: String, modifier: Mod
|
|||
isLastItem = index == currentItems.lastIndex,
|
||||
content = {
|
||||
BlockchainRow(
|
||||
modifier = Modifier.padding(end = TangemTheme.dimens.spacing8),
|
||||
model = with(network) {
|
||||
BlockchainRowUM(
|
||||
name = name,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ dependencies {
|
|||
implementation(projects.domain.markets)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.coil)
|
||||
|
|
@ -46,6 +47,7 @@ dependencies {
|
|||
implementation(projects.core.ui)
|
||||
implementation(projects.core.featuretoggles)
|
||||
|
||||
/* Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.uiCharts)
|
||||
}
|
||||
|
|
@ -6,24 +6,47 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.markets.component.BottomSheetState
|
||||
import com.tangem.features.markets.details.api.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.markets.details.impl.model.MarketsTokenDetailsModel
|
||||
import com.tangem.features.markets.details.impl.model.state.TokenNetworksState
|
||||
import com.tangem.features.markets.details.impl.ui.MarketsTokenDetailsContent
|
||||
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: MarketsTokenDetailsComponent.Params,
|
||||
@Assisted private val onBack: () -> Unit,
|
||||
portfolioComponentFactory: MarketsPortfolioComponent.Factory,
|
||||
) : AppComponentContext by appComponentContext, MarketsTokenDetailsComponent {
|
||||
|
||||
private val model: MarketsTokenDetailsModel = getOrCreateModel(params)
|
||||
|
||||
private val portfolioComponent = portfolioComponentFactory.create(
|
||||
context = child("my_portfolio"),
|
||||
params = MarketsPortfolioComponent.Params(params.token.id),
|
||||
)
|
||||
|
||||
init {
|
||||
componentScope.launch {
|
||||
model.networksState.collectLatest {
|
||||
when (it) {
|
||||
is TokenNetworksState.NetworksAvailable -> portfolioComponent.setTokenNetworks(it.networks)
|
||||
TokenNetworksState.NoNetworksAvailable -> portfolioComponent.setNoNetworksAvailable()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheetContent(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
|
|
@ -48,6 +71,9 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
|
|||
state = state,
|
||||
onBackClick = onBack,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
portfolioBlock = { modifier ->
|
||||
portfolioComponent.Content(modifier)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.markets.details.impl.model
|
|||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.charts.state.*
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
|
|
@ -25,6 +26,7 @@ import com.tangem.features.markets.details.impl.model.formatter.*
|
|||
import com.tangem.features.markets.details.impl.model.formatter.formatAsPrice
|
||||
import com.tangem.features.markets.details.impl.model.formatter.getChangePercentBetween
|
||||
import com.tangem.features.markets.details.impl.model.formatter.getPercentByInterval
|
||||
import com.tangem.features.markets.details.impl.model.state.TokenNetworksState
|
||||
import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent
|
||||
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
|
||||
import com.tangem.features.markets.impl.R
|
||||
|
|
@ -43,6 +45,7 @@ import javax.inject.Inject
|
|||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class MarketsTokenDetailsModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -117,6 +120,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
|
||||
val containerBottomSheetState = MutableStateFlow(BottomSheetState.COLLAPSED)
|
||||
val isVisibleOnScreen = MutableStateFlow(false)
|
||||
val networksState = MutableStateFlow<TokenNetworksState>(TokenNetworksState.Loading)
|
||||
|
||||
val state = MutableStateFlow(
|
||||
MarketsTokenDetailsUM(
|
||||
|
|
@ -292,6 +296,14 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
val networks = result.networks
|
||||
|
||||
networksState.value = if (networks.isNullOrEmpty()) {
|
||||
TokenNetworksState.NoNetworksAvailable
|
||||
} else {
|
||||
TokenNetworksState.NetworksAvailable(networks)
|
||||
}
|
||||
|
||||
chartDataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.features.markets.details.impl.model.state
|
||||
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
|
||||
internal sealed class TokenNetworksState {
|
||||
|
||||
data object Loading : TokenNetworksState()
|
||||
|
||||
data object NoNetworksAvailable : TokenNetworksState()
|
||||
|
||||
data class NetworksAvailable(val networks: List<TokenMarketInfo.Network>) : TokenNetworksState()
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ internal fun MarketsTokenDetailsContent(
|
|||
state: MarketsTokenDetailsUM,
|
||||
onBackClick: () -> Unit,
|
||||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
portfolioBlock: @Composable (Modifier) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Content(
|
||||
|
|
@ -58,6 +59,7 @@ internal fun MarketsTokenDetailsContent(
|
|||
state = state,
|
||||
onBackClick = onBackClick,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
portfolioBlock = portfolioBlock,
|
||||
)
|
||||
|
||||
InfoBottomSheet(config = state.infoBottomSheet)
|
||||
|
|
@ -69,6 +71,7 @@ private fun Content(
|
|||
state: MarketsTokenDetailsUM,
|
||||
onBackClick: () -> Unit,
|
||||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
portfolioBlock: @Composable (Modifier) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val backgroundColor = LocalMainBottomSheetColor.current.value
|
||||
|
|
@ -129,6 +132,7 @@ private fun Content(
|
|||
|
||||
tokenMarketDetailsBody(
|
||||
state = state.body,
|
||||
portfolioBlock = portfolioBlock,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -288,6 +292,7 @@ private fun Preview() {
|
|||
),
|
||||
onHeaderSizeChange = {},
|
||||
onBackClick = {},
|
||||
portfolioBlock = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,16 +11,31 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
|
||||
import com.tangem.features.markets.tokenlist.impl.ui.components.UnableToLoadData
|
||||
|
||||
internal fun LazyListScope.tokenMarketDetailsBody(state: MarketsTokenDetailsUM.Body) {
|
||||
internal fun LazyListScope.tokenMarketDetailsBody(
|
||||
state: MarketsTokenDetailsUM.Body,
|
||||
portfolioBlock: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
when (state) {
|
||||
MarketsTokenDetailsUM.Body.Loading -> {
|
||||
loading()
|
||||
item("description-loading") {
|
||||
DescriptionPlaceholder(modifier = Modifier.blockPaddings())
|
||||
}
|
||||
|
||||
item(key = "portfolio") {
|
||||
portfolioBlock(Modifier.blockPaddings())
|
||||
}
|
||||
|
||||
loadingInfoBlocks()
|
||||
}
|
||||
is MarketsTokenDetailsUM.Body.Content -> {
|
||||
if (state.description != null) {
|
||||
description(state.description)
|
||||
}
|
||||
|
||||
item(key = "portfolio") {
|
||||
portfolioBlock(Modifier.blockPaddings())
|
||||
}
|
||||
|
||||
infoBlocksList(state.infoBlocks)
|
||||
}
|
||||
is MarketsTokenDetailsUM.Body.Error -> {
|
||||
|
|
@ -106,11 +121,7 @@ internal fun LazyListScope.infoBlocksList(state: MarketsTokenDetailsUM.Informati
|
|||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.loading() {
|
||||
item("description-loading") {
|
||||
DescriptionPlaceholder(modifier = Modifier.blockPaddings())
|
||||
}
|
||||
|
||||
private fun LazyListScope.loadingInfoBlocks() {
|
||||
item("insights-loading") {
|
||||
InsightsBlockPlaceholder(modifier = Modifier.blockPaddings())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.markets.portfolio.api
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Stable
|
||||
interface MarketsPortfolioComponent : ComposableContentComponent {
|
||||
|
||||
@Serializable
|
||||
data class Params(val tokenId: String)
|
||||
|
||||
fun setTokenNetworks(networks: List<TokenMarketInfo.Network>)
|
||||
|
||||
fun setNoNetworksAvailable()
|
||||
|
||||
interface Factory : ComponentFactory<Params, MarketsPortfolioComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.features.markets.portfolio.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
|
||||
import com.tangem.features.markets.portfolio.impl.model.MarketsPortfolioModel
|
||||
import com.tangem.features.markets.portfolio.impl.ui.MyPortfolio
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@Stable
|
||||
internal class DefaultMarketsPortfolioComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: MarketsPortfolioComponent.Params,
|
||||
) : AppComponentContext by context, MarketsPortfolioComponent {
|
||||
|
||||
private val model: MarketsPortfolioModel = getOrCreateModel(params)
|
||||
|
||||
override fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) = model.setTokenNetworks(networks)
|
||||
override fun setNoNetworksAvailable() = model.setNoNetworksAvailable()
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
MyPortfolio(
|
||||
modifier = modifier,
|
||||
state = MyPortfolioUM.Loading,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MarketsPortfolioComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: MarketsPortfolioComponent.Params,
|
||||
): DefaultMarketsPortfolioComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.markets.portfolio.impl.di
|
||||
|
||||
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
|
||||
import com.tangem.features.markets.portfolio.impl.DefaultMarketsPortfolioComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindMarketsPortfolioComponent(
|
||||
factory: DefaultMarketsPortfolioComponent.Factory,
|
||||
): MarketsPortfolioComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.markets.portfolio.impl.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.markets.portfolio.impl.model.MarketsPortfolioModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface ModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(MarketsPortfolioModel::class)
|
||||
fun provideMarketsPortfolioModel(model: MarketsPortfolioModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.features.markets.portfolio.impl.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class MarketsPortfolioModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private val params = paramsContainer.require<MarketsPortfolioComponent.Params>()
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
}
|
||||
|
||||
fun setNoNetworksAvailable() {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.common.ui.userwallet.UserWalletItem
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.SpacerW6
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.components.block.information.InformationBlock
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.currency.icon.CoinIcon
|
||||
import com.tangem.core.ui.components.rows.ArrowRow
|
||||
import com.tangem.core.ui.components.rows.BlockchainRow
|
||||
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.markets.impl.R
|
||||
import com.tangem.features.markets.portfolio.impl.ui.preview.PreviewAddToPortfolioBSContentProvider
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.AddToPortfolioBSContentUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.SelectNetworkUM
|
||||
|
||||
@Composable
|
||||
internal fun AddToPortfolioBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet<AddToPortfolioBSContentUM>(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
titleText = resourceReference(R.string.markets_add_to_portfolio_button),
|
||||
) {
|
||||
Content(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = TangemTheme.dimens.spacing16),
|
||||
state = config.content as AddToPortfolioBSContentUM,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: AddToPortfolioBSContentUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
UserWalletItem(state.selectedWallet)
|
||||
|
||||
NetworkSelection(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state.selectNetworkUM,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = state.isScanCardNotificationVisible,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
ScanWalletWarning(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
|
||||
PrimaryButtonIconEnd(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.common_continue),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifier) {
|
||||
InformationBlock(
|
||||
modifier = modifier,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.markets_select_network),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = TangemTheme.dimens.spacing14),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CoinIcon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size36),
|
||||
url = state.iconUrl,
|
||||
alpha = 1f,
|
||||
colorFilter = null,
|
||||
fallbackResId = R.drawable.ic_custom_token_44,
|
||||
)
|
||||
SpacerW12()
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.alignByBaseline(),
|
||||
text = state.tokenName,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerW6()
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.alignByBaseline(),
|
||||
text = state.tokenCurrencySymbol,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
state.networks.fastForEachIndexed { index, network ->
|
||||
ArrowRow(
|
||||
isLastItem = index == state.networks.lastIndex,
|
||||
content = {
|
||||
BlockchainRow(
|
||||
modifier = Modifier.padding(
|
||||
end = TangemTheme.dimens.spacing4,
|
||||
),
|
||||
model = with(network) {
|
||||
BlockchainRowUM(
|
||||
name = name,
|
||||
type = type,
|
||||
iconResId = iconResId,
|
||||
isMainNetwork = isMainNetwork,
|
||||
isSelected = isSelected,
|
||||
)
|
||||
},
|
||||
action = {
|
||||
TangemSwitch(
|
||||
checked = network.isSelected,
|
||||
onCheckedChange = {
|
||||
state.onNetworkSwitchClick(network, it)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScanWalletWarning(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.button.disabled,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing10),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.requiredSize(TangemTheme.dimens.size20),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_tangem_24),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.markets_generate_addresses_notification),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview(
|
||||
@PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
AddToPortfolioBottomSheet(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
content = content,
|
||||
onDismissRequest = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun PreviewContent(
|
||||
@PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
Content(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.fillMaxWidth(),
|
||||
state = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun PreviewContentRtl(
|
||||
@PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM,
|
||||
) {
|
||||
TangemThemePreview(rtl = true) {
|
||||
Content(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.fillMaxWidth(),
|
||||
state = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SmallButtonShimmer
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.block.information.InformationBlock
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.markets.impl.R
|
||||
import com.tangem.features.markets.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
||||
|
||||
@Composable
|
||||
internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) {
|
||||
InformationBlock(
|
||||
modifier = modifier,
|
||||
contentHorizontalPadding = 0.dp,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.markets_common_my_portfolio),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
},
|
||||
action = {
|
||||
if (state !is MyPortfolioUM.Tokens) return@InformationBlock
|
||||
|
||||
when (state.buttonState) {
|
||||
MyPortfolioUM.Tokens.AddButtonState.Loading -> {
|
||||
SmallButtonShimmer(
|
||||
modifier = Modifier.size(width = 63.dp, height = TangemTheme.dimens.size18),
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius3),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
SecondarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
text = resourceReference(R.string.markets_add_token),
|
||||
icon = TangemButtonIconPosition.Start(R.drawable.ic_plus_24),
|
||||
onClick = state.onAddClick,
|
||||
enabled = state.buttonState == MyPortfolioUM.Tokens.AddButtonState.Available,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
when (state) {
|
||||
is MyPortfolioUM.Tokens -> TokenList(state = state)
|
||||
is MyPortfolioUM.AddFirstToken -> AddFirstTokenContent(state = state)
|
||||
MyPortfolioUM.Loading -> LoadingPlaceholder()
|
||||
MyPortfolioUM.Unavailable -> UnavailableContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenList(state: MyPortfolioUM.Tokens, modifier: Modifier = Modifier) {
|
||||
Column(modifier) {
|
||||
state.tokens.fastForEachIndexed { index, token ->
|
||||
PortfolioItem(
|
||||
state = token,
|
||||
lastInList = index == state.tokens.size - 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnavailableContent(modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
text = stringResource(R.string.markets_add_to_my_portfolio_unavailable_description),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddFirstTokenContent(state: MyPortfolioUM.AddFirstToken, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = "To start buying, exchanging or receiving this asset, add this token to at least 1 network",
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.markets_add_to_portfolio_button),
|
||||
onClick = state.onAddClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingPlaceholder(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
) {
|
||||
TextShimmer(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
style = TangemTheme.typography.body2,
|
||||
textSizeHeight = true,
|
||||
)
|
||||
TextShimmer(
|
||||
modifier = Modifier.fillMaxWidth(fraction = 0.7f),
|
||||
style = TangemTheme.typography.body2,
|
||||
textSizeHeight = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview(@PreviewParameter(PreviewMyPortfolioUMProvider::class) state: MyPortfolioUM) {
|
||||
TangemThemePreview {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
MyPortfolio(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewRtl(@PreviewParameter(PreviewMyPortfolioUMProvider::class) state: MyPortfolioUM) {
|
||||
TangemThemePreview(rtl = true) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
MyPortfolio(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.currency.icon.CoinIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.res.LocalHapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.markets.impl.R
|
||||
import com.tangem.features.markets.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioTokenUM
|
||||
import com.tangem.utils.StringsSigns
|
||||
|
||||
// TODO add rest of the balance states ([REDACTED_TASK_KEY] [Markets] Portfolio token item UI Improvement)
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifier: Modifier = Modifier) {
|
||||
val hapticManager = LocalHapticManager.current
|
||||
|
||||
Column(modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
hapticManager.perform(TangemHapticEffect.View.ContextClick)
|
||||
state.onClick()
|
||||
},
|
||||
onLongClick = {
|
||||
hapticManager.perform(TangemHapticEffect.View.LongPress)
|
||||
state.onLongTap()
|
||||
},
|
||||
)
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing15,
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Content(state)
|
||||
}
|
||||
|
||||
PortfolioQuickActions(
|
||||
modifier = Modifier.padding(
|
||||
bottom = if (lastInList) {
|
||||
TangemTheme.dimens.spacing12
|
||||
} else {
|
||||
TangemTheme.dimens.spacing24
|
||||
},
|
||||
),
|
||||
isVisible = state.isQuickActionsShown,
|
||||
onActionClick = state.onQuickActionClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.Content(state: PortfolioTokenUM) {
|
||||
// TODO add custom token
|
||||
CoinIcon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size36),
|
||||
url = state.iconUrl,
|
||||
alpha = 1f, // TODO add disabled state
|
||||
colorFilter = null,
|
||||
fallbackResId = R.drawable.ic_custom_token_44,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
|
||||
) {
|
||||
when (state.balanceContent) {
|
||||
is PortfolioTokenUM.BalanceContent.Disabled -> {
|
||||
Disabled(
|
||||
state = state,
|
||||
disabledText = state.balanceContent.text.resolveReference(),
|
||||
)
|
||||
}
|
||||
PortfolioTokenUM.BalanceContent.Loading -> {
|
||||
Loading(state = state)
|
||||
}
|
||||
is PortfolioTokenUM.BalanceContent.TokenBalance -> {
|
||||
TokenBalance(
|
||||
state = state,
|
||||
content = state.balanceContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.TokenBalance(state: PortfolioTokenUM, content: PortfolioTokenUM.BalanceContent.TokenBalance) {
|
||||
val balance = if (content.hidden) {
|
||||
StringsSigns.STARS
|
||||
} else {
|
||||
content.balance
|
||||
}
|
||||
val tokenAmount = if (content.hidden) {
|
||||
StringsSigns.STARS
|
||||
} else {
|
||||
content.tokenAmount
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
text = state.title,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
text = balance,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = state.subtitle,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
Text(
|
||||
text = tokenAmount,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.Loading(state: PortfolioTokenUM) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
text = state.title,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
TextShimmer(
|
||||
modifier = Modifier
|
||||
.width(TangemTheme.dimens.size40)
|
||||
.alignByBaseline(),
|
||||
style = TangemTheme.typography.body2,
|
||||
textSizeHeight = true,
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = state.subtitle,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
TextShimmer(
|
||||
modifier = Modifier
|
||||
.width(TangemTheme.dimens.size40)
|
||||
.alignByBaseline(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
textSizeHeight = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Disabled(state: PortfolioTokenUM, disabledText: String, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
Modifier.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = state.title,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
text = state.subtitle,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = disabledText,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
var quickActionsShown by remember { mutableStateOf(false) }
|
||||
var quickActionsShown2 by remember { mutableStateOf(false) }
|
||||
val sampleToken = PreviewMyPortfolioUMProvider().sampleToken
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
Column {
|
||||
PortfolioItem(
|
||||
state = sampleToken
|
||||
.copy(
|
||||
onClick = {
|
||||
if (quickActionsShown2) {
|
||||
quickActionsShown2 = false
|
||||
}
|
||||
quickActionsShown = quickActionsShown.not()
|
||||
},
|
||||
isQuickActionsShown = quickActionsShown,
|
||||
),
|
||||
lastInList = true,
|
||||
)
|
||||
PortfolioItem(
|
||||
state = sampleToken
|
||||
.copy(
|
||||
onClick = {
|
||||
if (quickActionsShown) {
|
||||
quickActionsShown = false
|
||||
}
|
||||
quickActionsShown2 = quickActionsShown2.not()
|
||||
},
|
||||
isQuickActionsShown = quickActionsShown2,
|
||||
),
|
||||
lastInList = true,
|
||||
)
|
||||
PortfolioItem(
|
||||
state = sampleToken
|
||||
.copy(
|
||||
balanceContent = (
|
||||
sampleToken.balanceContent
|
||||
as PortfolioTokenUM.BalanceContent.TokenBalance
|
||||
)
|
||||
.copy(hidden = true),
|
||||
),
|
||||
lastInList = true,
|
||||
)
|
||||
PortfolioItem(
|
||||
state = sampleToken
|
||||
.copy(
|
||||
balanceContent = PortfolioTokenUM.BalanceContent.Disabled(
|
||||
stringReference("No Address"),
|
||||
),
|
||||
),
|
||||
lastInList = true,
|
||||
)
|
||||
PortfolioItem(
|
||||
state = sampleToken
|
||||
.copy(balanceContent = PortfolioTokenUM.BalanceContent.Loading),
|
||||
lastInList = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.res.LocalHapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.QuickActionUM
|
||||
|
||||
@Composable
|
||||
internal fun PortfolioQuickActions(
|
||||
isVisible: Boolean,
|
||||
onActionClick: (QuickActionUM) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
enter = expandVertically(expandFrom = Alignment.Top),
|
||||
exit = shrinkVertically(shrinkTowards = Alignment.Top),
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
LineSeparator()
|
||||
QuickActionItem(
|
||||
state = QuickActionUM.Buy,
|
||||
onClick = { onActionClick(QuickActionUM.Buy) },
|
||||
)
|
||||
LineSeparator()
|
||||
QuickActionItem(
|
||||
state = QuickActionUM.Exchange,
|
||||
onClick = { onActionClick(QuickActionUM.Exchange) },
|
||||
)
|
||||
LineSeparator()
|
||||
QuickActionItem(
|
||||
state = QuickActionUM.Receive,
|
||||
onClick = { onActionClick(QuickActionUM.Receive) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun AnimatedVisibilityScope.LineSeparator(modifier: Modifier = Modifier) {
|
||||
val lineColor = TangemTheme.colors.stroke.primary
|
||||
val strokeWidth = TangemTheme.dimens.size1
|
||||
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
||||
val verticalPadding = TangemTheme.dimens.spacing2
|
||||
val startPadding = TangemTheme.dimens.spacing28
|
||||
|
||||
val height = TangemTheme.dimens.size16 + verticalPadding * 2
|
||||
|
||||
Canvas(
|
||||
modifier = modifier
|
||||
.animateEnterExit(
|
||||
enter = expandVertically(
|
||||
animationSpec = spring(
|
||||
stiffness = Spring.StiffnessLow,
|
||||
),
|
||||
expandFrom = Alignment.Top,
|
||||
) + fadeIn(),
|
||||
exit = shrinkVertically(
|
||||
spring(
|
||||
stiffness = Spring.StiffnessLow,
|
||||
),
|
||||
shrinkTowards = Alignment.Top,
|
||||
) + fadeOut(),
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.height(height),
|
||||
) {
|
||||
val x = if (isLtr) startPadding.toPx() else size.width - startPadding.toPx()
|
||||
|
||||
drawLine(
|
||||
color = lineColor,
|
||||
start = Offset(x, verticalPadding.toPx()),
|
||||
end = Offset(x, size.height - verticalPadding.toPx()),
|
||||
strokeWidth = strokeWidth.toPx(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun AnimatedVisibilityScope.QuickActionItem(
|
||||
state: QuickActionUM,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hapticManager = LocalHapticManager.current
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersMedium)
|
||||
.clickable {
|
||||
hapticManager.perform(TangemHapticEffect.View.SegmentTick)
|
||||
onClick()
|
||||
}
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing2,
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.animateEnterExit(
|
||||
enter = scaleIn(),
|
||||
exit = scaleOut(),
|
||||
)
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.size(TangemTheme.dimens.size32),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.requiredSize(TangemTheme.dimens.size16),
|
||||
imageVector = ImageVector.vectorResource(id = state.icon),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.button.primary,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateEnterExit(
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
|
||||
) {
|
||||
Text(
|
||||
text = state.title.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
text = state.description.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
var isVisible by remember { mutableStateOf(true) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(680.dp),
|
||||
) {
|
||||
Button(
|
||||
onClick = { isVisible = !isVisible },
|
||||
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(text = "Toggle")
|
||||
}
|
||||
SpacerH4()
|
||||
Box(
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.action),
|
||||
) {
|
||||
PortfolioQuickActions(
|
||||
isVisible = isVisible,
|
||||
onActionClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewRtl() {
|
||||
TangemThemePreview(rtl = true) {
|
||||
Box(modifier = Modifier.background(color = TangemTheme.colors.background.action)) {
|
||||
PortfolioQuickActions(
|
||||
isVisible = true,
|
||||
onActionClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle
|
||||
import com.tangem.core.ui.components.inputrow.InputRowChecked
|
||||
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
|
||||
import com.tangem.core.ui.components.rows.CornersToRound
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
fun TokenActionsBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet<TokenActionsBSContent>(
|
||||
config = config,
|
||||
title = { content ->
|
||||
TangemBottomSheetTitle(content.title)
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
content = { Content(it) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(content: TokenActionsBSContent) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
content.actions.forEachIndexed { index, action ->
|
||||
val cornersToRound = when (index) {
|
||||
0 -> CornersToRound.TOP_2
|
||||
content.actions.lastIndex -> CornersToRound.BOTTOM_2
|
||||
else -> CornersToRound.ZERO
|
||||
}
|
||||
|
||||
DividerContainer(
|
||||
modifier = Modifier
|
||||
.clip(cornersToRound.getShape())
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable { content.onActionClick(action) },
|
||||
showDivider = index != content.actions.lastIndex,
|
||||
) {
|
||||
InputRowChecked(
|
||||
text = action.text,
|
||||
checked = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 640)
|
||||
@Preview(widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview(
|
||||
alwaysShowBottomSheets = true,
|
||||
) {
|
||||
Box(Modifier.background(TangemTheme.colors.background.secondary)) {
|
||||
TokenActionsBottomSheet(
|
||||
TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = {},
|
||||
content = TokenActionsBSContent(
|
||||
title = "Wallet 1",
|
||||
actions = TokenActionsBSContent.Action.entries.toImmutableList(),
|
||||
onActionClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.preview
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.markets.impl.R
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.AddToPortfolioBSContentUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.SelectNetworkUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider<AddToPortfolioBSContentUM> {
|
||||
|
||||
override val values: Sequence<AddToPortfolioBSContentUM>
|
||||
get() = sequenceOf(
|
||||
AddToPortfolioBSContentUM(
|
||||
selectedWallet = UserWalletItemUM(
|
||||
id = UserWalletId("1"),
|
||||
name = stringReference("Wallet 1"),
|
||||
information = stringReference("3 cards, 10,123$"),
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
endIcon = UserWalletItemUM.EndIcon.Arrow,
|
||||
onClick = {},
|
||||
),
|
||||
selectNetworkUM = SelectNetworkUM(
|
||||
tokenId = "etherium",
|
||||
tokenName = "Etherium",
|
||||
tokenCurrencySymbol = "ETH",
|
||||
networks = persistentListOf(
|
||||
BlockchainRowUM(
|
||||
name = "Etherium",
|
||||
type = "MAIN",
|
||||
iconResId = R.drawable.ic_eth_16,
|
||||
isMainNetwork = true,
|
||||
isSelected = true,
|
||||
),
|
||||
BlockchainRowUM(
|
||||
name = "Etherium 2",
|
||||
type = "TEST",
|
||||
iconResId = R.drawable.ic_eth_16,
|
||||
isMainNetwork = false,
|
||||
isSelected = false,
|
||||
),
|
||||
BlockchainRowUM(
|
||||
name = "Etherium 3",
|
||||
type = "TEST",
|
||||
iconResId = R.drawable.ic_eth_16,
|
||||
isMainNetwork = false,
|
||||
isSelected = false,
|
||||
),
|
||||
),
|
||||
onNetworkSwitchClick = { _, _ -> },
|
||||
iconUrl = null,
|
||||
),
|
||||
isScanCardNotificationVisible = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.preview
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioTokenUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider<MyPortfolioUM> {
|
||||
|
||||
override val values: Sequence<MyPortfolioUM>
|
||||
get() = sequenceOf(
|
||||
MyPortfolioUM.Tokens(
|
||||
tokens = persistentListOf(sampleToken, sampleToken),
|
||||
buttonState = MyPortfolioUM.Tokens.AddButtonState.Available,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.Tokens(
|
||||
tokens = persistentListOf(sampleToken, sampleToken.copy(isQuickActionsShown = true)),
|
||||
buttonState = MyPortfolioUM.Tokens.AddButtonState.Unavailable,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.Tokens(
|
||||
tokens = persistentListOf(sampleToken.copy(isQuickActionsShown = true), sampleToken),
|
||||
buttonState = MyPortfolioUM.Tokens.AddButtonState.Loading,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.AddFirstToken(
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.Loading,
|
||||
MyPortfolioUM.Unavailable,
|
||||
)
|
||||
|
||||
val sampleToken = PortfolioTokenUM(
|
||||
id = "",
|
||||
networkId = "",
|
||||
iconUrl = "",
|
||||
balanceContent = PortfolioTokenUM.BalanceContent.TokenBalance(
|
||||
balance = "486,65 \$",
|
||||
tokenAmount = "733,71097 MATIC",
|
||||
hidden = false,
|
||||
),
|
||||
title = "My wallet",
|
||||
subtitle = "XRP Ledger token",
|
||||
onClick = {},
|
||||
onLongTap = {},
|
||||
isQuickActionsShown = false,
|
||||
onQuickActionClick = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.state
|
||||
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
|
||||
internal data class AddToPortfolioBSContentUM(
|
||||
val selectedWallet: UserWalletItemUM,
|
||||
val selectNetworkUM: SelectNetworkUM,
|
||||
val isScanCardNotificationVisible: Boolean,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed class MyPortfolioUM {
|
||||
|
||||
data class Tokens(
|
||||
val tokens: ImmutableList<PortfolioTokenUM>,
|
||||
val buttonState: AddButtonState,
|
||||
val onAddClick: () -> Unit,
|
||||
) : MyPortfolioUM() {
|
||||
|
||||
enum class AddButtonState {
|
||||
Loading,
|
||||
Available,
|
||||
Unavailable,
|
||||
}
|
||||
}
|
||||
|
||||
data class AddFirstToken(
|
||||
val onAddClick: () -> Unit,
|
||||
) : MyPortfolioUM()
|
||||
|
||||
data object Loading : MyPortfolioUM()
|
||||
|
||||
data object Unavailable : MyPortfolioUM()
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
internal data class PortfolioTokenUM(
|
||||
val id: String,
|
||||
val networkId: String,
|
||||
val iconUrl: String,
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val balanceContent: BalanceContent,
|
||||
val onClick: () -> Unit,
|
||||
val onLongTap: () -> Unit,
|
||||
val isQuickActionsShown: Boolean,
|
||||
val onQuickActionClick: (QuickActionUM) -> Unit,
|
||||
) {
|
||||
|
||||
// TODO add rest of the balance states ([REDACTED_TASK_KEY] [Markets] Portfolio token item UI Improvement)
|
||||
@Immutable
|
||||
sealed class BalanceContent {
|
||||
data class TokenBalance( // TODO Add stacking ([REDACTED_TASK_KEY] [Markets] Add staking info to portfolio token item)
|
||||
val balance: String,
|
||||
val tokenAmount: String,
|
||||
val hidden: Boolean,
|
||||
) : BalanceContent()
|
||||
|
||||
data class Disabled(
|
||||
val text: TextReference,
|
||||
) : BalanceContent()
|
||||
|
||||
data object Loading : BalanceContent()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.state
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.markets.impl.R
|
||||
|
||||
@Immutable
|
||||
internal enum class QuickActionUM(
|
||||
val title: TextReference,
|
||||
val description: TextReference,
|
||||
@DrawableRes val icon: Int,
|
||||
) {
|
||||
Buy(
|
||||
title = resourceReference(R.string.common_buy),
|
||||
description = resourceReference(R.string.buy_token_description),
|
||||
icon = R.drawable.ic_plus_24,
|
||||
),
|
||||
Exchange(
|
||||
title = resourceReference(R.string.common_exchange),
|
||||
description = resourceReference(R.string.exсhange_token_description),
|
||||
icon = R.drawable.ic_exchange_vertical_24,
|
||||
),
|
||||
Receive(
|
||||
title = resourceReference(R.string.common_receive),
|
||||
description = resourceReference(R.string.receive_token_description),
|
||||
icon = R.drawable.ic_arrow_down_24,
|
||||
),
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.state
|
||||
|
||||
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class SelectNetworkUM(
|
||||
val tokenId: String,
|
||||
val iconUrl: String?,
|
||||
val tokenName: String,
|
||||
val tokenCurrencySymbol: String,
|
||||
val networks: ImmutableList<BlockchainRowUM>,
|
||||
val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.markets.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class TokenActionsBSContent(
|
||||
val title: String,
|
||||
val actions: ImmutableList<Action>,
|
||||
val onActionClick: (Action) -> Unit,
|
||||
) : TangemBottomSheetConfigContent {
|
||||
|
||||
@Immutable
|
||||
enum class Action(
|
||||
val text: TextReference,
|
||||
) {
|
||||
CopyAddress(text = resourceReference(R.string.common_copy_address)),
|
||||
Receive(text = resourceReference(R.string.common_receive)),
|
||||
Sell(text = resourceReference(R.string.common_sell)),
|
||||
Buy(text = resourceReference(R.string.common_buy)),
|
||||
Send(text = resourceReference(R.string.common_send)),
|
||||
Exchange(text = resourceReference(R.string.common_exchange)),
|
||||
Stake(text = resourceReference(R.string.common_stake)),
|
||||
}
|
||||
}
|
||||
|
|
@ -25,16 +25,19 @@ import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
|||
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
|
||||
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
|
|
@ -54,8 +57,8 @@ import com.tangem.utils.Provider
|
|||
import com.tangem.utils.coroutines.*
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
|
@ -80,12 +83,17 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val submitHashUseCase: SubmitHashUseCase,
|
||||
private val isStakeMoreAvailableUseCase: IsStakeMoreAvailableUseCase,
|
||||
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
|
||||
private val updateDelayedNetworkStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase,
|
||||
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase,
|
||||
private val getAllowanceUseCase: GetAllowanceUseCase,
|
||||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val isApproveNeededUseCase: IsApproveNeededUseCase,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val vibratorHapticManager: VibratorHapticManager,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
|
||||
|
||||
|
|
@ -575,7 +583,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
},
|
||||
ifRight = { txHash ->
|
||||
submitHash(transactionId, txHash)
|
||||
updateStakeBalance()
|
||||
scheduleUpdates()
|
||||
val txUrl = getExplorerTransactionUrlUseCase(
|
||||
txHash = txHash,
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
|
|
@ -608,14 +616,55 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateStakeBalance() {
|
||||
viewModelScope.launch {
|
||||
stakingYieldBalanceUseCase(
|
||||
private fun scheduleUpdates() {
|
||||
coroutineScope.launch {
|
||||
listOf(
|
||||
// we should update network to find pending tx after 1 sec
|
||||
async {
|
||||
fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrencyStatus.currency.network))
|
||||
},
|
||||
// we should update tx history and network for new balances
|
||||
async {
|
||||
updateStakeBalance()
|
||||
},
|
||||
async {
|
||||
updateTxHistory()
|
||||
},
|
||||
async {
|
||||
updateNetworkStatuses()
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateNetworkStatuses() {
|
||||
updateDelayedNetworkStatusUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
delayMillis = BALANCE_UPDATE_DELAY,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateStakeBalance() {
|
||||
stakingYieldBalanceUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateTxHistory() {
|
||||
delay(BALANCE_UPDATE_DELAY)
|
||||
val txHistoryItemsCountEither = getTxHistoryItemsCountUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
)
|
||||
|
||||
txHistoryItemsCountEither.onRight {
|
||||
getTxHistoryItemsUseCase(
|
||||
userWalletId = userWalletId,
|
||||
address = CryptoCurrencyAddress(
|
||||
cryptoCurrencyStatus.currency,
|
||||
cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
),
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
|
@ -630,5 +679,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
private companion object {
|
||||
const val WHAT_IS_STAKING_ARTICLE_URL = "TODO staking"
|
||||
const val ALLOWANCE_UPDATE_DELAY = 10_000L
|
||||
const val BALANCE_UPDATE_DELAY = 11_000L
|
||||
}
|
||||
}
|
||||
|
|
@ -363,8 +363,8 @@ internal class StateBuilder(
|
|||
if (quoteModel.permissionState is PermissionDataState.PermissionLoading) {
|
||||
warnings.add(
|
||||
SwapWarning.TransactionInProgressWarning(
|
||||
title = resourceReference(R.string.warning_express_approval_in_progress_title),
|
||||
description = resourceReference(R.string.warning_express_approval_in_progress_message),
|
||||
title = stringReference("//TODO"),
|
||||
description = stringReference("//TODO"),
|
||||
),
|
||||
)
|
||||
} else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) {
|
||||
|
|
@ -1009,8 +1009,8 @@ internal class StateBuilder(
|
|||
warnings.add(
|
||||
0,
|
||||
SwapWarning.TransactionInProgressWarning(
|
||||
title = resourceReference(R.string.warning_express_approval_in_progress_title),
|
||||
description = resourceReference(R.string.warning_express_approval_in_progress_message),
|
||||
title = stringReference("//TODO"),
|
||||
description = stringReference("//TODO"),
|
||||
),
|
||||
)
|
||||
return uiState.copy(
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
fun UserWallet.getCardsCount(): Int? {
|
||||
return if (isMultiCurrency) {
|
||||
when (val status = scanResponse.card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount + 1
|
||||
is CardDTO.BackupStatus.NoBackup,
|
||||
is CardDTO.BackupStatus.CardLinked,
|
||||
-> 1
|
||||
null -> 1 // Multi-currency wallet without backup function. Example, 4.12
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.domain
|
|||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import arrow.core.Either
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.visa.model.VisaCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
|||
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import timber.log.Timber
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
|
|||
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
|
|||
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
|
|
|
|||
|
|
@ -223,19 +223,16 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
walletManagersFacade.getAddress(
|
||||
walletManagersFacade.getDefaultAddress(
|
||||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
)
|
||||
.find { it.type == AddressType.Default }
|
||||
?.value
|
||||
?.let {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = stateHolder.getSelectedWalletId()))
|
||||
)?.let {
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = stateHolder.getSelectedWalletId()))
|
||||
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.CopyAddress(address = it),
|
||||
)
|
||||
}
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.CopyAddress(address = it),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,9 +88,9 @@ markdown = "0.7.2"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "release-app_5.14-742"
|
||||
tangemBlockchainSdk = "develop-736"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "release-app_5.14-379"
|
||||
tangemCardSdk = "develop-378"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
tangemVico = "2.0.0-alpha.25-tangem16"
|
||||
#tangemVico = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue