Balances UI implementation

This commit is contained in:
Milan Cerovsky 2025-04-14 15:35:51 +02:00
parent 8f526602ce
commit 720c2f82ca
25 changed files with 539 additions and 103 deletions

View File

@ -1,6 +1,5 @@
package co.electriccoin.zcash.ui.design.component package co.electriccoin.zcash.ui.design.component
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -100,7 +99,6 @@ fun StyledBalance(
val resultModifier = val resultModifier =
Modifier Modifier
.basicMarquee() .basicMarquee()
.animateContentSize()
.then(modifier) .then(modifier)
Text( Text(

View File

@ -285,7 +285,7 @@ data class ButtonState(
) )
@Composable @Composable
private fun ZashiButtonColors.toButtonColors() = fun ZashiButtonColors.toButtonColors() =
ButtonDefaults.buttonColors( ButtonDefaults.buttonColors(
containerColor = containerColor, containerColor = containerColor,
contentColor = contentColor, contentColor = contentColor,

View File

@ -103,6 +103,10 @@ private fun ContactItemLeading(
) )
} }
} }
ImageResource.Loading -> {
// do nothing
}
} }
} }

View File

@ -66,6 +66,10 @@ fun ZashiCheckboxListItem(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
) )
ImageResource.Loading -> {
// do nothing
}
} }
} }
}, },

View File

@ -8,15 +8,14 @@ import androidx.compose.runtime.Stable
sealed interface ImageResource { sealed interface ImageResource {
@Immutable @Immutable
@JvmInline @JvmInline
value class ByDrawable( value class ByDrawable(@DrawableRes val resource: Int) : ImageResource
@DrawableRes val resource: Int
) : ImageResource
@JvmInline @JvmInline
@Immutable @Immutable
value class DisplayString( value class DisplayString(val value: String) : ImageResource
val value: String
) : ImageResource @Immutable
data object Loading: ImageResource
} }
@Stable @Stable
@ -26,3 +25,6 @@ fun imageRes(
@Stable @Stable
fun imageRes(value: String): ImageResource = ImageResource.DisplayString(value) fun imageRes(value: String): ImageResource = ImageResource.DisplayString(value)
@Stable
fun loadingImageRes(): ImageResource = ImageResource.Loading

View File

@ -9,6 +9,7 @@ import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.AddressBookViewMode
import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.SelectRecipientViewModel import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.SelectRecipientViewModel
import co.electriccoin.zcash.ui.screen.advancedsettings.AdvancedSettingsViewModel import co.electriccoin.zcash.ui.screen.advancedsettings.AdvancedSettingsViewModel
import co.electriccoin.zcash.ui.screen.balances.BalanceViewModel import co.electriccoin.zcash.ui.screen.balances.BalanceViewModel
import co.electriccoin.zcash.ui.screen.balances.action.BalanceActionViewModel
import co.electriccoin.zcash.ui.screen.chooseserver.ChooseServerViewModel import co.electriccoin.zcash.ui.screen.chooseserver.ChooseServerViewModel
import co.electriccoin.zcash.ui.screen.contact.viewmodel.AddContactViewModel import co.electriccoin.zcash.ui.screen.contact.viewmodel.AddContactViewModel
import co.electriccoin.zcash.ui.screen.contact.viewmodel.UpdateContactViewModel import co.electriccoin.zcash.ui.screen.contact.viewmodel.UpdateContactViewModel
@ -152,4 +153,5 @@ val viewModelModule =
viewModelOf(::ExchangeRateSettingsViewModel) viewModelOf(::ExchangeRateSettingsViewModel)
viewModelOf(::WalletBackupDetailViewModel) viewModelOf(::WalletBackupDetailViewModel)
viewModelOf(::ErrorViewModel) viewModelOf(::ErrorViewModel)
viewModelOf(::BalanceActionViewModel)
} }

View File

