Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-08 14:38:23 +04:00
commit f6c438419f
49 changed files with 1344 additions and 774 deletions

View file

@ -22,6 +22,10 @@ sealed class TapError(
object UnknownError : TapError(R.string.send_error_unknown)
open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
data class UnsupportedState(
val stateError: String,
) : TapError(R.string.common_custom_string, listOf("Unsupported state: $stateError"))
object ScanCardError : TapError(R.string.scan_card_error)
object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle)
object NoInternetConnection : TapError(R.string.wallet_notification_no_internet)
@ -38,11 +42,6 @@ sealed class TapError(
data class DustAmount(override val args: List<Any>) : TapError(R.string.send_error_dust_amount_format)
object DustChange : TapError(R.string.send_error_dust_change)
data class UnsupportedState(
val stateError: String,
val customMessage: String = "Unsupported state:",
) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError"))
sealed class WalletManager {
object CreationError : CustomError("Can't create wallet manager")
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)

View file

@ -79,6 +79,7 @@ data class WalletDataModel(
data class Unreachable(
override val errorMessage: String?,
override val amount: BigDecimal = BigDecimal.ZERO,
) : Status() {
override val isErrorStatus: Boolean = true
}

View file

@ -170,7 +170,10 @@ internal fun List<WalletDataModel>.updateWithMissedDerivation(): List<WalletData
internal fun List<WalletDataModel>.updateWithUnreachable(): List<WalletDataModel> {
return this.map { walletData ->
walletData.copy(
status = WalletDataModel.Unreachable(errorMessage = null),
status = WalletDataModel.Unreachable(
errorMessage = null,
amount = walletData.status.amount,
),
)
}
}

View file

@ -5,12 +5,11 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.PrimaryButtonIconLeft
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
import com.tangem.wallet.R
@ -25,13 +24,13 @@ import com.tangem.wallet.R
*/
@Composable
internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, modifier: Modifier = Modifier) {
PrimaryButtonIconLeft(
PrimaryButtonIconStart(
modifier = modifier
.imePadding()
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(id = R.string.common_add),
icon = painterResource(id = R.drawable.ic_plus_24),
iconResId = R.drawable.ic_plus_24,
enabled = model.isEnabled,
onClick = model.onClick,
)

View file

@ -1,48 +1,21 @@
package com.tangem.tap.features.customtoken.legacy.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.BottomSheetScaffold
import androidx.compose.material.BottomSheetScaffoldState
import androidx.compose.material.BottomSheetState
import androidx.compose.material.BottomSheetValue
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.FabPosition
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.rememberBottomSheetScaffoldState
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButtonIconLeft
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.common.form.DataField
import com.tangem.domain.common.form.FieldId
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
import com.tangem.domain.features.addCustomToken.redux.ScreenState
@ -189,12 +162,12 @@ fun Warnings(warnings: List<AddCustomTokenError.Warning>) {
@Composable
private fun AddButton(state: MutableState<AddCustomTokenState>) {
PrimaryButtonIconLeft(
PrimaryButtonIconStart(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(id = R.string.common_add),
icon = painterResource(id = R.drawable.ic_plus_24),
iconResId = R.drawable.ic_plus_24,
enabled = state.value.screenState.addButton.isEnabled,
onClick = { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) },
)

View file

@ -1,23 +1,9 @@
package com.tangem.tap.features.details.ui.common
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.RadioButton
import androidx.compose.material.RadioButtonDefaults
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -25,7 +11,7 @@ import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButtonIconRight
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.res.TangemTheme
import com.tangem.wallet.R
@ -110,13 +96,13 @@ fun EmptyTopBarWithNavigation(
@Composable
fun DetailsMainButton(title: String, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true) {
PrimaryButtonIconRight(
PrimaryButtonIconEnd(
text = title,
enabled = enabled,
onClick = onClick,
modifier = modifier
.fillMaxWidth(),
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
)
}

View file

@ -26,9 +26,9 @@ interface SendScreenActionUi : SendScreenAction
object ReleaseSendState : Action
data class PrepareSendScreen(
val walletManager: WalletManager,
val coinAmount: Amount?,
val coinRate: BigDecimal?,
val walletManager: WalletManager?,
val tokenAmount: Amount? = null,
val tokenRate: BigDecimal? = null,
) : SendScreenAction

View file

@ -85,7 +85,7 @@ private class EmptyReducer : SendInternalReducer {
private class PrepareSendScreenStatesReducer : SendInternalReducer {
override fun handle(action: SendScreenAction, sendState: SendState): SendState {
val prepareAction = action as PrepareSendScreen
val walletManager = action.walletManager!!
val walletManager = action.walletManager
val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!!
val decimals = amountToExtract.decimals
val feePaidInNetworkCurrency = isFeePaidInNetworkCurrency(walletManager.wallet.blockchain)

View file

@ -98,7 +98,7 @@ sealed class WalletAction : Action {
val swapAllowed: Boolean,
) : DialogAction()
data class ChooseCurrency(val amounts: List<Amount>?) : DialogAction()
data class ChooseCurrency(val amounts: List<Amount>) : DialogAction()
data class RussianCardholdersWarningDialog(
val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null,
) : DialogAction()

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.wallet.redux.middlewares
import androidx.core.os.bundleOf
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.AmountType
import com.tangem.common.extensions.guard
@ -129,13 +130,16 @@ class TradeCryptoMiddleware {
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency)))
val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency).guard {
FirebaseCrashlytics.getInstance().recordException(IllegalStateException("WalletManager is null"))
return
}
val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency)
store.dispatchOnMain(
PrepareSendScreen(
coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin),
coinRate = selectedWalletData.fiatRate,
walletManager = walletManager,
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
coinRate = selectedWalletData.fiatRate,
),
)
store.dispatchOnMain(

View file

@ -37,6 +37,8 @@ class WalletDialogsMiddleware {
)
}
is WalletAction.DialogAction.ChooseCurrency -> {
if (action.amounts.isEmpty()) return
store.dispatchDialogShow(
WalletDialog.SelectAmountToSendDialog(
amounts = action.amounts,

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.common.doOnSuccess
@ -32,7 +33,6 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.ifActive
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@ -147,26 +147,76 @@ class WalletMiddleware {
store.dispatchOpenUrl(action.exploreUrl)
}
is WalletAction.Send -> {
val walletStore = walletState.getWalletStore(walletState.selectedCurrency)
val selectedWalletData = walletState.selectedWalletData
val walletManager = walletStore?.walletManager
if (walletStore == null || walletManager == null || selectedWalletData == null) {
val error = TapError.UnsupportedState(
"WalletAction.Send: walletStore or selectedWalletData or walletManager is null",
)
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
store.dispatchErrorNotification(error)
return
}
if (!networkConnectionManager.isOnline) {
store.dispatchErrorNotification(TapError.NoInternetConnection)
return
}
val newAction = prepareSendAction(action.amount, store.state.walletState)
if (newAction is PrepareSendScreen && newAction.walletManager == null) {
store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home))
FirebaseCrashlytics.getInstance().recordException(
IllegalStateException("PrepareSendScreen: walletManager is null"),
)
store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found)
} else {
store.dispatch(newAction)
if (newAction is PrepareSendScreen) {
store.state.walletState.selectedWalletData?.currency?.let { currency ->
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency)))
}
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
val currency = selectedWalletData.currency
val initSendStateAction = if (action.amount == null) {
val sendableAmounts = walletManager.wallet.getSendableAmounts()
if (sendableAmounts.isEmpty()) {
val error = TapError.UnsupportedState("WalletAction.Send: Nothing to send")
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
store.dispatchErrorNotification(error)
return
}
if (walletState.isMultiwalletAllowed) {
val amountToSend = sendableAmounts.find { it.currencySymbol == currency.currencySymbol }
if (amountToSend == null) {
val error = TapError.UnsupportedState("WalletAction.Send: Amount to send is null")
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
store.dispatchErrorNotification(error)
return
}
makeInitSendStateActionByCurrency(
currency = currency,
amount = amountToSend,
walletStore = walletStore,
walletManager = walletManager,
selectedWalletData = selectedWalletData,
)
} else {
val isSingleAmount = sendableAmounts.size == 1
if (isSingleAmount) {
makeInitSendStateActionByAmount(
amount = sendableAmounts.first(),
walletStore = walletStore,
walletManager = walletManager,
selectedWalletData = selectedWalletData,
)
} else {
store.dispatch(WalletAction.DialogAction.ChooseCurrency(sendableAmounts))
return
}
}
} else {
// action.amount received from the ChooseCurrency dialog
makeInitSendStateActionByAmount(
amount = action.amount,
walletManager = walletManager,
walletStore = walletStore,
selectedWalletData = selectedWalletData,
)
}
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency)))
store.dispatch(initSendStateAction)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
}
is WalletAction.ShowSaveWalletIfNeeded -> {
showSaveWalletIfNeeded()
@ -222,6 +272,59 @@ class WalletMiddleware {
}
}
private fun makeInitSendStateActionByAmount(
amount: Amount,
walletStore: WalletStoreModel,
walletManager: WalletManager,
selectedWalletData: WalletDataModel,
): PrepareSendScreen = when (amount.type) {
AmountType.Coin ->
PrepareSendScreen(
walletManager = walletManager,
coinAmount = amount,
coinRate = selectedWalletData.fiatRate,
)
is AmountType.Token -> {
PrepareSendScreen(
walletManager = walletManager,
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
coinRate = walletStore.blockchainWalletData.fiatRate,
tokenAmount = amount,
tokenRate = selectedWalletData.fiatRate,
)
}
AmountType.Reserve -> {
val exception = IllegalStateException("WalletAction.Send: Reserve can't be sent")
FirebaseCrashlytics.getInstance().recordException(exception)
throw exception
}
}
private fun makeInitSendStateActionByCurrency(
currency: Currency,
amount: Amount,
walletStore: WalletStoreModel,
walletManager: WalletManager,
selectedWalletData: WalletDataModel,
): PrepareSendScreen = when (currency) {
is Currency.Blockchain -> {
PrepareSendScreen(
walletManager = walletManager,
coinAmount = amount,
coinRate = selectedWalletData.fiatRate,
)
}
is Currency.Token -> {
PrepareSendScreen(
walletManager = walletManager,
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
coinRate = walletStore.blockchainWalletData.fiatRate,
tokenAmount = amount,
tokenRate = selectedWalletData.fiatRate,
)
}
}
private fun changeSelectedWalletAddress(type: AddressType, state: WalletState) {
val selectedUserWalletId = userWalletsListManager.selectedUserWalletSync?.walletId.guard {
Timber.e("Unable to change selected wallet address, no user wallet selected")
@ -284,72 +387,4 @@ class WalletMiddleware {
}
}
}
private fun prepareSendAction(amount: Amount?, state: WalletState?): Action {
val selectedWalletData = state?.selectedWalletData
val currency = selectedWalletData?.currency
val walletStore = state?.getWalletStore(currency)
return if (amount != null) {
if (amount.type is AmountType.Token) {
prepareSendActionForToken(amount, selectedWalletData, walletStore)
} else {
PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletStore?.walletManager)
}
} else {
val amounts = walletStore?.walletManager?.wallet?.getSendableAmounts()
if (currency != null && state.isMultiwalletAllowed) {
when (currency) {
is Currency.Blockchain -> {
val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency }
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
PrepareSendScreen(
coinAmount = amountToSend,
coinRate = selectedWalletData.fiatRate,
walletManager = walletStore.walletManager,
)
}
is Currency.Token -> {
val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol }
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
prepareSendActionForToken(
amount = amountToSend,
selectedWalletData = selectedWalletData,
walletStore = walletStore,
)
}
}
} else {
val amountsSize = amounts?.size ?: 0
if (amountsSize > 1) {
WalletAction.DialogAction.ChooseCurrency(amounts)
} else {
val amountToSend = amounts?.first()
PrepareSendScreen(
coinAmount = amountToSend,
coinRate = selectedWalletData?.fiatRate,
walletManager = walletStore?.walletManager,
)
}
}
}
}
private fun prepareSendActionForToken(
amount: Amount,
selectedWalletData: WalletDataModel?,
walletStore: WalletStoreModel?,
): PrepareSendScreen {
val coinRate = walletStore?.blockchainWalletData?.fiatRate
val tokenRate = selectedWalletData?.fiatRate
val coinAmount = walletStore?.walletManager?.wallet?.amounts?.get(AmountType.Coin)
return PrepareSendScreen(
coinAmount = coinAmount,
coinRate = coinRate,
walletManager = walletStore?.walletManager,
tokenAmount = amount,
tokenRate = tokenRate,
)
}
}