@ -51,6 +51,8 @@ import co.electriccoin.zcash.ui.screen.addressbook.WrapAddressBook
import co.electriccoin.zcash.ui.screen.advancedsettings.WrapAdvancedSettings import co.electriccoin.zcash.ui.screen.advancedsettings.WrapAdvancedSettings
import co.electriccoin.zcash.ui.screen.authentication.AuthenticationUseCase import co.electriccoin.zcash.ui.screen.authentication.AuthenticationUseCase
import co.electriccoin.zcash.ui.screen.authentication.WrapAuthentication import co.electriccoin.zcash.ui.screen.authentication.WrapAuthentication
import co.electriccoin.zcash.ui.screen.balances.action.AndroidBalanceAction
import co.electriccoin.zcash.ui.screen.balances.action.BalanceAction
import co.electriccoin.zcash.ui.screen.chooseserver.WrapChooseServer import co.electriccoin.zcash.ui.screen.chooseserver.WrapChooseServer
import co.electriccoin.zcash.ui.screen.connectkeystone.AndroidConnectKeystone import co.electriccoin.zcash.ui.screen.connectkeystone.AndroidConnectKeystone
import co.electriccoin.zcash.ui.screen.connectkeystone.ConnectKeystone import co.electriccoin.zcash.ui.screen.connectkeystone.ConnectKeystone
@ -493,6 +495,15 @@ internal fun MainActivity.Navigation() {
) { ) {
AndroidErrorBottomSheet() AndroidErrorBottomSheet()
} }
dialog<BalanceAction>(
dialogProperties =
DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false
)
) {
AndroidBalanceAction()
}
} }
} }

View File

@ -8,15 +8,13 @@ object BalanceStateFixture {
private const val BALANCE_VALUE = 0L private const val BALANCE_VALUE = 0L
val TOTAL_BALANCE = Zatoshi(BALANCE_VALUE) val TOTAL_BALANCE = Zatoshi(BALANCE_VALUE)
val SPENDABLE_BALANCE = Zatoshi(BALANCE_VALUE)
fun new( fun new(
totalBalance: Zatoshi = TOTAL_BALANCE, totalBalance: Zatoshi = TOTAL_BALANCE,
spendableBalance: Zatoshi = SPENDABLE_BALANCE,
exchangeRate: ExchangeRateState = ObserveFiatCurrencyResultFixture.new() exchangeRate: ExchangeRateState = ObserveFiatCurrencyResultFixture.new()
) = BalanceState.Available( ) = BalanceState(
totalBalance = totalBalance, totalBalance = totalBalance,
spendableBalance = spendableBalance,
exchangeRate = exchangeRate, exchangeRate = exchangeRate,
button = null
) )
} }

View File

@ -0,0 +1,114 @@
package co.electriccoin.zcash.ui.screen.balances
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
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.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.BlankSurface
import co.electriccoin.zcash.ui.design.component.LottieProgress
import co.electriccoin.zcash.ui.design.component.Spacer
import co.electriccoin.zcash.ui.design.component.ZashiButtonDefaults
import co.electriccoin.zcash.ui.design.component.toButtonColors
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
import co.electriccoin.zcash.ui.design.util.getValue
import co.electriccoin.zcash.ui.design.util.stringRes
@Suppress("LongParameterList")
@Composable
fun BalanceButton(
state: BalanceButtonState,
modifier: Modifier = Modifier,
) {
val colors = ZashiButtonDefaults.secondaryColors(
containerColor = ZashiColors.Surfaces.bgPrimary,
borderColor = ZashiColors.Utility.Gray.utilityGray100
)
val borderColor = colors.borderColor
Button(
onClick = state.onClick,
modifier = modifier,
shape = RoundedCornerShape(ZashiDimensions.Radius.radiusIg),
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 10.dp),
colors = colors.toButtonColors(),
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 2.dp,
pressedElevation = 0.dp
),
border = borderColor.takeIf { it != Color.Unspecified }?.let { BorderStroke(1.dp, it) },
content = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(state.icon),
contentDescription = null,
colorFilter = ColorFilter.tint(ZashiColors.Text.textTertiary)
)
Spacer(4.dp)
Text(
text = state.text.getValue(),
color = ZashiColors.Btns.Tertiary.btnTertiaryFg,
style = ZashiTypography.textSm,
fontWeight = FontWeight.SemiBold
)
if (state.amount != null) {
Spacer(6.dp)
Image(
modifier = Modifier.padding(top = 1.dp),
painter = painterResource(R.drawable.ic_balance_zec_small),
contentDescription = null,
colorFilter = ColorFilter.tint(ZashiColors.Text.textTertiary)
)
Spacer(3.dp)
Text(
text = stringRes(state.amount).getValue(),
color = ZashiColors.Btns.Tertiary.btnTertiaryFg,
style = ZashiTypography.textSm,
fontWeight = FontWeight.SemiBold
)
} else {
Spacer(6.dp)
LottieProgress(
modifier = Modifier.size(20.dp),
)
}
}
}
)
}
@PreviewScreens
@Composable
private fun Preview() = ZcashTheme {
BlankSurface {
BalanceButton(
state = BalanceButtonState(
icon = R.drawable.ic_help,
text = stringRes("text"),
amount = Zatoshi(1000),
onClick = {}
)
)
}
}

View File

@ -1,29 +1,22 @@
package co.electriccoin.zcash.ui.screen.balances package co.electriccoin.zcash.ui.screen.balances
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import co.electriccoin.zcash.ui.design.util.StringResource
sealed interface BalanceState { @Immutable
val totalBalance: Zatoshi data class BalanceState(
val spendableBalance: Zatoshi val totalBalance: Zatoshi,
val exchangeRate: ExchangeRateState val button: BalanceButtonState?,
val exchangeRate: ExchangeRateState?,
)
data class None( @Immutable
override val exchangeRate: ExchangeRateState data class BalanceButtonState(
) : BalanceState { @DrawableRes val icon: Int,
override val totalBalance: Zatoshi = Zatoshi(0L) val text: StringResource,
override val spendableBalance: Zatoshi = Zatoshi(0L) val amount: Zatoshi?,
} val onClick: () -> Unit
)
data class Loading(
override val totalBalance: Zatoshi,
override val spendableBalance: Zatoshi,
override val exchangeRate: ExchangeRateState,
) : BalanceState
data class Available(
override val totalBalance: Zatoshi,
override val spendableBalance: Zatoshi,
override val exchangeRate: ExchangeRateState,
) : BalanceState
}

View File

@ -34,30 +34,11 @@ class BalanceViewModel(
) )
) )
private fun createState( private fun createState(account: WalletAccount?, exchangeRateUsd: ExchangeRateState): BalanceState {
account: WalletAccount?, return BalanceState(
exchangeRateUsd: ExchangeRateState totalBalance = account?.totalBalance ?: Zatoshi(0),
): BalanceState = exchangeRate = exchangeRateUsd,
when { button = null
( )
account != null && }
account.spendableBalance.value == 0L &&
account.totalBalance.value > 0L &&
(account.hasChangePending || account.hasValuePending)
) -> {
BalanceState.Loading(
totalBalance = account.totalBalance,
spendableBalance = account.spendableBalance,
exchangeRate = exchangeRateUsd,
)
}
else -> {
BalanceState.Available(
totalBalance = account?.totalBalance ?: Zatoshi(0),
spendableBalance = account?.spendableBalance ?: Zatoshi(0),
exchangeRate = exchangeRateUsd,
)
}
}
} }

View File