View file

@ -6,7 +6,7 @@ import com.tangem.tap.common.redux.StateDialog
import com.tangem.wallet.R
sealed interface WalletDialog : StateDialog {
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog
data class SelectAmountToSendDialog(val amounts: List<Amount>) : WalletDialog
object SignedHashesMultiWalletDialog : WalletDialog
data class ChooseTradeActionDialog(
val buyAllowed: Boolean,

View file

@ -5,11 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.*
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.blockchain.common.Amount
import com.tangem.tap.common.extensions.toFormattedString
@ -55,7 +51,7 @@ class AmountToSendBottomSheetDialog(
}
}
class ChooseAmountAdapter : ListAdapter<Amount, ChooseAmountAdapter.AmountViewHolder>(DiffUtilCallback) {
private class ChooseAmountAdapter : ListAdapter<Amount, ChooseAmountAdapter.AmountViewHolder>(DiffUtilCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder {
val binding = ItemWalletAmountToSendBinding.inflate(

View file

@ -3,14 +3,7 @@ package com.tangem.tap.features.walletSelector.ui.components
import androidx.annotation.StringRes
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Icon
@ -28,7 +21,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButtonIconRight
import com.tangem.core.ui.components.SecondaryButtonIconEnd
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.res.TangemTheme
@ -191,11 +184,11 @@ private fun Footer(
onClick = onUnlockClick,
)
}
SecondaryButtonIconRight(
SecondaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.user_wallet_list_add_button),
showProgress = showAddCardProgress,
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
onClick = onAddCardClick,
)
}

View file

@ -1,11 +1,7 @@
package com.tangem.tap.features.welcome.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@ -15,12 +11,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButtonIconRight
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.wallet.R
@ -76,13 +67,13 @@ internal fun WelcomeScreenContent(
onClick = onUnlockClick,
)
SpacerH12()
PrimaryButtonIconRight(
PrimaryButtonIconEnd(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(R.string.welcome_unlock_card),
showProgress = showScanCardProgress,
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
onClick = onScanCardClick,
)
SpacerH16()

View file

@ -73,4 +73,29 @@ val assembleQA by tasks.registering {
dependsOn(assembleInternalQA)
dependsOn(assembleExternalQA)
}
val generateComposeMetrics by tasks.registering {
group = "other"
description = "Build external APK and generates compose metrics to 'build/compose-metrics' directory"
subprojects {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
compilerOptions {
val outputDirectory = "${project.buildDir.absolutePath}/compose_metrics"
// Metrics
freeCompilerArgs.addAll(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=$outputDirectory",
)
// Reports
freeCompilerArgs.addAll(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=$outputDirectory",
)
}
}
}
finalizedBy(assembleExternalQA)
}

View file

@ -473,6 +473,7 @@
<string name="wallet_error_no_account">Аккаунт не создан</string>
<string name="wallet_error_unsupported_blockchain">Эта карта не поддерживается</string>
<string name="wallet_error_unsupported_blockchain_subtitle">Ваша карта Tangem предназначена для работы с другим приложением. Пожалуйста, ознакомьтесь с названием и инструкциями на вашей карте и установите правильное приложение.</string>
<string name="wallet_network_group_title">Сеть %s</string>
<string name="wallet_notification_address_copied">Адрес скопирован в буфер обмена</string>
<string name="wallet_notification_no_internet">Нет соединения с интернетом</string>
<string name="wallet_pending_tx_receiving">Получение</string>

View file

@ -465,6 +465,7 @@
<string name="wallet_error_no_account">Account is not created</string>
<string name="wallet_error_unsupported_blockchain">This card is not supported</string>
<string name="wallet_error_unsupported_blockchain_subtitle">Your Tangem card was made to work with a different application. Please see the name and instructions on your card, and install the correct app</string>
<string name="wallet_network_group_title">%s network</string>
<string name="wallet_notification_address_copied">Address was copied to clipboard</string>
<string name="wallet_notification_no_internet">No internet connection</string>
<string name="wallet_pending_tx_receiving">Receiving</string>

View file

@ -1,34 +1,14 @@
package com.tangem.core.ui.components
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Button
import androidx.compose.material.ButtonColors
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ButtonElevation
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
@ -59,9 +39,9 @@ fun TextButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier,
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A62&t=TmfD6UBHPg9uYfev-4)
* */
@Composable
fun TextButtonIconLeft(
fun TextButtonIconStart(
text: String,
icon: Painter,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
@ -69,7 +49,7 @@ fun TextButtonIconLeft(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Left(icon),
icon = TangemButtonIcon.Start(iconResId),
onClick = onClick,
enabled = enabled,
showProgress = false,
@ -117,9 +97,9 @@ fun PrimaryButton(
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=68%3A47&t=TmfD6UBHPg9uYfev-4)
* */
@Composable
fun PrimaryButtonIconRight(
fun PrimaryButtonIconEnd(
text: String,
icon: Painter,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
showProgress: Boolean = false,
@ -128,7 +108,7 @@ fun PrimaryButtonIconRight(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Right(icon),
icon = TangemButtonIcon.End(iconResId),
onClick = onClick,
colors = TangemButtonsDefaults.primaryButtonColors,
enabled = enabled,
@ -140,9 +120,9 @@ fun PrimaryButtonIconRight(
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A258&t=TmfD6UBHPg9uYfev-4)
* */
@Composable
fun PrimaryButtonIconLeft(
fun PrimaryButtonIconStart(
text: String,
icon: Painter,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
showProgress: Boolean = false,
@ -151,7 +131,7 @@ fun PrimaryButtonIconLeft(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Left(icon),
icon = TangemButtonIcon.Start(iconResId),
onClick = onClick,
colors = TangemButtonsDefaults.primaryButtonColors,
enabled = enabled,
@ -184,9 +164,9 @@ fun SecondaryButton(
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=99%3A50&t=TmfD6UBHPg9uYfev-4)
* */
@Composable
fun SecondaryButtonIconRight(
fun SecondaryButtonIconEnd(
text: String,
icon: Painter,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
showProgress: Boolean = false,
@ -195,7 +175,7 @@ fun SecondaryButtonIconRight(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Right(icon),
icon = TangemButtonIcon.End(iconResId),
onClick = onClick,
colors = TangemButtonsDefaults.secondaryButtonColors,
enabled = enabled,
@ -207,9 +187,9 @@ fun SecondaryButtonIconRight(
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A262&t=TmfD6UBHPg9uYfev-4)
* */
@Composable
fun SecondaryButtonIconLeft(
fun SecondaryButtonIconStart(
text: String,
icon: Painter,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
showProgress: Boolean = false,
@ -218,7 +198,7 @@ fun SecondaryButtonIconLeft(
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Left(icon),
icon = TangemButtonIcon.Start(iconResId),
onClick = onClick,
colors = TangemButtonsDefaults.secondaryButtonColors,
enabled = enabled,
@ -234,7 +214,7 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif
modifier = modifier,
text = text,
textStyle = TangemTheme.typography.subtitle2,
icon = TangemButtonIcon.Right(painterResource(id = R.drawable.ic_chevron_24)),
icon = TangemButtonIcon.End(iconResId = R.drawable.ic_chevron_24),
onClick = onClick,
colors = TangemButtonsDefaults.selectorButtonColors,
showProgress = false,
@ -244,6 +224,78 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif
}
// endregion Other
// region Action
/**
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-305&t=3z98eFnTeyIx5TH5-4)
* */
@Composable
fun RoundedActionButton(
text: String,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Start(iconResId),
onClick = onClick,
enabled = enabled,
showProgress = false,
colors = TangemButtonsDefaults.secondaryButtonColors,
size = TangemButtonSize.RoundedAction,
)
}
/**
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1208-1395&t=3z98eFnTeyIx5TH5-4)
* */
@Composable
fun ActionButton(
text: String,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Start(iconResId),
onClick = onClick,
enabled = enabled,
showProgress = false,
colors = TangemButtonsDefaults.secondaryButtonColors,
size = TangemButtonSize.Action,
)
}
/**
* Same as [RoundedActionButton] but colored in primary background color
* */
@Composable
fun BackgroundActionButton(
text: String,
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
TangemButton(
modifier = modifier,
text = text,
icon = TangemButtonIcon.Start(iconResId),
onClick = onClick,
enabled = enabled,
showProgress = false,
colors = TangemButtonsDefaults.backgroundButtonColors,
size = TangemButtonSize.RoundedAction,
)
}
// endregion Action
// region Defaults
@Suppress("LongParameterList")
@Composable
@ -272,6 +324,7 @@ private fun TangemButton(
elevation = elevation,
shape = size.toShape(),
colors = colors,
contentPadding = size.toContentPadding(icon = icon),
) {
ButtonContent(
text = text,
@ -296,10 +349,10 @@ private fun ButtonContent(
enabled: Boolean,
showProgress: Boolean,
) {
val icon = @Composable { painter: Painter ->
val icon = @Composable { iconResId: Int ->
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
painter = painter,
painter = painterResource(id = iconResId),
tint = colors.contentColor(enabled = enabled).value,
contentDescription = null,
)
@ -320,63 +373,132 @@ private fun ButtonContent(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()),
) {
if (buttonIcon is TangemButtonIcon.Left) {
icon(buttonIcon.painter)
if (buttonIcon is TangemButtonIcon.Start) {
icon(buttonIcon.iconResId)
}
Text(
text = text,
style = textStyle,
color = colors.contentColor(enabled = enabled).value,
maxLines = 1,
)
if (buttonIcon is TangemButtonIcon.Right) {
icon(buttonIcon.painter)
if (buttonIcon is TangemButtonIcon.End) {
icon(buttonIcon.iconResId)
}
}
}
}
sealed interface TangemButtonIcon {
val painter: Painter?
@Immutable
private sealed interface TangemButtonIcon {
val iconResId: Int?
@Immutable
data class Left(override val painter: Painter) : TangemButtonIcon
data class Start(override val iconResId: Int) : TangemButtonIcon
@Immutable
data class Right(override val painter: Painter) : TangemButtonIcon
data class End(override val iconResId: Int) : TangemButtonIcon
object None : TangemButtonIcon {
override val painter: Painter? = null
override val iconResId: Int? = null
}
}
enum class TangemButtonSize {
private enum class TangemButtonSize {
Default,
Text,
Selector,
Action,
RoundedAction,
}
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toHeightDp(): Dp = when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.size48
TangemButtonSize.Text -> TangemTheme.dimens.size40
TangemButtonSize.Selector -> TangemTheme.dimens.size24
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> TangemTheme.dimens.size36
}
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toShape(): Shape = when (this) {
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge
}
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toIconPadding(): Dp = when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
TangemButtonSize.Selector -> 0.dp
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> TangemTheme.dimens.spacing8
}
object TangemButtonsDefaults {
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toContentPadding(icon: TangemButtonIcon): PaddingValues {
val horizontalPadding = this.toHorizontalContentPadding(icon = icon)
return when (this) {
TangemButtonSize.Default -> PaddingValues(
top = TangemTheme.dimens.spacing14,
bottom = TangemTheme.dimens.spacing14,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
TangemButtonSize.Text -> PaddingValues(
top = TangemTheme.dimens.spacing10,
bottom = TangemTheme.dimens.spacing10,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
TangemButtonSize.Selector -> PaddingValues(
top = TangemTheme.dimens.spacing0_5,
bottom = TangemTheme.dimens.spacing0_5,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> PaddingValues(
top = TangemTheme.dimens.spacing8,
bottom = TangemTheme.dimens.spacing8,
start = horizontalPadding.first,
end = horizontalPadding.second,
)
}
}
@Composable
@ReadOnlyComposable
private fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIcon): Pair<Dp, Dp> {
return when (this) {
TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
TangemButtonSize.Text -> when (icon) {
is TangemButtonIcon.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
is TangemButtonIcon.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
is TangemButtonIcon.End -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing14
}
TangemButtonSize.Selector -> TangemTheme.dimens.spacing0_5 to TangemTheme.dimens.spacing0_5
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> when (icon) {
is TangemButtonIcon.None -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing24
is TangemButtonIcon.Start -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing24
is TangemButtonIcon.End -> TangemTheme.dimens.spacing24 to TangemTheme.dimens.spacing16
}
}
}
private object TangemButtonsDefaults {
val elevation: ButtonElevation
@Composable get() = ButtonDefaults
.elevation(
@ -385,7 +507,9 @@ object TangemButtonsDefaults {
)
val primaryButtonColors: ButtonColors
@Composable get() = TangemButtonColors(
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.button.primary,
contentColor = TangemTheme.colors.text.primary2,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
@ -393,7 +517,9 @@ object TangemButtonsDefaults {
)
val secondaryButtonColors: ButtonColors
@Composable get() = TangemButtonColors(
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.button.secondary,
contentColor = TangemTheme.colors.text.primary1,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
@ -401,7 +527,9 @@ object TangemButtonsDefaults {
)
val defaultTextButtonColors: ButtonColors
@Composable get() = TangemButtonColors(
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = Color.Transparent,
contentColor = TangemTheme.colors.text.secondary,
disabledBackgroundColor = Color.Transparent,
@ -409,7 +537,9 @@ object TangemButtonsDefaults {
)
val warningTextButtonColors: ButtonColors
@Composable get() = TangemButtonColors(
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = Color.Transparent,
contentColor = TangemTheme.colors.text.warning,
disabledBackgroundColor = Color.Transparent,
@ -417,12 +547,24 @@ object TangemButtonsDefaults {
)
val selectorButtonColors: ButtonColors
@Composable get() = TangemButtonColors(
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = Color.Transparent,
contentColor = TangemTheme.colors.text.tertiary,
disabledBackgroundColor = Color.Transparent,
disabledContentColor = TangemTheme.colors.text.disabled,
)
val backgroundButtonColors: ButtonColors
@Composable
@ReadOnlyComposable
get() = TangemButtonColors(
backgroundColor = TangemTheme.colors.background.primary,
contentColor = TangemTheme.colors.text.primary1,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
)
}
@Immutable
@ -464,17 +606,17 @@ private fun PrimaryButtonSample(modifier: Modifier = Modifier) {
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
PrimaryButtonIconRight(
PrimaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
PrimaryButtonIconLeft(
PrimaryButtonIconStart(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
@ -485,18 +627,18 @@ private fun PrimaryButtonSample(modifier: Modifier = Modifier) {
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
PrimaryButtonIconRight(
PrimaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
enabled = false,
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
PrimaryButtonIconLeft(
PrimaryButtonIconStart(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
enabled = false,
onClick = { /* no-op */ },
)
@ -538,17 +680,17 @@ private fun SecondaryButtonSample(modifier: Modifier = Modifier) {
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
SecondaryButtonIconRight(
SecondaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
SecondaryButtonIconLeft(
SecondaryButtonIconStart(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
@ -559,18 +701,18 @@ private fun SecondaryButtonSample(modifier: Modifier = Modifier) {
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
SecondaryButtonIconRight(
SecondaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
enabled = false,
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
SecondaryButtonIconLeft(
SecondaryButtonIconStart(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
enabled = false,
onClick = { /* no-op */ },
)
@ -604,9 +746,9 @@ private fun TextButtonSample(modifier: Modifier = Modifier) {
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
TextButtonIconLeft(
TextButtonIconStart(
text = "Enabled",
icon = painterResource(id = R.drawable.ic_plus_24),
iconResId = R.drawable.ic_plus_24,
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
@ -616,9 +758,9 @@ private fun TextButtonSample(modifier: Modifier = Modifier) {
onClick = { /* no-op */ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
TextButtonIconLeft(
TextButtonIconStart(
text = "Enabled",
icon = painterResource(id = R.drawable.ic_plus_24),
iconResId = R.drawable.ic_plus_24,
enabled = false,
onClick = { /* no-op */ },
)
@ -650,4 +792,67 @@ private fun TextButtonPreview_Dark() {
TextButtonSample()
}
}
@Composable
private fun ActionButtonSample(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.background(TangemTheme.colors.background.primary),
) {
RoundedActionButton(
text = "Send",
iconResId = R.drawable.ic_arrow_up_24,
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
ActionButton(
text = "Send",
iconResId = R.drawable.ic_arrow_up_24,
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
BackgroundActionButton(
text = "Send",
iconResId = R.drawable.ic_arrow_up_24,
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
RoundedActionButton(
text = "Send",
iconResId = R.drawable.ic_arrow_up_24,
enabled = false,
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
ActionButton(
text = "Send",
iconResId = R.drawable.ic_arrow_up_24,
enabled = false,
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
BackgroundActionButton(
text = "Send",
iconResId = R.drawable.ic_arrow_up_24,
enabled = false,
onClick = { /* [REDACTED_TODO_COMMENT]*/ },
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun ActionButtonPreview_Light() {
TangemTheme {
ActionButtonSample()
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun ActionButtonPreview_Dark() {
TangemTheme(isDark = true) {
ActionButtonSample()
}
}
// endregion Preview

View file

@ -14,7 +14,7 @@ import com.valentinilk.shimmer.shimmer
* Rectangle shimmer item with rounded shape from DS
*/
@Composable
fun ShimmerRectangle(modifier: Modifier = Modifier) {
fun RectangleShimmer(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.shimmer()
@ -51,7 +51,7 @@ private fun ShimmersPreview() {
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
) {
ShimmerRectangle(
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size72,
height = TangemTheme.dimens.size12,

View file

@ -3,15 +3,7 @@ package com.tangem.core.ui.components
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
@ -143,9 +135,9 @@ private fun SecondaryButtonForResultScreen(
@DrawableRes secondaryButtonIcon: Int? = null,
) {
if (secondaryButtonIcon != null) {
SecondaryButtonIconLeft(
SecondaryButtonIconStart(
text = stringResource(id = secondaryButtonText),
icon = painterResource(id = secondaryButtonIcon),
iconResId = secondaryButtonIcon,
onClick = onSecondaryButtonClick,
modifier = Modifier.fillMaxWidth(),
)

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:fillType="evenOdd"
android:pathData="M7,8H5V10H7V8ZM13,8H11V10H13V8ZM17,8H19V10H17V8ZM7,14H5V16H7V14ZM11,14H13V16H11V14ZM19,14H17V16H19V14Z" />
</vector>

View file

@ -4,11 +4,10 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.R
import com.tangem.core.ui.components.OutlineTextField
import com.tangem.core.ui.components.PrimaryButtonIconLeft
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description
@ -57,11 +56,11 @@ fun CheckSeedPhraseScreen(state: CheckSeedPhraseState, modifier: Modifier = Modi
}
OnboardingActionBlock(
firstActionContent = {
PrimaryButtonIconLeft(
PrimaryButtonIconStart(
modifier = Modifier
.fillMaxWidth(),
text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet),
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
enabled = state.buttonCreateWallet.enabled,
showProgress = state.buttonCreateWallet.showProgress,
onClick = state.buttonCreateWallet.onClick,

View file

@ -9,12 +9,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.unit.IntOffset
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.PrimaryButtonIconLeft
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.core.ui.components.TangemTextFieldsDefault
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.onboarding.R
@ -56,11 +55,11 @@ fun ImportSeedPhraseScreen(state: ImportSeedPhraseState, modifier: Modifier = Mo
Box {
OnboardingActionBlock(
firstActionContent = {
PrimaryButtonIconLeft(
PrimaryButtonIconStart(
modifier = Modifier
.fillMaxWidth(),
text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet),
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
enabled = state.buttonCreateWallet.enabled,
showProgress = state.buttonCreateWallet.showProgress,
onClick = state.buttonCreateWallet.onClick,

View file

@ -11,7 +11,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.PrimaryButtonIconLeft
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.onboarding.R
@ -46,11 +46,11 @@ fun IntroScreen(state: IntroState, modifier: Modifier = Modifier) {
Box {
OnboardingActionBlock(
firstActionContent = {
PrimaryButtonIconLeft(
PrimaryButtonIconStart(
modifier = Modifier
.fillMaxWidth(),
text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet),
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
enabled = state.buttonCreateWallet.enabled,
showProgress = state.buttonCreateWallet.showProgress,
onClick = state.buttonCreateWallet.onClick,

View file

@ -74,9 +74,9 @@ fun SwapPermissionBottomSheetContent(data: SwapPermissionState.ReadyForRequest,
SpacerH28()
PrimaryButtonIconRight(
PrimaryButtonIconEnd(
text = stringResource(id = R.string.swapping_permission_buttons_approve),
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
modifier = Modifier.fillMaxWidth(),
onClick = data.approveButton.onClick,
)

View file

@ -345,10 +345,10 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
)
}
else -> {
PrimaryButtonIconRight(
PrimaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.swapping_swap),
icon = painterResource(id = R.drawable.ic_tangem_24),
iconResId = R.drawable.ic_tangem_24,
enabled = state.swapButton.enabled,
showProgress = state.swapButton.loading,
onClick = state.swapButton.onClick,

View file

@ -22,6 +22,7 @@ dependencies {
implementation(deps.compose.ui.tooling)
implementation(deps.compose.shimmer)
implementation(deps.compose.accompanist.systemUiController)
implementation(deps.kotlin.immutable.collections)
/** DI */
implementation(deps.hilt.android)

View file

@ -0,0 +1,161 @@
package com.tangem.feature.wallet.presentation.common
import com.tangem.core.ui.R
import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import kotlinx.collections.immutable.persistentListOf
import java.util.UUID
internal object WalletPreviewData {
val walletCardContent = WalletCardState.Content(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
balance = "8923,05 $",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardLoading = WalletCardState.Loading(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardHiddenContent = WalletCardState.HiddenContent(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardError = WalletCardState.Error(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletScreenState = WalletStateHolder(
onBackClick = {},
headerConfig = WalletStateHolder.HeaderConfig(
wallets = persistentListOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError),
onScanCardClick = {},
onMoreClick = {},
),
)
val tokenItemVisibleState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
tokenIconUrl = null,
tokenIconResId = R.drawable.img_polygon_22,
networkIconResId = R.drawable.img_polygon_22,
name = "Polygon",
amount = "5,412 MATIC",
hasPending = true,
tokenOptions = TokenOptionsState.Visible(
fiatAmount = "321 $",
priceChange = TokenOptionsState.PriceChange(
valuePercent = "2%",
type = TokenOptionsState.PriceChange.Type.UP,
),
),
)
val tokenItemHiddenState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
tokenIconUrl = null,
tokenIconResId = R.drawable.img_polygon_22,
networkIconResId = R.drawable.img_polygon_22,
name = "Polygon",
amount = "5,412 MATIC",
hasPending = true,
tokenOptions = TokenOptionsState.Hidden(
priceChange = TokenOptionsState.PriceChange(
valuePercent = "2%",
type = TokenOptionsState.PriceChange.Type.UP,
),
),
)
val tokenItemDragState = TokenItemState.Draggable(
id = UUID.randomUUID().toString(),
tokenIconUrl = null,
tokenIconResId = R.drawable.img_polygon_22,
networkIconResId = R.drawable.img_polygon_22,
name = "Polygon",
fiatAmount = "3 172,14 $",
)
val tokenItemUnreachableState = TokenItemState.Unreachable(
id = UUID.randomUUID().toString(),
tokenIconUrl = null,
tokenIconResId = R.drawable.img_polygon_22,
networkIconResId = R.drawable.img_polygon_22,
name = "Polygon",
)
val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString())
private val draggableTokenList = persistentListOf(
tokenItemDragState.copy(
id = "token_1",
name = "Ethereum",
tokenIconResId = R.drawable.img_eth_22,
networkIconResId = null,
fiatAmount = "3 172,14 $",
),
tokenItemDragState.copy(
id = "token_2",
networkIconResId = R.drawable.img_eth_22,
fiatAmount = "803,65 $",
),
tokenItemDragState.copy(
id = "token_3",
name = "USDT",
tokenIconResId = R.drawable.img_arbitrum_22,
networkIconResId = R.drawable.img_eth_22,
fiatAmount = "88,01 $",
),
)
val networkGroup = NetworkGroupState.Content(
id = UUID.randomUUID().toString(),
networkName = "Ethereum",
tokens = persistentListOf(
tokenItemVisibleState.copy(
id = "token_1",
name = "Ethereum",
tokenIconResId = R.drawable.img_eth_22,
networkIconResId = null,
amount = "1,89340821 ETH",
),
tokenItemVisibleState.copy(
id = "token_2",
networkIconResId = R.drawable.img_eth_22,
amount = "733,71097 MATIC",
),
tokenItemVisibleState.copy(
id = "token_3",
name = "USDT",
tokenIconResId = R.drawable.img_arbitrum_22,
networkIconResId = R.drawable.img_eth_22,
amount = "0,25404523 ARB",
),
),
)
val draggableNetworkGroup = NetworkGroupState.Draggable(
id = UUID.randomUUID().toString(),
networkName = "Ethereum",
tokens = draggableTokenList,
)
}

View file

@ -0,0 +1,79 @@
package com.tangem.feature.wallet.presentation.common.component
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState
@Composable
internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Row(
modifier = Modifier
.background(TangemTheme.colors.background.primary)
.padding(horizontal = TangemTheme.dimens.spacing12)
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size48),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(id = R.string.wallet_network_group_title, state.networkName),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
if (state is NetworkGroupState.Draggable) {
Icon(
painter = painterResource(id = R.drawable.ic_group_drop_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
Column {
state.tokens.forEach { token ->
key(token.id) {
TokenItem(state = token)
}
}
}
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) {
TangemTheme {
NetworkGroupItem(group)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) {
TangemTheme(isDark = true) {
NetworkGroupItem(group)
}
}
private class NetworkGroupProvider : CollectionPreviewParameterProvider<NetworkGroupState>(
collection = listOf(
WalletPreviewData.networkGroup,
WalletPreviewData.draggableNetworkGroup,
),
)
// endregion Preview

View file

@ -0,0 +1,412 @@
package com.tangem.feature.wallet.presentation.common.component
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstrainedLayoutReference
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.ConstraintLayoutScope
import androidx.constraintlayout.compose.Dimension
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemTypography
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
private const val DOTS = "•••"
@Composable
internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) {
when (state) {
is TokenItemState.Content -> ContentTokenItem(state, modifier)
is TokenItemState.Loading -> LoadingTokenItem(modifier)
is TokenItemState.Draggable -> DraggableTokenItem(state, modifier)
is TokenItemState.Unreachable -> UnreachableTokenItem(state, modifier)
}
}
@Composable
private fun ContentTokenItem(content: TokenItemState.Content, modifier: Modifier = Modifier) {
InternalTokenItem(
modifier = modifier,
name = content.name,
tokenIconUrl = content.tokenIconUrl,
tokenIconResId = content.tokenIconResId,
networkIconResId = content.networkIconResId,
amount = if (content.tokenOptions is TokenOptionsState.Hidden) DOTS else content.amount,
hasPending = content.hasPending,
options = { ref ->
TokenOptionsBlock(
modifier = Modifier.constrainAsOptionsItem(scope = this, ref),
state = content.tokenOptions,
)
},
)
}
@Composable
internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modifier = Modifier) {
InternalTokenItem(
modifier = modifier,
name = state.name,
tokenIconUrl = state.tokenIconUrl,
tokenIconResId = state.tokenIconResId,
networkIconResId = state.networkIconResId,
amount = state.fiatAmount,
hasPending = false,
options = { ref ->
Icon(
modifier = Modifier.constrainAsOptionsItem(scope = this, ref),
painter = painterResource(id = R.drawable.ic_drag_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
},
)
}
@Composable
internal fun UnreachableTokenItem(state: TokenItemState.Unreachable, modifier: Modifier = Modifier) {
InternalTokenItem(
modifier = modifier,
name = state.name,
tokenIconUrl = state.tokenIconUrl,
tokenIconResId = state.tokenIconResId,
networkIconResId = state.networkIconResId,
amount = null,
hasPending = false,
options = { ref ->
Text(
modifier = Modifier.constrainAsOptionsItem(scope = this, ref),
text = "Unreachable", // TODO (conform this text)
style = TangemTypography.body2,
color = TangemTheme.colors.text.tertiary,
)
},
)
}
@Composable
private fun LoadingTokenItem(modifier: Modifier = Modifier) {
BaseSurface(modifier) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing4,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size72,
height = TangemTheme.dimens.size12,
),
)
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size50,
height = TangemTheme.dimens.size12,
),
)
}
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size40,
height = TangemTheme.dimens.size12,
),
)
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size40,
height = TangemTheme.dimens.size12,
),
)
}
}
}
}
}
/**
* Block for end part of token item
* shows status is reachable, is drag, hidden or show balance
*/
@Composable
private fun TokenOptionsBlock(state: TokenOptionsState, modifier: Modifier = Modifier) {
when (state) {
is TokenOptionsState.Visible -> {
TokenFiatPercentageBlock(
modifier = modifier,
fiatAmount = state.fiatAmount,
priceChange = state.priceChange,
)
}
is TokenOptionsState.Hidden -> {
TokenFiatPercentageBlock(
modifier = modifier,
fiatAmount = DOTS,
priceChange = state.priceChange,
)
}
}
}
@Suppress("LongParameterList")
@Composable
private fun InternalTokenItem(
name: String,
tokenIconUrl: String?,
@DrawableRes tokenIconResId: Int,
@DrawableRes networkIconResId: Int?,
amount: String?,
hasPending: Boolean,
options: @Composable ConstraintLayoutScope.(ref: ConstrainedLayoutReference) -> Unit,
modifier: Modifier = Modifier,
) {
BaseSurface(modifier) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing4,
),
) {
val (iconItem, tokenNameItem, optionsItem) = createRefs()
TokenIcon(
modifier = Modifier.constrainAs(iconItem) {
centerVerticallyTo(parent)
start.linkTo(parent.start)
},
tokenIconUrl = tokenIconUrl,
tokenIconResId = tokenIconResId,
networkIconRes = networkIconResId,
)
TokenTitleAmountBlock(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing12)
.constrainAs(tokenNameItem) {
centerVerticallyTo(parent)
start.linkTo(iconItem.end)
end.linkTo(optionsItem.start)
width = Dimension.fillToConstraints
},
title = name,
amount = amount,
hasPending = hasPending,
)
options(optionsItem)
}
}
}
@Stable
private fun Modifier.constrainAsOptionsItem(scope: ConstraintLayoutScope, ref: ConstrainedLayoutReference): Modifier {
return with(scope) {
this@constrainAsOptionsItem.constrainAs(ref) {
centerVerticallyTo(parent)
end.linkTo(parent.end)
}
}
}
@Composable
private fun BaseSurface(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
content: @Composable () -> Unit,
) {
Surface(
modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68),
color = TangemTheme.colors.background.primary,
onClick = onClick ?: {},
enabled = onClick != null,
) {
content()
}
}
@Composable
private fun TokenTitleAmountBlock(title: String, amount: String?, hasPending: Boolean, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
) {
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
Text(
text = title,
style = TangemTypography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
if (hasPending) {
Image(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = R.drawable.img_loader_15),
contentDescription = null,
)
}
}
if (!amount.isNullOrBlank()) {
Text(
text = amount,
style = TangemTypography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Composable
private fun TokenFiatPercentageBlock(
fiatAmount: String,
priceChange: TokenOptionsState.PriceChange,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier.requiredWidth(IntrinsicSize.Max)) {
Text(
modifier = Modifier.align(Alignment.End),
text = fiatAmount,
style = TangemTypography.body2,
color = TangemTheme.colors.text.primary1,
)
SpacerH2()
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
val iconChangeArrow: Int
val changeTextColor: Color
when (priceChange.type) {
TokenOptionsState.PriceChange.Type.UP -> {
iconChangeArrow = R.drawable.img_arrow_up_8
changeTextColor = TangemTheme.colors.text.accent
}
TokenOptionsState.PriceChange.Type.DOWN -> {
iconChangeArrow = R.drawable.img_arrow_down_8
changeTextColor = TangemTheme.colors.text.warning
}
}
Image(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = iconChangeArrow),
contentDescription = null,
)
SpacerW4()
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = priceChange.valuePercent,
style = TangemTypography.body2,
color = changeTextColor,
)
}
}
}
@Composable
private fun TokenIcon(
tokenIconUrl: String?,
modifier: Modifier = Modifier,
@DrawableRes tokenIconResId: Int? = null,
@DrawableRes networkIconRes: Int? = null,
) {
Box(
modifier = modifier
.padding(end = TangemTheme.dimens.spacing16)
.size(TangemTheme.dimens.size42),
) {
val tokenImageModifier = Modifier
.align(Alignment.BottomStart)
.size(TangemTheme.dimens.size36)
val data = if (tokenIconUrl.isNullOrEmpty()) tokenIconResId else tokenIconUrl
SubcomposeAsyncImage(
modifier = tokenImageModifier,
model = ImageRequest.Builder(LocalContext.current)
.data(data)
.crossfade(true)
.build(),
loading = { CircleShimmer(modifier = tokenImageModifier) },
contentDescription = null,
)
if (networkIconRes != null) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(TangemTheme.dimens.size18)
.background(color = Color.White, shape = CircleShape),
contentAlignment = Alignment.Center,
) {
Image(
modifier = Modifier.padding(all = 0.5.dp),
painter = painterResource(id = networkIconRes),
contentDescription = null,
)
}
}
}
}
// region preview
@Preview
@Composable
private fun Preview_Tokens_LightTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
TangemTheme(isDark = false) {
TokenItem(state)
}
}
@Preview
@Composable
private fun Preview_Tokens_DarkTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
TangemTheme(isDark = true) {
TokenItem(state)
}
}
private class TokenConfigProvider : CollectionPreviewParameterProvider<TokenItemState>(
collection = listOf(
WalletPreviewData.tokenItemVisibleState,
WalletPreviewData.tokenItemUnreachableState,
WalletPreviewData.tokenItemDragState,
WalletPreviewData.tokenItemHiddenState,
WalletPreviewData.loadingTokenItemState,
),
)
// endregion preview

View file

@ -0,0 +1,111 @@
package com.tangem.feature.wallet.presentation.common.state
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState.PriceChange.Type
/** Token item state */
@Immutable
internal sealed interface TokenItemState {
/** Token id */
val id: String
/**
* Loading token state
*
* @property id token id
*/
data class Loading(override val id: String) : TokenItemState
/**
* Content token state
*
* @property id token id
* @property tokenIconUrl token icon url
* @property tokenIconResId token icon resource id
* @property networkIconResId network icon resource id, may be null if it is a coin
* @property name token name
* @property amount amount of token
* @property hasPending pending tx in blockchain
* @property tokenOptions state for token options
*/
data class Content(
override val id: String,
val tokenIconUrl: String?,
@DrawableRes val tokenIconResId: Int,
@DrawableRes val networkIconResId: Int?,
val name: String,
val amount: String,
val hasPending: Boolean,
val tokenOptions: TokenOptionsState,
) : TokenItemState
/**
* Draggable token state
*
* @property id token id
* @property tokenIconUrl token icon url
* @property tokenIconResId token icon resource id
* @property networkIconResId network icon resource id, may be null if it is a coin
* @property name token name
*/
data class Draggable(
override val id: String,
val tokenIconUrl: String?,
@DrawableRes val tokenIconResId: Int,
@DrawableRes val networkIconResId: Int?,
val name: String,
val fiatAmount: String,
) : TokenItemState
/**
* Unreachable token state
*
* @property id token id
* @property tokenIconUrl token icon url
* @property tokenIconResId token icon resource id
* @property networkIconResId network icon resource id, may be null if it is a coin
* @property name token name
*/
data class Unreachable(
override val id: String,
val tokenIconUrl: String?,
@DrawableRes val tokenIconResId: Int,
@DrawableRes val networkIconResId: Int?,
val name: String,
) : TokenItemState
/** Token options state */
sealed interface TokenOptionsState {
/**
* Visible token options state
*
* @property fiatAmount fiat amount of token
* @property priceChange value of price changing
*/
data class Visible(val fiatAmount: String, val priceChange: PriceChange) : TokenOptionsState
/**
* Hidden token options state
*
* @property priceChange value of price changing
*/
data class Hidden(val priceChange: PriceChange) : TokenOptionsState
/**
* Price changing option state
*
* @property valuePercent value in percent
* @property type type [Type]
*/
data class PriceChange(val valuePercent: String, val type: Type) {
/** Price changing type */
enum class Type {
UP, DOWN
}
}
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.feature.wallet.presentation.common.state
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal sealed interface TokenListState {
data class GroupedByNetwork(
val groups: ImmutableList<NetworkGroupState>,
) : TokenListState
data class Ungrouped(
val tokens: ImmutableList<TokenItemState>,
) : TokenListState
}
@Immutable
internal sealed interface NetworkGroupState {
val id: String
val networkName: String
val tokens: ImmutableList<TokenItemState>
data class Draggable(
override val id: String,
override val networkName: String,
override val tokens: ImmutableList<TokenItemState.Draggable>,
) : NetworkGroupState
data class Content(
override val id: String,
override val networkName: String,
override val tokens: ImmutableList<TokenItemState>,
) : NetworkGroupState
}

View file

@ -10,8 +10,8 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.WalletFragment
import com.tangem.feature.wallet.presentation.ui.WalletScreen
import com.tangem.feature.wallet.presentation.viewmodels.WalletViewModel
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */

View file

@ -1,13 +1,18 @@
package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import com.tangem.features.wallet.navigation.WalletRouter
/**
* Interface of inner wallet feature router
*
* Annotated as [Stable] because implementation of this interface passed as argument to [Initialize] method by compose
* compiler
*
[REDACTED_AUTHOR]
*/
@Stable
internal interface InnerWalletRouter : WalletRouter {
/** Initialize router */

View file

@ -1,9 +0,0 @@
package com.tangem.feature.wallet.presentation.state
internal enum class TokenUIState {
LOADED, LOADING
}
internal enum class TokenOptionsUIState {
VISIBLE, HIDDEN, DRAG, UNREACHABLE
}

View file

@ -1,39 +0,0 @@
package com.tangem.feature.wallet.presentation.state
import androidx.annotation.DrawableRes
/**
* Token config
*
* @property name of token/coin
* @property amount of token
* @property fiatAmount amount in fiat
* @property priceChange value of price changing
* @property iconUrl
* @property icon token/coin icon
* @property networkIcon
* @property tokenUIState token state [TokenUIState] loading etc.
* @property tokenOptionsUIState state for token options like 'unreachable', 'hidden' etc
* @property hasPending pending tx in blockchain
*/
internal data class TokenV2Config(
val name: String,
val amount: String,
val fiatAmount: String,
val priceChange: PriceChange,
val iconUrl: String?,
@DrawableRes val icon: Int?,
@DrawableRes val networkIcon: Int,
val tokenUIState: TokenUIState,
val tokenOptionsUIState: TokenOptionsUIState,
val hasPending: Boolean,
)
data class PriceChange(
val valuePercent: String,
val type: PriceChangeType,
)
enum class PriceChangeType {
UP, DOWN
}

View file

@ -1,352 +0,0 @@
package com.tangem.feature.wallet.presentation.ui
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemTypography
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.state.*
private const val DOTS = "•••"
@Composable
internal fun TokenItemV2(config: TokenV2Config) {
when (config.tokenUIState) {
TokenUIState.LOADED -> LoadedTokenState(config)
TokenUIState.LOADING -> LoadingTokenState()
}
}
@Composable
private fun LoadedTokenState(config: TokenV2Config) {
BaseSurface {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing4,
),
) {
val (iconItem, tokenNameItem, fiatItem) = createRefs()
TokenIcon(
modifier = Modifier.constrainAs(iconItem) {
centerVerticallyTo(parent)
start.linkTo(parent.start)
},
tokenIconUrl = config.iconUrl,
icon = config.icon,
networkIconRes = config.networkIcon,
)
TokenTitleAmountBlock(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing12)
.constrainAs(tokenNameItem) {
centerVerticallyTo(parent)
start.linkTo(iconItem.end)
end.linkTo(fiatItem.start)
width = Dimension.fillToConstraints
},
title = config.name,
amount = config.amount,
hasPending = config.hasPending,
)
TokenOptionsBlock(
config = config,
modifier = Modifier.constrainAs(fiatItem) {
centerVerticallyTo(parent)
end.linkTo(parent.end)
},
)
}
}
}
/**
* Block for end part of token item
* shows status is reachable, is drag, hidden or show balance
*/
@Composable
private fun TokenOptionsBlock(config: TokenV2Config, modifier: Modifier = Modifier) {
when (config.tokenOptionsUIState) {
TokenOptionsUIState.VISIBLE -> TokenFiatPercentageBlock(
modifier = modifier,
fiatAmount = config.fiatAmount,
priceChange = config.priceChange,
)
TokenOptionsUIState.UNREACHABLE -> Text(
modifier = modifier,
text = "Unreachable", // TODO (conform this text)
style = TangemTypography.body2,
color = TangemTheme.colors.text.tertiary,
)
TokenOptionsUIState.HIDDEN -> TokenFiatPercentageBlock(
modifier = modifier,
fiatAmount = DOTS,
priceChange = config.priceChange,
)
TokenOptionsUIState.DRAG -> Icon(
modifier = modifier,
painter = painterResource(id = R.drawable.ic_drag_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
@Composable
private fun LoadingTokenState() {
BaseSurface {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing4,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42))
SpacerW12()
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size72,
height = TangemTheme.dimens.size12,
),
)
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size50,
height = TangemTheme.dimens.size12,
),
)
}
}
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size40,
height = TangemTheme.dimens.size12,
),
)
ShimmerRectangle(
modifier = Modifier.size(
width = TangemTheme.dimens.size40,
height = TangemTheme.dimens.size12,
),
)
}
}
}
@Composable
private fun BaseSurface(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) {
Surface(
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68),
color = TangemTheme.colors.background.primary,
onClick = onClick ?: {},
enabled = onClick != null,
) {
content()
}
}
@Composable
private fun TokenTitleAmountBlock(title: String, amount: String, hasPending: Boolean, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
) {
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
Text(
text = title,
style = TangemTypography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
if (hasPending) {
Image(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = R.drawable.img_loader_15),
contentDescription = null,
)
}
}
Text(
text = amount,
style = TangemTypography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
}
@Composable
private fun TokenFiatPercentageBlock(fiatAmount: String, priceChange: PriceChange, modifier: Modifier = Modifier) {
Column(modifier = modifier.requiredWidth(IntrinsicSize.Max)) {
Text(
modifier = Modifier.align(Alignment.End),
text = fiatAmount,
style = TangemTypography.body2,
color = TangemTheme.colors.text.primary1,
)
SpacerH2()
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
val iconChangeArrow: Int
val changeTextColor: Color
when (priceChange.type) {
PriceChangeType.UP -> {
iconChangeArrow = R.drawable.img_arrow_up_8
changeTextColor = TangemTheme.colors.text.accent
}
PriceChangeType.DOWN -> {
iconChangeArrow = R.drawable.img_arrow_down_8
changeTextColor = TangemTheme.colors.text.warning
}
}
Image(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = iconChangeArrow),
contentDescription = null,
)
SpacerW4()
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = priceChange.valuePercent,
style = TangemTypography.body2,
color = changeTextColor,
)
}
}
}
@Composable
private fun TokenIcon(
tokenIconUrl: String?,
modifier: Modifier = Modifier,
@DrawableRes icon: Int? = null,
@DrawableRes networkIconRes: Int? = null,
) {
Box(
modifier = modifier
.padding(end = TangemTheme.dimens.spacing16)
.size(TangemTheme.dimens.size42),
) {
val tokenImageModifier = Modifier
.align(Alignment.BottomStart)
.size(TangemTheme.dimens.size36)
val data = if (tokenIconUrl.isNullOrEmpty()) {
icon
} else {
tokenIconUrl
}
SubcomposeAsyncImage(
modifier = tokenImageModifier,
model = ImageRequest.Builder(LocalContext.current)
.data(data)
.crossfade(true)
.build(),
loading = { CircleShimmer(modifier = tokenImageModifier) },
contentDescription = null,
)
if (networkIconRes != null) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(TangemTheme.dimens.size18)
.background(color = Color.White, shape = CircleShape),
contentAlignment = Alignment.Center,
) {
Image(
modifier = Modifier.padding(all = 0.5.dp),
painter = painterResource(id = networkIconRes),
contentDescription = null,
)
}
}
}
}
// region preview
@Composable
private fun TokensPreview() {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
) {
val config = TokenV2Config(
name = "Polygon",
amount = "5,412 MATIC",
fiatAmount = "321 $",
priceChange = PriceChange(
valuePercent = "2%",
type = PriceChangeType.UP,
),
iconUrl = null,
icon = R.drawable.img_polygon_22,
networkIcon = R.drawable.img_polygon_22,
tokenUIState = TokenUIState.LOADED,
tokenOptionsUIState = TokenOptionsUIState.VISIBLE,
hasPending = true,
)
// Loaded item
TokenItemV2(config)
// Unreachable item
TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE))
// Drag item
TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.DRAG))
// Hidden item
TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE))
// Loading item
TokenItemV2(
config.copy(
tokenUIState = TokenUIState.LOADING,
),
)
}
}
@Preview(showBackground = true)
@Composable
private fun Preview_Tokens_InLightTheme() {
TangemTheme(isDark = false) {
TokensPreview()
}
}
@Preview(showBackground = true)
@Composable
private fun Preview_Tokens_InDarkTheme() {
TangemTheme(isDark = true) {
TokensPreview()
}
}
// endregion preview

View file

@ -1,51 +0,0 @@
package com.tangem.feature.wallet.presentation.ui
import com.tangem.core.ui.R
import com.tangem.feature.wallet.presentation.state.WalletCardState
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import java.util.UUID
internal object WalletPreviewData {
val walletCardContent = WalletCardState.Content(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
balance = "8923,05 $",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardLoading = WalletCardState.Loading(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardHiddenContent = WalletCardState.HiddenContent(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletCardError = WalletCardState.Error(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
imageResId = R.drawable.ill_businessman_3d,
onClick = {},
)
val walletScreenState = WalletStateHolder(
onBackClick = {},
headerConfig = WalletStateHolder.HeaderConfig(
wallets = listOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError),
onScanCardClick = {},
onMoreClick = {},
),
)
}

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.state
package com.tangem.feature.wallet.presentation.wallet.state
import androidx.annotation.DrawableRes

View file

@ -1,4 +1,6 @@
package com.tangem.feature.wallet.presentation.state
package com.tangem.feature.wallet.presentation.wallet.state
import kotlinx.collections.immutable.ImmutableList
/**
* Wallet state holder
@ -21,7 +23,7 @@ internal data class WalletStateHolder(
* @property onMoreClick lambda be invoked when more button is clicked
*/
data class HeaderConfig(
val wallets: List<WalletCardState>,
val wallets: ImmutableList<WalletCardState>,
val onScanCardClick: () -> Unit,
val onMoreClick: () -> Unit,
)

View file

@ -1,10 +1,10 @@
package com.tangem.feature.wallet.presentation.ui
package com.tangem.feature.wallet.presentation.wallet.ui
import androidx.activity.compose.BackHandler
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.ui.components.WalletHeader
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletHeader
/**
* Wallet screen

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.ui.components
package com.tangem.feature.wallet.presentation.wallet.ui.components
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
@ -17,12 +17,12 @@ import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import com.tangem.core.ui.components.FontSizeRange
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.ShimmerRectangle
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.state.WalletCardState
import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
private const val DOTS = "•••"
@ -119,7 +119,7 @@ private fun Balance(state: WalletCardState) {
)
}
is WalletCardState.Loading -> {
ShimmerRectangle(
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size102,
height = TangemTheme.dimens.size24,

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.ui.components
package com.tangem.feature.wallet.presentation.wallet.ui.components
import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi
@ -21,9 +21,10 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.state.WalletCardState
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import kotlinx.collections.immutable.persistentListOf
/**
* Wallet screen header
@ -109,7 +110,7 @@ private fun Preview_WalletHeader_DarkTheme(
private class WalletHeaderProvider : CollectionPreviewParameterProvider<WalletStateHolder.HeaderConfig>(
collection = listOf(
WalletStateHolder.HeaderConfig(
wallets = listOf(
wallets = persistentListOf(
WalletPreviewData.walletCardContent,
WalletPreviewData.walletCardLoading,
WalletPreviewData.walletCardHiddenContent,

View file

@ -1,12 +1,12 @@
package com.tangem.feature.wallet.presentation.viewmodels
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlin.properties.Delegates

View file

@ -6,7 +6,7 @@
androidGradlePlugin = "7.4.2"
firebaseCrashlytics = "2.9.4"
googleServices = "4.3.10"
kotlin = "1.8.10"
kotlin = "1.8.21"
# endregion Classpath
# region AndroidX
@ -21,14 +21,14 @@ androidx-paging = "3.1.1"
# endregion AndroidX
# region Compose
compose-compiler = "1.4.3"
compose-runtime = "1.3.3"
compose-foundation = "1.3.1"
compose-material = "1.4.2"
compose-compiler = "1.4.7"
compose-runtime = "1.4.3"
compose-foundation = "1.4.3"
compose-material = "1.4.3"
compose-material3 = "1.1.0"
compose-constraint = "1.0.1"
compose-navigation = "2.5.3"
compose-accompanist = "0.28.0"
compose-accompanist = "0.30.1"
compose-paging = "1.0.0-alpha18"
# endregion Compose

View file

@ -1,6 +1,6 @@
#Tue Mar 07 14:45:01 MSK 2023
distributionBase = GRADLE_USER_HOME
distributionUrl = https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl = https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionPath = wrapper/dists
zipStorePath = wrapper/dists
zipStoreBase = GRADLE_USER_HOME

View file

@ -12,12 +12,12 @@ import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
class ConfigurationPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.plugins.all(startConfigurationAction(project = target))
override fun apply(project: Project) {
project.configure()
project.plugins.all(startPluginConfigurationAction(project))
}
private fun startConfigurationAction(project: Project) = Action<Plugin<*>> {
project.configure()
private fun startPluginConfigurationAction(project: Project) = Action<Plugin<*>> {
when (this) {
is AppPlugin -> {
project.configure<AppExtension> { configure(project) }