@ -3,40 +3,38 @@ package co.electriccoin.zcash.ui.screen.balances
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.extension.toZecStringFull import cash.z.ecc.sdk.extension.toZecStringFull
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.extension.asZecAmountTriple import co.electriccoin.zcash.ui.common.extension.asZecAmountTriple
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import co.electriccoin.zcash.ui.design.R
import co.electriccoin.zcash.ui.design.component.BlankSurface import co.electriccoin.zcash.ui.design.component.BlankSurface
import co.electriccoin.zcash.ui.design.component.Spacer
import co.electriccoin.zcash.ui.design.component.StyledBalance import co.electriccoin.zcash.ui.design.component.StyledBalance
import co.electriccoin.zcash.ui.design.component.StyledBalanceDefaults import co.electriccoin.zcash.ui.design.component.StyledBalanceDefaults
import co.electriccoin.zcash.ui.design.component.ZecAmountTriple import co.electriccoin.zcash.ui.design.component.ZecAmountTriple
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.theme.balances.LocalBalancesAvailable import co.electriccoin.zcash.ui.design.theme.balances.LocalBalancesAvailable
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
import co.electriccoin.zcash.ui.design.util.stringRes
import co.electriccoin.zcash.ui.fixture.ObserveFiatCurrencyResultFixture import co.electriccoin.zcash.ui.fixture.ObserveFiatCurrencyResultFixture
import co.electriccoin.zcash.ui.screen.balances.BalanceTag.BALANCE_VIEWS import co.electriccoin.zcash.ui.screen.balances.BalanceTag.BALANCE_VIEWS
import co.electriccoin.zcash.ui.screen.exchangerate.widget.StyledExchangeBalance import co.electriccoin.zcash.ui.screen.exchangerate.widget.StyledExchangeBalance
@Composable @Composable
fun BalanceWidget( fun BalanceWidget(state: BalanceState, modifier: Modifier = Modifier) {
balanceState: BalanceState,
modifier: Modifier = Modifier
) {
Column( Column(
modifier = modifier =
Modifier Modifier
@ -46,20 +44,22 @@ fun BalanceWidget(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
BalanceWidgetHeader( BalanceWidgetHeader(
parts = balanceState.totalBalance.toZecStringFull().asZecAmountTriple() parts = state.totalBalance.toZecStringFull().asZecAmountTriple()
) )
if (balanceState.exchangeRate is ExchangeRateState.Data) { state.button?.let {
Spacer(modifier = Modifier.height(12.dp)) Spacer(12.dp)
BalanceButton(it)
} }
StyledExchangeBalance( state.exchangeRate?.let {
zatoshi = balanceState.totalBalance, if (state.exchangeRate is ExchangeRateState.Data) {
state = balanceState.exchangeRate, Spacer(12.dp)
) }
StyledExchangeBalance(state = it, zatoshi = state.totalBalance)
if (balanceState.exchangeRate is ExchangeRateState.Data) { if (state.exchangeRate is ExchangeRateState.Data) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(12.dp)
}
} }
} }
} }
@ -74,6 +74,12 @@ fun BalanceWidgetHeader(
modifier = modifier, modifier = modifier,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Image(
painter = painterResource(R.drawable.ic_balance_zec),
contentDescription = null,
colorFilter = ColorFilter.tint(ZashiColors.Text.textPrimary)
)
Spacer(6.dp)
StyledBalance( StyledBalance(
balanceParts = parts, balanceParts = parts,
isHideBalances = isHideBalances, isHideBalances = isHideBalances,
@ -83,13 +89,6 @@ fun BalanceWidgetHeader(
leastSignificantPart = ZashiTypography.textXs.copy(fontWeight = FontWeight.SemiBold), leastSignificantPart = ZashiTypography.textXs.copy(fontWeight = FontWeight.SemiBold),
) )
) )
Spacer(modifier = Modifier.width(4.dp))
Image(
painter = painterResource(id = R.drawable.ic_zcash_zec_icon),
contentDescription = null,
)
} }
} }
@ -101,10 +100,15 @@ private fun BalanceWidgetPreview() {
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
BalanceWidget( BalanceWidget(
balanceState = state =
BalanceState.Available( BalanceState(
totalBalance = Zatoshi(1234567891234567L), totalBalance = Zatoshi(1234567891234567L),
spendableBalance = Zatoshi(1234567891234567L), button = BalanceButtonState(
icon = R.drawable.ic_help,
text = stringRes("text"),
amount = Zatoshi(1000),
onClick = {}
),
exchangeRate = ObserveFiatCurrencyResultFixture.new() exchangeRate = ObserveFiatCurrencyResultFixture.new()
), ),
modifier = Modifier, modifier = Modifier,

View File

@ -0,0 +1,19 @@
package co.electriccoin.zcash.ui.screen.balances.action
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.serialization.Serializable
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AndroidBalanceAction() {
val vm = koinViewModel<BalanceActionViewModel>()
val state by vm.state.collectAsStateWithLifecycle()
BalanceActionView(state)
}
@Serializable
data object BalanceAction

View File

@ -0,0 +1,31 @@
package co.electriccoin.zcash.ui.screen.balances.action
import androidx.compose.runtime.Immutable
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.ModalBottomSheetState
import co.electriccoin.zcash.ui.design.util.ImageResource
import co.electriccoin.zcash.ui.design.util.StringResource
@Immutable
data class BalanceActionState(
val title: StringResource,
val message: StringResource,
val rows: List<BalanceActionRowState>,
val shieldButton: BalanceShieldButtonState?,
val positive: ButtonState,
override val onBack: () -> Unit,
): ModalBottomSheetState
@Immutable
data class BalanceActionRowState(
val title: StringResource,
val icon: ImageResource,
val value: StringResource
)
@Immutable
data class BalanceShieldButtonState(
val amount: Zatoshi,
val onShieldClick: () -> Unit,
)

View File

@ -0,0 +1,211 @@
package co.electriccoin.zcash.ui.screen.balances.action
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.LottieProgress
import co.electriccoin.zcash.ui.design.component.Spacer
import co.electriccoin.zcash.ui.design.component.ZashiButton
import co.electriccoin.zcash.ui.design.component.ZashiCard
import co.electriccoin.zcash.ui.design.component.ZashiScreenModalBottomSheet
import co.electriccoin.zcash.ui.design.component.rememberScreenModalBottomSheetState
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
import co.electriccoin.zcash.ui.design.util.ImageResource
import co.electriccoin.zcash.ui.design.util.getValue
import co.electriccoin.zcash.ui.design.util.imageRes
import co.electriccoin.zcash.ui.design.util.loadingImageRes
import co.electriccoin.zcash.ui.design.util.stringRes
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BalanceActionView(
state: BalanceActionState?,
sheetState: SheetState = rememberScreenModalBottomSheetState(),
) {
ZashiScreenModalBottomSheet(
state = state,
sheetState = sheetState,
content = {
BottomSheetContent(it, modifier = Modifier.weight(1f, false))
},
)
}
@Composable
fun BottomSheetContent(state: BalanceActionState, modifier: Modifier = Modifier) {
Column(
modifier =
modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = 24.dp)
) {
Text(
text = state.title.getValue(),
color = ZashiColors.Text.textPrimary,
style = ZashiTypography.textXl,
fontWeight = FontWeight.SemiBold
)
Spacer(12.dp)
Text(
text = state.message.getValue(),
color = ZashiColors.Text.textTertiary,
style = ZashiTypography.textMd
)
Spacer(32.dp)
state.rows.forEachIndexed { index, state ->
if (index != 0) {
Spacer(12.dp)
}
BalanceActionRow(state)
}
Spacer(32.dp)
state.shieldButton?.let {
BalanceShieldButton(it)
}
Spacer(32.dp)
ZashiButton(
modifier = Modifier.fillMaxWidth(),
state = state.positive
)
}
}
@Composable
private fun BalanceActionRow(state: BalanceActionRowState) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = state.title.getValue(),
color = ZashiColors.Text.textTertiary,
style = ZashiTypography.textSm,
)
Spacer(1f)
when (state.icon) {
is ImageResource.ByDrawable -> Image(
modifier = Modifier.size(20.dp),
painter = painterResource(state.icon.resource),
contentDescription = null
)
ImageResource.Loading -> LottieProgress(modifier = Modifier.size(20.dp))
is ImageResource.DisplayString -> {
// do nothing
}
}
Spacer(8.dp)
Text(
text = state.value.getValue(),
color = if (state.icon is ImageResource.Loading) {
ZashiColors.Text.textTertiary
} else {
ZashiColors.Text.textPrimary
},
style = ZashiTypography.textSm,
fontWeight = FontWeight.Medium
)
}
}
@Composable
private fun BalanceShieldButton(state: BalanceShieldButtonState) {
ZashiCard(
modifier = Modifier.fillMaxWidth(),
contentPadding =
PaddingValues(
horizontal = 20.dp,
vertical = 12.dp
),
borderColor = ZashiColors.Surfaces.strokeSecondary
) {
Row {
Column {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
stringResource(R.string.balance_action_shield_button_header),
color = ZashiColors.Text.textPrimary,
style = ZashiTypography.textMd,
fontWeight = FontWeight.Medium
)
Spacer(4.dp)
Image(
painter = painterResource(R.drawable.ic_transparent_small),
contentDescription = null
)
}
Spacer(4.dp)
Text(
text =
stringResource(
R.string.home_message_transparent_balance_subtitle,
stringRes(state.amount).getValue()
),
color = ZashiColors.Text.textPrimary,
style = ZashiTypography.textXl,
fontWeight = FontWeight.SemiBold
)
}
Spacer(1f)
ZashiButton(
state = ButtonState(
text = stringRes(R.string.balance_action_shield),
onClick = state.onShieldClick)
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@PreviewScreens
@Composable
private fun Preview() = ZcashTheme {
BalanceActionView(
state = BalanceActionState(
title = stringRes("Title"),
message = stringRes("Subtitle"),
positive = ButtonState(
text = stringRes("Positive")
),
onBack = {},
rows = listOf(
BalanceActionRowState(
title = stringRes("Row"),
icon = loadingImageRes(),
value = stringRes("Value")
),
BalanceActionRowState(
title = stringRes("Row"),
icon = imageRes(R.drawable.ic_home_buy),
value = stringRes("Value")
)
),
shieldButton = BalanceShieldButtonState(
amount = Zatoshi(10000),
onShieldClick = {}
)
)
)
}

View File

@ -0,0 +1,39 @@
package co.electriccoin.zcash.ui.screen.balances.action
import androidx.lifecycle.ViewModel
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.util.imageRes
import co.electriccoin.zcash.ui.design.util.loadingImageRes
import co.electriccoin.zcash.ui.design.util.stringRes
import kotlinx.coroutines.flow.MutableStateFlow
class BalanceActionViewModel : ViewModel() {
val state = MutableStateFlow(
BalanceActionState(
title = stringRes("Error"),
message = stringRes("Something went wrong"),
positive = ButtonState(
text = stringRes("Positive")
),
onBack = {},
rows = listOf(
BalanceActionRowState(
title = stringRes("Row"),
icon = loadingImageRes(),
value = stringRes("Value")
),
BalanceActionRowState(
title = stringRes("Row"),
icon = imageRes(R.drawable.ic_home_buy),
value = stringRes("Value")
)
),
shieldButton = BalanceShieldButtonState(
amount = Zatoshi(10000),
onShieldClick = {}
)
)
)
}

View File

@ -2,11 +2,9 @@
package co.electriccoin.zcash.ui.screen.exchangerate.widget package co.electriccoin.zcash.ui.screen.exchangerate.widget
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
@ -105,10 +103,7 @@ private fun ExchangeAvailableRateLabelInternal(
val isEnabled = !state.isLoading && state.isRefreshEnabled val isEnabled = !state.isLoading && state.isRefreshEnabled
ExchangeRateButton( ExchangeRateButton(
modifier = modifier = modifier,
modifier
.basicMarquee()
.animateContentSize(),
onClick = state.onRefresh, onClick = state.onRefresh,
isEnabled = isEnabled, isEnabled = isEnabled,
textColor = textColor, textColor = textColor,
@ -233,10 +228,7 @@ private fun ExchangeRateButton(
content: @Composable RowScope.() -> Unit content: @Composable RowScope.() -> Unit
) { ) {
ElevatedButton( ElevatedButton(
modifier = modifier = modifier.height(36.dp),
modifier
.height(36.dp)
.animateContentSize(),
onClick = onClick, onClick = onClick,
enabled = isEnabled, enabled = isEnabled,
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),

View File

@ -79,7 +79,7 @@ private fun Content(
start = ZcashTheme.dimens.screenHorizontalSpacingRegular, start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
end = ZcashTheme.dimens.screenHorizontalSpacingRegular, end = ZcashTheme.dimens.screenHorizontalSpacingRegular,
), ),
balanceState = balanceState, state = balanceState,
) )
NavButtons( NavButtons(
modifier = modifier =

View File

@ -298,7 +298,7 @@ private fun SendForm(
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall)) Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
BalanceWidget( BalanceWidget(
balanceState = balanceState state = balanceState
) )
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="19dp"
android:height="32dp"
android:viewportWidth="19"
android:viewportHeight="32">
<path
android:pathData="M18.537,8.104V4.249H11.624V0H7.376V4.249H0.463V9.363H11.175L0.463,23.907V27.763H7.376V32H11.624V27.763H18.537V22.649H7.814L18.537,8.104Z"
android:fillColor="#282622"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="6dp"
android:height="10dp"
android:viewportWidth="6"
android:viewportHeight="10">
<path
android:pathData="M5.824,2.533V1.328H3.664V0H2.336V1.328H0.176V2.926H3.523L0.176,7.471V8.676H2.336V10H3.664V8.676H5.824V7.078H2.473L5.824,2.533Z"
android:fillColor="#716C5D"/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="balance_action_shield">Shield</string>
<string name="balance_action_shield_button_header">Transparent</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="balance_action_shield">Shield</string>
<string name="balance_action_shield_button_header">Transparent</string>
</resources>

View File

@ -29,4 +29,9 @@
<string name="general_ok">OK</string> <string name="general_ok">OK</string>
<string name="general_remind_me_later">Remind me later</string> <string name="general_remind_me_later">Remind me later</string>
<string name="general_remind_me_in">Remind me in %s</string>
<string name="general_report">Report</string>
<string name="general_remind_me_in_two_days">two days</string>
<string name="general_remind_me_in_two_weeks">two weeks</string>
<string name="general_remind_me_in_two_months">two months</string>
</resources> </resources>

View File

@ -29,9 +29,9 @@
<string name="general_etc"></string> <string name="general_etc"></string>
<string name="general_ok">OK</string> <string name="general_ok">OK</string>
<string name="general_report">Report</string>
<string name="general_remind_me_later">Remind me later</string> <string name="general_remind_me_later">Remind me later</string>
<string name="general_remind_me_in">Remind me in %s</string> <string name="general_remind_me_in">Remind me in %s</string>
<string name="general_report">Report</string>
<string name="general_remind_me_in_two_days">two days</string> <string name="general_remind_me_in_two_days">two days</string>
<string name="general_remind_me_in_two_weeks">two weeks</string> <string name="general_remind_me_in_two_weeks">two weeks</string>
<string name="general_remind_me_in_two_months">two months</string> <string name="general_remind_me_in_two_months">two months</string>