parent
28ce6b5a08
commit
095234a6cd
|
@ -6,6 +6,10 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Balance now also displays USD value
|
||||
- An option to enter USD amount in Send Transaction screen
|
||||
|
||||
## [1.1.4 (700)] - 2024-07-23
|
||||
|
||||
### Added
|
||||
|
|
|
@ -9,6 +9,10 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Balance now also displays USD value
|
||||
- An option to enter USD amount in Send Transaction screen
|
||||
|
||||
## [1.1.4 (700)] - 2024-07-23
|
||||
|
||||
### Added
|
||||
|
|
|
@ -131,7 +131,7 @@ ZCASH_EMULATOR_WTF_API_KEY=
|
|||
# Optional absolute path to a Zcash SDK checkout.
|
||||
# When blank, it pulls the SDK from Maven.
|
||||
# When set, it uses the path for a Gradle included build. Path can either be absolute or relative to the root of this app's Gradle project.
|
||||
SDK_INCLUDED_BUILD_PATH=
|
||||
SDK_INCLUDED_BUILD_PATH=/Users/milancerovsky/Developer/zcash/zcash-android-wallet-sdk
|
||||
|
||||
# When blank, it pulls the BIP-39 library from Maven.
|
||||
# When set, it uses the path for a Gradle included build. Path can either be absolute or relative to the root of this app's Gradle project.
|
||||
|
|
|
@ -4,8 +4,6 @@ import androidx.compose.animation.animateContentSize
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
@ -17,7 +15,6 @@ import androidx.compose.ui.text.TextStyle
|
|||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.design.R
|
||||
|
@ -26,7 +23,7 @@ import java.util.Locale
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun StyledBalanceComposablePreview() {
|
||||
private fun StyledBalancePreview() =
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
BlankSurface {
|
||||
Column {
|
||||
|
@ -35,9 +32,16 @@ private fun StyledBalanceComposablePreview() {
|
|||
isHideBalances = false,
|
||||
modifier = Modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun HiddenStyledBalancePreview() =
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
BlankSurface {
|
||||
Column {
|
||||
StyledBalance(
|
||||
balanceParts = ZecAmountTriple(main = "1,234.56789012"),
|
||||
isHideBalances = true,
|
||||
|
@ -46,7 +50,6 @@ private fun StyledBalanceComposablePreview() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This accepts string with balance and displays it in the UI component styled according to the design
|
||||
|
|
|
@ -123,7 +123,15 @@ class SendViewTestSetup(
|
|||
// TODO [#1260]: https://github.com/Electric-Coin-Company/zashi-android/issues/1260
|
||||
},
|
||||
setAmountState = {},
|
||||
amountState = AmountState.new(context, monetarySeparators, "", false),
|
||||
amountState =
|
||||
AmountState.newFromZec(
|
||||
context = context,
|
||||
monetarySeparators = monetarySeparators,
|
||||
value = "",
|
||||
fiatValue = "",
|
||||
isTransparentRecipient = false,
|
||||
fiatCurrencyConversion = null
|
||||
),
|
||||
setMemoState = {},
|
||||
memoState = MemoState.new(""),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
package co.electriccoin.zcash.ui
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrency
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.model.toFiatCurrencyState
|
||||
import co.electriccoin.zcash.ui.common.extension.toKotlinLocale
|
||||
import co.electriccoin.zcash.ui.design.R
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
import co.electriccoin.zcash.ui.design.component.CircularSmallProgressIndicator
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.util.StringResource
|
||||
import co.electriccoin.zcash.ui.util.getValue
|
||||
import co.electriccoin.zcash.ui.util.stringRes
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun StyledExchangeBalance(
|
||||
zatoshi: Zatoshi,
|
||||
exchangeRate: FiatCurrencyResult,
|
||||
modifier: Modifier = Modifier,
|
||||
isHideBalances: Boolean = false,
|
||||
hiddenBalancePlaceholder: StringResource = stringRes(R.string.hide_balance_placeholder),
|
||||
textColor: Color = Color.Unspecified,
|
||||
style: TextStyle = ZcashTheme.typography.primary.titleSmall
|
||||
) {
|
||||
val currencySymbol = exchangeRate.fiatCurrency.symbol
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
modifier
|
||||
.basicMarquee()
|
||||
.animateContentSize(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
when {
|
||||
exchangeRate is FiatCurrencyResult.Error -> {
|
||||
// empty view
|
||||
}
|
||||
exchangeRate is FiatCurrencyResult.Loading && !isHideBalances -> {
|
||||
StyledExchangeText(
|
||||
text = currencySymbol,
|
||||
textColor = textColor,
|
||||
style = style
|
||||
)
|
||||
CircularSmallProgressIndicator()
|
||||
}
|
||||
else -> {
|
||||
StyledExchangeText(
|
||||
text =
|
||||
if (isHideBalances) {
|
||||
"${currencySymbol}${hiddenBalancePlaceholder.getValue()}"
|
||||
} else {
|
||||
when (
|
||||
val state =
|
||||
zatoshi.toFiatCurrencyState(
|
||||
fiatCurrencyResult = exchangeRate,
|
||||
locale = Locale.current.toKotlinLocale(),
|
||||
monetarySeparators = MonetarySeparators.current(java.util.Locale.getDefault())
|
||||
)
|
||||
) {
|
||||
is FiatCurrencyConversionRateState.Current -> state.formattedFiatValue
|
||||
is FiatCurrencyConversionRateState.Stale -> state.formattedFiatValue
|
||||
FiatCurrencyConversionRateState.Unavailable ->
|
||||
stringResource(co.electriccoin.zcash.ui.R.string.fiat_currency_conversion_rate_unavailable)
|
||||
}
|
||||
},
|
||||
textColor = textColor,
|
||||
style = style
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StyledExchangeText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
textColor: Color,
|
||||
style: TextStyle
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = textColor,
|
||||
style = style,
|
||||
maxLines = 1,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StyledExchangeBalancePreview() =
|
||||
BlankSurface {
|
||||
Column {
|
||||
StyledExchangeBalance(
|
||||
isHideBalances = false,
|
||||
modifier = Modifier,
|
||||
zatoshi = Zatoshi(1),
|
||||
exchangeRate =
|
||||
FiatCurrencyResult.Success(
|
||||
FiatCurrencyConversion(
|
||||
fiatCurrency = FiatCurrency.USD,
|
||||
timestamp = Clock.System.now(),
|
||||
priceOfZec = 25.0
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HiddenStyledExchangeBalancePreview() =
|
||||
BlankSurface {
|
||||
Column {
|
||||
StyledExchangeBalance(
|
||||
isHideBalances = true,
|
||||
modifier = Modifier,
|
||||
zatoshi = Zatoshi(1),
|
||||
exchangeRate =
|
||||
FiatCurrencyResult.Success(
|
||||
FiatCurrencyConversion(
|
||||
fiatCurrency = FiatCurrency.USD,
|
||||
timestamp = Clock.System.now(),
|
||||
priceOfZec = 25.0
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingStyledExchangeBalancePreview() =
|
||||
BlankSurface {
|
||||
Column {
|
||||
StyledExchangeBalance(
|
||||
isHideBalances = true,
|
||||
modifier = Modifier,
|
||||
zatoshi = Zatoshi(1),
|
||||
exchangeRate = FiatCurrencyResult.Loading()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun StyledExchangeBalancePreviewLight() =
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
StyledExchangeBalancePreview()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun HiddenStyledExchangeBalancePreviewLight() =
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
HiddenStyledExchangeBalancePreview()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun LoadingStyledExchangeBalancePreviewLight() =
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
LoadingStyledExchangeBalancePreview()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun StyledExchangeBalancePreviewDark() =
|
||||
ZcashTheme(forceDarkMode = true) {
|
||||
StyledExchangeBalancePreview()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun HiddenStyledExchangeBalancePreviewDark() =
|
||||
ZcashTheme(forceDarkMode = true) {
|
||||
HiddenStyledExchangeBalancePreview()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun LoadingStyledExchangeBalancePreviewDark() =
|
||||
ZcashTheme(forceDarkMode = true) {
|
||||
LoadingStyledExchangeBalancePreview()
|
||||
}
|
|
@ -17,9 +17,11 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.sdk.extension.toZecStringFull
|
||||
import cash.z.ecc.sdk.type.ZcashCurrency
|
||||
import co.electriccoin.zcash.ui.StyledExchangeBalance
|
||||
import co.electriccoin.zcash.ui.common.extension.asZecAmountTriple
|
||||
import co.electriccoin.zcash.ui.design.R
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
|
@ -30,6 +32,7 @@ import co.electriccoin.zcash.ui.design.component.StyledBalance
|
|||
import co.electriccoin.zcash.ui.design.component.StyledBalanceDefaults
|
||||
import co.electriccoin.zcash.ui.design.component.ZecAmountTriple
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.FiatCurrencyResultFixture
|
||||
|
||||
@Preview(device = Devices.PIXEL_2)
|
||||
@Composable
|
||||
|
@ -44,12 +47,13 @@ private fun BalanceWidgetPreview() {
|
|||
balanceState =
|
||||
BalanceState.Available(
|
||||
totalBalance = Zatoshi(1234567891234567L),
|
||||
spendableBalance = Zatoshi(1234567891234567L)
|
||||
spendableBalance = Zatoshi(1234567891234567L),
|
||||
exchangeRate = FiatCurrencyResultFixture.new()
|
||||
),
|
||||
isHideBalances = false,
|
||||
isReferenceToBalances = true,
|
||||
onReferenceClick = {},
|
||||
modifier = Modifier
|
||||
modifier = Modifier,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -65,11 +69,15 @@ private fun BalanceWidgetNotAvailableYetPreview() {
|
|||
) {
|
||||
@Suppress("MagicNumber")
|
||||
BalanceWidget(
|
||||
balanceState = BalanceState.Loading(Zatoshi(0L)),
|
||||
balanceState =
|
||||
BalanceState.Loading(
|
||||
totalBalance = Zatoshi(value = 0L),
|
||||
exchangeRate = FiatCurrencyResultFixture.new()
|
||||
),
|
||||
isHideBalances = false,
|
||||
isReferenceToBalances = true,
|
||||
onReferenceClick = {},
|
||||
modifier = Modifier
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -84,22 +92,40 @@ private fun BalanceWidgetHiddenAmountPreview() {
|
|||
) {
|
||||
@Suppress("MagicNumber")
|
||||
BalanceWidget(
|
||||
balanceState = BalanceState.Loading(Zatoshi(0L)),
|
||||
balanceState =
|
||||
BalanceState.Loading(
|
||||
totalBalance = Zatoshi(0L),
|
||||
exchangeRate = FiatCurrencyResultFixture.new()
|
||||
),
|
||||
isHideBalances = true,
|
||||
isReferenceToBalances = true,
|
||||
onReferenceClick = {},
|
||||
modifier = Modifier
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class BalanceState(open val totalBalance: Zatoshi) {
|
||||
data object None : BalanceState(Zatoshi(0L))
|
||||
sealed interface BalanceState {
|
||||
val totalBalance: Zatoshi
|
||||
val exchangeRate: FiatCurrencyResult
|
||||
|
||||
data class Loading(override val totalBalance: Zatoshi) : BalanceState(totalBalance)
|
||||
data class None(
|
||||
override val exchangeRate: FiatCurrencyResult
|
||||
) : BalanceState {
|
||||
override val totalBalance: Zatoshi = Zatoshi(0L)
|
||||
}
|
||||
|
||||
data class Available(override val totalBalance: Zatoshi, val spendableBalance: Zatoshi) : BalanceState(totalBalance)
|
||||
data class Loading(
|
||||
override val totalBalance: Zatoshi,
|
||||
override val exchangeRate: FiatCurrencyResult
|
||||
) : BalanceState
|
||||
|
||||
data class Available(
|
||||
override val totalBalance: Zatoshi,
|
||||
override val exchangeRate: FiatCurrencyResult,
|
||||
val spendableBalance: Zatoshi
|
||||
) : BalanceState
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -123,6 +149,12 @@ fun BalanceWidget(
|
|||
parts = balanceState.totalBalance.toZecStringFull().asZecAmountTriple()
|
||||
)
|
||||
|
||||
StyledExchangeBalance(
|
||||
zatoshi = balanceState.totalBalance,
|
||||
exchangeRate = balanceState.exchangeRate,
|
||||
isHideBalances = isHideBalances
|
||||
)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (isReferenceToBalances) {
|
||||
Reference(
|
||||
|
@ -151,7 +183,7 @@ fun BalanceWidget(
|
|||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
when (balanceState) {
|
||||
BalanceState.None, is BalanceState.Loading -> {
|
||||
is BalanceState.None, is BalanceState.Loading -> {
|
||||
CircularSmallProgressIndicator(color = ZcashTheme.colors.circularProgressBarSmallDark)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package co.electriccoin.zcash.ui.common.model
|
|||
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
|
@ -15,6 +16,7 @@ data class WalletSnapshot(
|
|||
val orchardBalance: WalletBalance,
|
||||
val saplingBalance: WalletBalance,
|
||||
val transparentBalance: Zatoshi,
|
||||
val exchangeRateUsd: FiatCurrencyResult,
|
||||
val progress: PercentDecimal,
|
||||
val synchronizerError: SynchronizerError?
|
||||
) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import cash.z.ecc.android.sdk.WalletInitMode
|
|||
import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor
|
||||
import cash.z.ecc.android.sdk.model.Account
|
||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrency
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||
import cash.z.ecc.android.sdk.model.PersistableWallet
|
||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||
|
@ -95,19 +95,6 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||
null
|
||||
)
|
||||
|
||||
/**
|
||||
* A flow of the user's preferred fiat currency.
|
||||
*/
|
||||
val preferredFiatCurrency: StateFlow<FiatCurrency?> =
|
||||
flow<FiatCurrency?> {
|
||||
val preferenceProvider = StandardPreferenceSingleton.getInstance(application)
|
||||
emitAll(StandardPreferenceKeys.PREFERRED_FIAT_CURRENCY.observe(preferenceProvider))
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
null
|
||||
)
|
||||
|
||||
/**
|
||||
* A flow of the wallet block synchronization state.
|
||||
*/
|
||||
|
@ -310,20 +297,22 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||
(snapshot.hasChangePending() || snapshot.hasValuePending())
|
||||
) -> {
|
||||
BalanceState.Loading(
|
||||
totalBalance = snapshot.totalBalance()
|
||||
totalBalance = snapshot.totalBalance(),
|
||||
exchangeRate = snapshot.exchangeRateUsd
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
BalanceState.Available(
|
||||
totalBalance = snapshot.totalBalance(),
|
||||
spendableBalance = snapshot.spendableBalance()
|
||||
spendableBalance = snapshot.spendableBalance(),
|
||||
exchangeRate = snapshot.exchangeRateUsd
|
||||
)
|
||||
}
|
||||
}
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
BalanceState.None
|
||||
BalanceState.None(FiatCurrencyResult.Loading())
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -597,24 +586,29 @@ private fun Synchronizer.toWalletSnapshot() =
|
|||
// 4
|
||||
transparentBalance,
|
||||
// 5
|
||||
progress,
|
||||
exchangeRateUsd,
|
||||
// 6
|
||||
progress,
|
||||
// 7
|
||||
toCommonError()
|
||||
) { flows ->
|
||||
val orchardBalance = flows[2] as WalletBalance?
|
||||
val saplingBalance = flows[3] as WalletBalance?
|
||||
val transparentBalance = flows[4] as Zatoshi?
|
||||
|
||||
val progressPercentDecimal = flows[5] as PercentDecimal
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val exchangeRateUsd = flows[5] as FiatCurrencyResult
|
||||
val progressPercentDecimal = (flows[6] as PercentDecimal)
|
||||
|
||||
WalletSnapshot(
|
||||
flows[0] as Synchronizer.Status,
|
||||
flows[1] as CompactBlockProcessor.ProcessorInfo,
|
||||
orchardBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)),
|
||||
saplingBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)),
|
||||
transparentBalance ?: Zatoshi(0),
|
||||
progressPercentDecimal,
|
||||
flows[6] as SynchronizerError?
|
||||
status = flows[0] as Synchronizer.Status,
|
||||
processorInfo = flows[1] as CompactBlockProcessor.ProcessorInfo,
|
||||
orchardBalance = orchardBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)),
|
||||
saplingBalance = saplingBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)),
|
||||
transparentBalance = transparentBalance ?: Zatoshi(0),
|
||||
exchangeRateUsd = exchangeRateUsd,
|
||||
progress = progressPercentDecimal,
|
||||
synchronizerError = flows[7] as SynchronizerError?
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package co.electriccoin.zcash.ui.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceState
|
||||
|
||||
|
@ -11,9 +12,11 @@ object BalanceStateFixture {
|
|||
|
||||
fun new(
|
||||
totalBalance: Zatoshi = TOTAL_BALANCE,
|
||||
spendableBalance: Zatoshi = SPENDABLE_BALANCE
|
||||
spendableBalance: Zatoshi = SPENDABLE_BALANCE,
|
||||
exchangeRate: FiatCurrencyResult = FiatCurrencyResultFixture.new()
|
||||
) = BalanceState.Available(
|
||||
totalBalance = totalBalance,
|
||||
spendableBalance = spendableBalance
|
||||
spendableBalance = spendableBalance,
|
||||
exchangeRate = exchangeRate
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package co.electriccoin.zcash.ui.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrency
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
object FiatCurrencyResultFixture {
|
||||
fun new() =
|
||||
FiatCurrencyResult.Success(
|
||||
FiatCurrencyConversion(
|
||||
fiatCurrency = FiatCurrency.USD,
|
||||
timestamp = Clock.System.now(),
|
||||
priceOfZec = 25.0
|
||||
)
|
||||
)
|
||||
}
|
|
@ -3,6 +3,7 @@ package co.electriccoin.zcash.ui.fixture
|
|||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor
|
||||
import cash.z.ecc.android.sdk.fixture.WalletBalanceFixture
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
|
@ -34,12 +35,13 @@ object WalletSnapshotFixture {
|
|||
progress: PercentDecimal = PROGRESS,
|
||||
synchronizerError: SynchronizerError? = null
|
||||
) = WalletSnapshot(
|
||||
status,
|
||||
processorInfo,
|
||||
orchardBalance,
|
||||
saplingBalance,
|
||||
transparentBalance,
|
||||
progress,
|
||||
synchronizerError
|
||||
status = status,
|
||||
processorInfo = processorInfo,
|
||||
orchardBalance = orchardBalance,
|
||||
saplingBalance = saplingBalance,
|
||||
transparentBalance = transparentBalance,
|
||||
exchangeRateUsd = FiatCurrencyResult.Loading(),
|
||||
progress = progress,
|
||||
synchronizerError = synchronizerError
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.preference
|
||||
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrency
|
||||
import co.electriccoin.zcash.preference.api.PreferenceProvider
|
||||
import co.electriccoin.zcash.preference.model.entry.PreferenceDefault
|
||||
import co.electriccoin.zcash.preference.model.entry.PreferenceKey
|
||||
|
||||
data class FiatCurrencyPreferenceDefault(
|
||||
override val key: PreferenceKey
|
||||
) : PreferenceDefault<FiatCurrency> {
|
||||
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
|
||||
preferenceProvider.getString(key)?.let { FiatCurrency(it) } ?: FiatCurrency("USD")
|
||||
|
||||
override suspend fun putValue(
|
||||
preferenceProvider: PreferenceProvider,
|
||||
newValue: FiatCurrency
|
||||
) = preferenceProvider.putString(key, newValue.code)
|
||||
}
|
|
@ -35,11 +35,6 @@ object StandardPreferenceKeys {
|
|||
val IS_RESTORING_INITIAL_WARNING_SEEN =
|
||||
BooleanPreferenceDefault(PreferenceKey("IS_RESTORING_INITIAL_WARNING_SEEN"), false)
|
||||
|
||||
/**
|
||||
* The fiat currency that the user prefers.
|
||||
*/
|
||||
val PREFERRED_FIAT_CURRENCY = FiatCurrencyPreferenceDefault(PreferenceKey("preferred_fiat_currency_code"))
|
||||
|
||||
/**
|
||||
* Screens or flows protected by required authentication
|
||||
*/
|
||||
|
|
|
@ -18,6 +18,9 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrency
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceState
|
||||
|
@ -37,6 +40,7 @@ import co.electriccoin.zcash.ui.screen.account.AccountTag
|
|||
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@Preview("Account No History")
|
||||
@Composable
|
||||
|
@ -69,8 +73,16 @@ private fun HistoryListComposablePreview() {
|
|||
Account(
|
||||
balanceState =
|
||||
BalanceState.Available(
|
||||
Zatoshi(123_000_000L),
|
||||
Zatoshi(123_000_000L)
|
||||
totalBalance = Zatoshi(value = 123_000_000L),
|
||||
spendableBalance = Zatoshi(value = 123_000_000L),
|
||||
exchangeRate =
|
||||
FiatCurrencyResult.Success(
|
||||
FiatCurrencyConversion(
|
||||
fiatCurrency = FiatCurrency.USD,
|
||||
timestamp = Clock.System.now(),
|
||||
priceOfZec = 25.0
|
||||
)
|
||||
)
|
||||
),
|
||||
isHideBalances = false,
|
||||
goBalances = {},
|
||||
|
@ -216,7 +228,7 @@ private fun AccountMainContent(
|
|||
isHideBalances = isHideBalances,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = ZcashTheme.dimens.screenHorizontalSpacingRegular)
|
||||
.padding(horizontal = ZcashTheme.dimens.screenHorizontalSpacingRegular),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
@ -252,7 +264,7 @@ private fun BalancesStatus(
|
|||
balanceState = balanceState,
|
||||
isHideBalances = isHideBalances,
|
||||
isReferenceToBalances = true,
|
||||
onReferenceClick = goBalances
|
||||
onReferenceClick = goBalances,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,12 +34,9 @@ data class WalletDisplayValues(
|
|||
var statusText = ""
|
||||
var statusAction: StatusAction = StatusAction.None
|
||||
|
||||
// TODO [#578]: Provide Zatoshi -> USD fiat currency formatting
|
||||
// TODO [#578]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/578
|
||||
// We'll ideally provide a "fresh" currencyConversion object here
|
||||
val fiatCurrencyAmountState =
|
||||
walletSnapshot.spendableBalance().toFiatCurrencyState(
|
||||
null,
|
||||
walletSnapshot.exchangeRateUsd,
|
||||
Locale.current.toKotlinLocale(),
|
||||
MonetarySeparators.current(java.util.Locale.getDefault())
|
||||
)
|
||||
|
|
|
@ -13,6 +13,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||
import cash.z.ecc.android.sdk.model.ZecSend
|
||||
|
@ -138,24 +139,45 @@ internal fun WrapSend(
|
|||
rememberSaveable(stateSaver = AmountState.Saver) {
|
||||
// Default amount state
|
||||
mutableStateOf(
|
||||
AmountState.new(
|
||||
AmountState.newFromZec(
|
||||
context = context,
|
||||
value = zecSend?.amount?.toZecString() ?: "",
|
||||
monetarySeparators = monetarySeparators,
|
||||
isTransparentRecipient = recipientAddressState.type?.let { it == AddressType.Transparent } ?: false
|
||||
isTransparentRecipient = recipientAddressState.type?.let { it == AddressType.Transparent } ?: false,
|
||||
fiatValue = "",
|
||||
fiatCurrencyConversion =
|
||||
(walletSnapshot?.exchangeRateUsd as? FiatCurrencyResult.Success)
|
||||
?.currencyConversion
|
||||
)
|
||||
)
|
||||
}
|
||||
// New amount state based on the recipient address type (e.g. shielded supports zero funds sending and
|
||||
// transparent not)
|
||||
LaunchedEffect(key1 = recipientAddressState) {
|
||||
LaunchedEffect(recipientAddressState, walletSnapshot?.exchangeRateUsd) {
|
||||
setAmountState(
|
||||
AmountState.new(
|
||||
context = context,
|
||||
isTransparentRecipient = recipientAddressState.type?.let { it == AddressType.Transparent } ?: false,
|
||||
monetarySeparators = monetarySeparators,
|
||||
value = amountState.value
|
||||
)
|
||||
if (amountState.value.isNotBlank() || amountState.fiatValue.isBlank()) {
|
||||
AmountState.newFromZec(
|
||||
context = context,
|
||||
isTransparentRecipient = recipientAddressState.type?.let { it == AddressType.Transparent } ?: false,
|
||||
monetarySeparators = monetarySeparators,
|
||||
value = amountState.value,
|
||||
fiatValue = amountState.fiatValue,
|
||||
fiatCurrencyConversion =
|
||||
(walletSnapshot?.exchangeRateUsd as? FiatCurrencyResult.Success)
|
||||
?.currencyConversion
|
||||
)
|
||||
} else {
|
||||
AmountState.newFromFiat(
|
||||
context = context,
|
||||
isTransparentRecipient = recipientAddressState.type?.let { it == AddressType.Transparent } ?: false,
|
||||
monetarySeparators = monetarySeparators,
|
||||
value = amountState.value,
|
||||
fiatValue = amountState.fiatValue,
|
||||
fiatCurrencyConversion =
|
||||
(walletSnapshot?.exchangeRateUsd as? FiatCurrencyResult.Success)
|
||||
?.currencyConversion
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -170,14 +192,28 @@ internal fun WrapSend(
|
|||
setSendStage(SendStage.Form)
|
||||
setZecSend(null)
|
||||
setRecipientAddressState(RecipientAddressState.new("", null))
|
||||
setAmountState(AmountState.new(context, monetarySeparators, "", false))
|
||||
setAmountState(
|
||||
AmountState.newFromZec(
|
||||
context = context,
|
||||
monetarySeparators = monetarySeparators,
|
||||
value = "",
|
||||
fiatValue = "",
|
||||
isTransparentRecipient = false,
|
||||
fiatCurrencyConversion =
|
||||
(walletSnapshot?.exchangeRateUsd as? FiatCurrencyResult.Success)
|
||||
?.currencyConversion
|
||||
)
|
||||
)
|
||||
setMemoState(MemoState.new(""))
|
||||
}
|
||||
|
||||
val onBackAction = {
|
||||
when (sendStage) {
|
||||
SendStage.Form -> goBack()
|
||||
SendStage.Proposing -> { /* no action - wait until the sending is done */ }
|
||||
SendStage.Proposing -> {
|
||||
// no action - wait until the sending is done
|
||||
}
|
||||
|
||||
is SendStage.SendFailure -> setSendStage(SendStage.Form)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,51 +2,102 @@ package co.electriccoin.zcash.ui.screen.send.model
|
|||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.saveable.mapSaver
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
|
||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.model.ZecStringExt
|
||||
import cash.z.ecc.android.sdk.model.fromZecString
|
||||
import cash.z.ecc.android.sdk.model.toFiatString
|
||||
import cash.z.ecc.android.sdk.model.toZatoshi
|
||||
import cash.z.ecc.android.sdk.model.toZecString
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.common.extension.toKotlinLocale
|
||||
|
||||
sealed interface AmountState {
|
||||
val value: String
|
||||
val fiatValue: String
|
||||
|
||||
sealed class AmountState(
|
||||
open val value: String,
|
||||
) {
|
||||
data class Valid(
|
||||
override val value: String,
|
||||
override val fiatValue: String,
|
||||
val zatoshi: Zatoshi
|
||||
) : AmountState(value)
|
||||
) : AmountState
|
||||
|
||||
data class Invalid(
|
||||
override val value: String,
|
||||
) : AmountState(value)
|
||||
data class Invalid(override val value: String, override val fiatValue: String) : AmountState
|
||||
|
||||
companion object {
|
||||
fun new(
|
||||
fun newFromZec(
|
||||
context: Context,
|
||||
monetarySeparators: MonetarySeparators,
|
||||
value: String,
|
||||
isTransparentRecipient: Boolean
|
||||
fiatValue: String,
|
||||
isTransparentRecipient: Boolean,
|
||||
fiatCurrencyConversion: FiatCurrencyConversion?,
|
||||
): AmountState {
|
||||
// Validate raw input string
|
||||
val validated =
|
||||
runCatching {
|
||||
ZecStringExt.filterContinuous(context, monetarySeparators, value)
|
||||
}.onFailure {
|
||||
Twig.error(it) { "Failed while filtering raw amount characters" }
|
||||
}.getOrDefault(false)
|
||||
val isValid = validate(context, monetarySeparators, value)
|
||||
|
||||
if (!validated) {
|
||||
return Invalid(value)
|
||||
if (!isValid) {
|
||||
return Invalid(value, if (value.isBlank()) "" else fiatValue)
|
||||
}
|
||||
|
||||
// Convert the input to Zatoshi type-safe amount representation
|
||||
val zatoshi = (Zatoshi.fromZecString(context, value, monetarySeparators))
|
||||
val zatoshi = Zatoshi.fromZecString(context, value, monetarySeparators)
|
||||
|
||||
// Note that the zero funds sending is supported for sending a memo-only shielded transaction
|
||||
return when {
|
||||
(zatoshi == null) -> Invalid(value)
|
||||
(zatoshi.value == 0L && isTransparentRecipient) -> Invalid(value)
|
||||
else -> Valid(value, zatoshi)
|
||||
(zatoshi == null) -> Invalid(value, if (value.isBlank()) "" else fiatValue)
|
||||
(zatoshi.value == 0L && isTransparentRecipient) -> Invalid(value, fiatValue)
|
||||
else -> {
|
||||
Valid(
|
||||
value = value,
|
||||
zatoshi = zatoshi,
|
||||
fiatValue =
|
||||
if (fiatCurrencyConversion == null) {
|
||||
fiatValue
|
||||
} else {
|
||||
zatoshi.toFiatString(
|
||||
currencyConversion = fiatCurrencyConversion,
|
||||
locale = Locale.current.toKotlinLocale(),
|
||||
monetarySeparators = MonetarySeparators.current(java.util.Locale.getDefault()),
|
||||
includeSymbols = false
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun newFromFiat(
|
||||
context: Context,
|
||||
monetarySeparators: MonetarySeparators,
|
||||
value: String,
|
||||
fiatValue: String,
|
||||
isTransparentRecipient: Boolean,
|
||||
fiatCurrencyConversion: FiatCurrencyConversion?,
|
||||
): AmountState {
|
||||
val isValid = validate(context, monetarySeparators, fiatValue)
|
||||
|
||||
if (!isValid) {
|
||||
return Invalid(value, fiatValue)
|
||||
}
|
||||
|
||||
val zatoshi =
|
||||
fiatCurrencyConversion?.toZatoshi(
|
||||
context = context,
|
||||
value = fiatValue,
|
||||
monetarySeparators = MonetarySeparators.current(java.util.Locale.getDefault())
|
||||
)
|
||||
|
||||
return when {
|
||||
(zatoshi == null) -> Invalid(value, fiatValue)
|
||||
(zatoshi.value == 0L && isTransparentRecipient) -> Invalid(value, fiatValue)
|
||||
else -> {
|
||||
Valid(
|
||||
value = zatoshi.toZecString(),
|
||||
zatoshi = zatoshi,
|
||||
fiatValue = fiatValue
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,22 +105,45 @@ sealed class AmountState(
|
|||
private const val TYPE_INVALID = "invalid" // $NON-NLS
|
||||
private const val KEY_TYPE = "type" // $NON-NLS
|
||||
private const val KEY_VALUE = "value" // $NON-NLS
|
||||
private const val KEY_FIAT_VALUE = "fiat_value" // $NON-NLS
|
||||
private const val KEY_ZATOSHI = "zatoshi" // $NON-NLS
|
||||
|
||||
private fun validate(
|
||||
context: Context,
|
||||
monetarySeparators: MonetarySeparators,
|
||||
value: String
|
||||
) = runCatching {
|
||||
ZecStringExt.filterContinuous(context, monetarySeparators, value)
|
||||
}.onFailure {
|
||||
Twig.error(it) { "Failed while filtering raw amount characters" }
|
||||
}.getOrDefault(false)
|
||||
|
||||
internal val Saver
|
||||
get() =
|
||||
run {
|
||||
mapSaver<AmountState>(
|
||||
mapSaver(
|
||||
save = { it.toSaverMap() },
|
||||
restore = {
|
||||
if (it.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
val amountString = (it[KEY_VALUE] as String)
|
||||
val fiatAmountString = (it[KEY_FIAT_VALUE] as String)
|
||||
val type = (it[KEY_TYPE] as String)
|
||||
when (type) {
|
||||
TYPE_VALID -> Valid(amountString, Zatoshi(it[KEY_ZATOSHI] as Long))
|
||||
TYPE_INVALID -> Invalid(amountString)
|
||||
TYPE_VALID ->
|
||||
Valid(
|
||||
value = amountString,
|
||||
fiatValue = fiatAmountString,
|
||||
zatoshi = Zatoshi(it[KEY_ZATOSHI] as Long)
|
||||
)
|
||||
|
||||
TYPE_INVALID ->
|
||||
Invalid(
|
||||
value = amountString,
|
||||
fiatValue = fiatAmountString
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
@ -84,9 +158,11 @@ sealed class AmountState(
|
|||
saverMap[KEY_TYPE] = TYPE_VALID
|
||||
saverMap[KEY_ZATOSHI] = this.zatoshi.value
|
||||
}
|
||||
|
||||
is Invalid -> saverMap[KEY_TYPE] = TYPE_INVALID
|
||||
}
|
||||
saverMap[KEY_VALUE] = this.value
|
||||
saverMap[KEY_FIAT_VALUE] = this.fiatValue
|
||||
|
||||
return saverMap
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ package co.electriccoin.zcash.ui.screen.send.view
|
|||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
@ -12,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||
|
@ -32,6 +34,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInRoot
|
||||
|
@ -47,6 +50,9 @@ import androidx.compose.ui.text.input.ImeAction
|
|||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.Memo
|
||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||
import cash.z.ecc.android.sdk.model.ZecSend
|
||||
|
@ -104,7 +110,12 @@ private fun PreviewSendForm() {
|
|||
recipientAddressState = RecipientAddressState("invalid_address", AddressType.Invalid()),
|
||||
onRecipientAddressChange = {},
|
||||
setAmountState = {},
|
||||
amountState = AmountState.Valid(ZatoshiFixture.ZATOSHI_LONG.toString(), ZatoshiFixture.new()),
|
||||
amountState =
|
||||
AmountState.Valid(
|
||||
value = ZatoshiFixture.ZATOSHI_LONG.toString(),
|
||||
fiatValue = "",
|
||||
zatoshi = ZatoshiFixture.new()
|
||||
),
|
||||
setMemoState = {},
|
||||
memoState = MemoState.new("Test message"),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
|
@ -135,7 +146,12 @@ private fun SendFormTransparentAddressPreview() {
|
|||
),
|
||||
onRecipientAddressChange = {},
|
||||
setAmountState = {},
|
||||
amountState = AmountState.Valid(ZatoshiFixture.ZATOSHI_LONG.toString(), ZatoshiFixture.new()),
|
||||
amountState =
|
||||
AmountState.Valid(
|
||||
value = ZatoshiFixture.ZATOSHI_LONG.toString(),
|
||||
fiatValue = "",
|
||||
zatoshi = ZatoshiFixture.new()
|
||||
),
|
||||
setMemoState = {},
|
||||
memoState = MemoState.new("Test message"),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
|
@ -363,7 +379,7 @@ private fun SendForm(
|
|||
Spacer(Modifier.size(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
SendFormAmountTextField(
|
||||
amountSate = amountState,
|
||||
amountState = amountState,
|
||||
imeAction =
|
||||
if (recipientAddressState.type == AddressType.Transparent) {
|
||||
ImeAction.Done
|
||||
|
@ -583,7 +599,7 @@ fun SendFormAddressTextField(
|
|||
@Suppress("LongParameterList", "LongMethod")
|
||||
@Composable
|
||||
fun SendFormAmountTextField(
|
||||
amountSate: AmountState,
|
||||
amountState: AmountState,
|
||||
imeAction: ImeAction,
|
||||
isTransparentRecipient: Boolean,
|
||||
monetarySeparators: MonetarySeparators,
|
||||
|
@ -597,9 +613,9 @@ fun SendFormAmountTextField(
|
|||
val zcashCurrency = ZcashCurrency.getLocalizedName(context)
|
||||
|
||||
val amountError =
|
||||
when (amountSate) {
|
||||
when (amountState) {
|
||||
is AmountState.Invalid -> {
|
||||
if (amountSate.value.isEmpty()) {
|
||||
if (amountState.value.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
stringResource(id = R.string.send_amount_invalid)
|
||||
|
@ -607,7 +623,7 @@ fun SendFormAmountTextField(
|
|||
}
|
||||
|
||||
is AmountState.Valid -> {
|
||||
if (walletSnapshot.spendableBalance() < amountSate.zatoshi) {
|
||||
if (walletSnapshot.spendableBalance() < amountState.zatoshi) {
|
||||
stringResource(id = R.string.send_amount_insufficient_balance)
|
||||
} else {
|
||||
null
|
||||
|
@ -629,47 +645,123 @@ fun SendFormAmountTextField(
|
|||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
FormTextField(
|
||||
value = amountSate.value,
|
||||
onValueChange = { newValue ->
|
||||
setAmountState(
|
||||
AmountState.new(
|
||||
context = context,
|
||||
value = newValue,
|
||||
monetarySeparators = monetarySeparators,
|
||||
isTransparentRecipient = isTransparentRecipient
|
||||
Row {
|
||||
FormTextField(
|
||||
textStyle = ZcashTheme.extendedTypography.textFieldValue.copy(fontSize = 14.sp),
|
||||
value = amountState.value,
|
||||
onValueChange = { newValue ->
|
||||
setAmountState(
|
||||
AmountState.newFromZec(
|
||||
context = context,
|
||||
value = newValue,
|
||||
monetarySeparators = monetarySeparators,
|
||||
isTransparentRecipient = isTransparentRecipient,
|
||||
fiatValue = amountState.fiatValue,
|
||||
fiatCurrencyConversion =
|
||||
(walletSnapshot.exchangeRateUsd as? FiatCurrencyResult.Success)
|
||||
?.currencyConversion
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
error = amountError,
|
||||
placeholder = {
|
||||
Text(
|
||||
text =
|
||||
stringResource(
|
||||
id = R.string.send_amount_hint,
|
||||
zcashCurrency
|
||||
),
|
||||
style = ZcashTheme.extendedTypography.textFieldHint,
|
||||
color = ZcashTheme.colors.textFieldHint
|
||||
)
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = imeAction
|
||||
),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onDone = {
|
||||
focusManager.clearFocus(true)
|
||||
},
|
||||
onNext = {
|
||||
focusManager.moveFocus(FocusDirection.Down)
|
||||
}
|
||||
),
|
||||
bringIntoViewRequester = bringIntoViewRequester,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
error = amountError,
|
||||
placeholder = {
|
||||
Text(
|
||||
text =
|
||||
stringResource(
|
||||
id = R.string.send_amount_hint,
|
||||
zcashCurrency
|
||||
),
|
||||
style = ZcashTheme.extendedTypography.textFieldHint,
|
||||
color = ZcashTheme.colors.textFieldHint
|
||||
)
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = imeAction
|
||||
),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onDone = {
|
||||
focusManager.clearFocus(true)
|
||||
},
|
||||
onNext = {
|
||||
focusManager.moveFocus(FocusDirection.Right)
|
||||
}
|
||||
),
|
||||
bringIntoViewRequester = bringIntoViewRequester,
|
||||
leadingIcon = {
|
||||
Image(
|
||||
modifier = Modifier.requiredSize(7.dp, 13.dp),
|
||||
painter = painterResource(R.drawable.ic_send_zashi),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(color = ZcashTheme.colors.secondaryColor),
|
||||
)
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingMin))
|
||||
Image(
|
||||
modifier = Modifier.padding(top = 24.dp),
|
||||
painter = painterResource(id = R.drawable.ic_send_convert),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(color = ZcashTheme.colors.secondaryColor),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingMin))
|
||||
FormTextField(
|
||||
textStyle = ZcashTheme.extendedTypography.textFieldValue.copy(fontSize = 14.sp),
|
||||
value = amountState.fiatValue,
|
||||
onValueChange = { newValue -> // TODO
|
||||
setAmountState(
|
||||
AmountState.newFromFiat(
|
||||
context = context,
|
||||
value = amountState.value,
|
||||
monetarySeparators = monetarySeparators,
|
||||
isTransparentRecipient = isTransparentRecipient,
|
||||
fiatValue = newValue,
|
||||
fiatCurrencyConversion =
|
||||
(walletSnapshot.exchangeRateUsd as? FiatCurrencyResult.Success)
|
||||
?.currencyConversion
|
||||
)
|
||||
)
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
placeholder = {
|
||||
Text(
|
||||
text =
|
||||
stringResource(
|
||||
id = R.string.send_usd_amount_hint,
|
||||
zcashCurrency
|
||||
),
|
||||
style = ZcashTheme.extendedTypography.textFieldHint,
|
||||
color = ZcashTheme.colors.textFieldHint
|
||||
)
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = imeAction
|
||||
),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onDone = {
|
||||
focusManager.clearFocus(true)
|
||||
},
|
||||
onNext = {
|
||||
focusManager.moveFocus(FocusDirection.Down)
|
||||
}
|
||||
),
|
||||
bringIntoViewRequester = bringIntoViewRequester,
|
||||
leadingIcon = {
|
||||
Image(
|
||||
modifier = Modifier.requiredSize(7.dp, 13.dp),
|
||||
painter = painterResource(R.drawable.ic_usd),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(color = ZcashTheme.colors.secondaryColor),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.Proposal
|
||||
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||
|
@ -27,6 +28,7 @@ import co.electriccoin.zcash.spackle.Twig
|
|||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.AuthenticationViewModel
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||
|
@ -74,6 +76,8 @@ internal fun MainActivity.WrapSendConfirmation(
|
|||
|
||||
val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value
|
||||
|
||||
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
|
||||
|
||||
WrapSendConfirmation(
|
||||
activity = this,
|
||||
arguments = arguments,
|
||||
|
@ -87,6 +91,7 @@ internal fun MainActivity.WrapSendConfirmation(
|
|||
supportMessage = supportMessage,
|
||||
synchronizer = synchronizer,
|
||||
topAppBarSubTitleState = walletState,
|
||||
walletSnapshot = walletSnapshot
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -106,6 +111,7 @@ internal fun WrapSendConfirmation(
|
|||
supportMessage: SupportInfo?,
|
||||
synchronizer: Synchronizer?,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
walletSnapshot: WalletSnapshot?
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
@ -206,6 +212,7 @@ internal fun WrapSendConfirmation(
|
|||
}
|
||||
},
|
||||
topAppBarSubTitleState = topAppBarSubTitleState,
|
||||
exchangeRate = walletSnapshot?.exchangeRateUsd ?: FiatCurrencyResult.Loading()
|
||||
)
|
||||
|
||||
if (sendFundsAuthentication.value) {
|
||||
|
|
|
@ -32,6 +32,7 @@ import androidx.compose.ui.text.font.FontStyle
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyResult
|
||||
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
||||
import cash.z.ecc.android.sdk.model.ZecSend
|
||||
|
@ -39,6 +40,7 @@ import cash.z.ecc.sdk.extension.toZecStringFull
|
|||
import cash.z.ecc.sdk.fixture.MemoFixture
|
||||
import cash.z.ecc.sdk.fixture.ZatoshiFixture
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.StyledExchangeBalance
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceWidgetBigLineOnly
|
||||
import co.electriccoin.zcash.ui.common.extension.asZecAmountTriple
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
|
@ -83,7 +85,8 @@ private fun SendConfirmationPreview() {
|
|||
stage = SendConfirmationStage.Confirmation,
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
onContactSupport = {},
|
||||
submissionResults = emptyList<TransactionSubmitResult>().toImmutableList()
|
||||
submissionResults = emptyList<TransactionSubmitResult>().toImmutableList(),
|
||||
exchangeRate = FiatCurrencyResult.Loading()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +109,8 @@ private fun SendConfirmationDarkPreview() {
|
|||
stage = SendConfirmationStage.Confirmation,
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
onContactSupport = {},
|
||||
submissionResults = emptyList<TransactionSubmitResult>().toImmutableList()
|
||||
submissionResults = emptyList<TransactionSubmitResult>().toImmutableList(),
|
||||
exchangeRate = FiatCurrencyResult.Loading()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +133,8 @@ private fun SendMultipleErrorPreview() {
|
|||
stage = SendConfirmationStage.MultipleTrxFailure,
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
onContactSupport = {},
|
||||
submissionResults = emptyList<TransactionSubmitResult>().toImmutableList()
|
||||
submissionResults = emptyList<TransactionSubmitResult>().toImmutableList(),
|
||||
exchangeRate = FiatCurrencyResult.Loading()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +157,8 @@ private fun SendMultipleErrorDarkPreview() {
|
|||
stage = SendConfirmationStage.MultipleTrxFailure,
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
onContactSupport = {},
|
||||
submissionResults = emptyList<TransactionSubmitResult>().toImmutableList()
|
||||
submissionResults = emptyList<TransactionSubmitResult>().toImmutableList(),
|
||||
exchangeRate = FiatCurrencyResult.Loading()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +177,8 @@ private fun PreviewSendConfirmation() {
|
|||
),
|
||||
onConfirmation = {},
|
||||
onBack = {},
|
||||
isSending = false
|
||||
isSending = false,
|
||||
exchangeRate = FiatCurrencyResult.Loading()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -244,6 +251,7 @@ fun SendConfirmation(
|
|||
submissionResults: ImmutableList<TransactionSubmitResult>,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
zecSend: ZecSend,
|
||||
exchangeRate: FiatCurrencyResult
|
||||
) {
|
||||
BlankBgScaffold(
|
||||
topBar = {
|
||||
|
@ -269,7 +277,8 @@ fun SendConfirmation(
|
|||
bottom = paddingValues.calculateBottomPadding(),
|
||||
start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
|
||||
end = ZcashTheme.dimens.screenHorizontalSpacingRegular
|
||||
)
|
||||
),
|
||||
exchangeRate = exchangeRate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -327,6 +336,7 @@ private fun SendConfirmationMainContent(
|
|||
submissionResults: ImmutableList<TransactionSubmitResult>,
|
||||
zecSend: ZecSend,
|
||||
modifier: Modifier = Modifier,
|
||||
exchangeRate: FiatCurrencyResult
|
||||
) {
|
||||
when (stage) {
|
||||
SendConfirmationStage.Confirmation, SendConfirmationStage.Sending, is SendConfirmationStage.Failure -> {
|
||||
|
@ -335,7 +345,8 @@ private fun SendConfirmationMainContent(
|
|||
onBack = onBack,
|
||||
onConfirmation = onConfirmation,
|
||||
isSending = stage == SendConfirmationStage.Sending,
|
||||
modifier = modifier
|
||||
modifier = modifier,
|
||||
exchangeRate = exchangeRate
|
||||
)
|
||||
if (stage is SendConfirmationStage.Failure) {
|
||||
SendFailure(
|
||||
|
@ -358,6 +369,7 @@ private fun SendConfirmationMainContent(
|
|||
@Suppress("LongMethod")
|
||||
private fun SendConfirmationContent(
|
||||
zecSend: ZecSend,
|
||||
exchangeRate: FiatCurrencyResult,
|
||||
onConfirmation: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
isSending: Boolean,
|
||||
|
@ -380,6 +392,12 @@ private fun SendConfirmationContent(
|
|||
isHideBalances = false
|
||||
)
|
||||
|
||||
StyledExchangeBalance(
|
||||
zatoshi = zecSend.amount,
|
||||
exchangeRate = exchangeRate,
|
||||
isHideBalances = false
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
Small(stringResource(R.string.send_confirmation_address))
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="11dp"
|
||||
android:height="9dp"
|
||||
android:viewportWidth="11"
|
||||
android:viewportHeight="9">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0.5,0.601h10v8h-10z"/>
|
||||
<path
|
||||
android:pathData="M3.289,8.601L0.5,6.047V5.181H10.5V6.162H2.064L3.926,7.868L3.289,8.601ZM7.71,0.601L10.499,3.155V4.053L0.499,4.022V3.04H8.935L7.073,1.334L7.71,0.601Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="7dp"
|
||||
android:height="13dp"
|
||||
android:viewportWidth="7"
|
||||
android:viewportHeight="13">
|
||||
<path
|
||||
android:pathData="M7.009,3.942V2.534H4.48V0.98H2.926V2.534H0.397V4.405H4.321L0.397,9.728V11.138H2.926V12.683H4.48V11.138H7.009V9.267H3.085L7.009,3.942Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="7dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="7"
|
||||
android:viewportHeight="12">
|
||||
<path
|
||||
android:pathData="M3.067,2.719V0.381H3.865V2.719H3.067ZM3.067,11.343V8.921H3.865V11.343H3.067ZM3.431,9.649C2.918,9.649 2.456,9.593 2.045,9.481C1.635,9.369 1.285,9.215 0.995,9.019C0.706,8.814 0.482,8.571 0.323,8.291C0.174,8.011 0.099,7.694 0.099,7.339C0.099,7.302 0.099,7.264 0.099,7.227C0.099,7.19 0.104,7.162 0.113,7.143H1.989C1.989,7.162 1.989,7.18 1.989,7.199C1.989,7.218 1.989,7.236 1.989,7.255C1.999,7.488 2.073,7.68 2.213,7.829C2.353,7.969 2.535,8.072 2.759,8.137C2.993,8.202 3.235,8.235 3.487,8.235C3.711,8.235 3.926,8.216 4.131,8.179C4.346,8.132 4.523,8.053 4.663,7.941C4.813,7.829 4.887,7.684 4.887,7.507C4.887,7.283 4.794,7.11 4.607,6.989C4.43,6.868 4.192,6.77 3.893,6.695C3.604,6.62 3.287,6.536 2.941,6.443C2.624,6.368 2.307,6.284 1.989,6.191C1.672,6.088 1.383,5.958 1.121,5.799C0.869,5.64 0.664,5.435 0.505,5.183C0.347,4.922 0.267,4.595 0.267,4.203C0.267,3.82 0.351,3.489 0.519,3.209C0.687,2.92 0.916,2.682 1.205,2.495C1.504,2.308 1.849,2.173 2.241,2.089C2.643,1.996 3.072,1.949 3.529,1.949C3.959,1.949 4.36,1.996 4.733,2.089C5.107,2.173 5.433,2.304 5.713,2.481C5.993,2.649 6.213,2.864 6.371,3.125C6.53,3.377 6.609,3.662 6.609,3.979C6.609,4.044 6.609,4.105 6.609,4.161C6.609,4.217 6.605,4.254 6.595,4.273H4.733V4.161C4.733,3.993 4.682,3.853 4.579,3.741C4.477,3.62 4.327,3.526 4.131,3.461C3.945,3.396 3.716,3.363 3.445,3.363C3.259,3.363 3.086,3.377 2.927,3.405C2.778,3.433 2.647,3.475 2.535,3.531C2.423,3.587 2.335,3.657 2.269,3.741C2.213,3.816 2.185,3.909 2.185,4.021C2.185,4.18 2.251,4.31 2.381,4.413C2.521,4.506 2.703,4.586 2.927,4.651C3.151,4.716 3.399,4.786 3.669,4.861C4.005,4.954 4.355,5.048 4.719,5.141C5.093,5.225 5.438,5.342 5.755,5.491C6.073,5.64 6.329,5.855 6.525,6.135C6.721,6.406 6.819,6.774 6.819,7.241C6.819,7.689 6.731,8.067 6.553,8.375C6.385,8.683 6.147,8.93 5.839,9.117C5.531,9.304 5.172,9.439 4.761,9.523C4.351,9.607 3.907,9.649 3.431,9.649Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
|
@ -6,6 +6,7 @@
|
|||
<string name="send_address_invalid">Invalid address</string>
|
||||
<string name="send_amount_label">Amount:</string>
|
||||
<string name="send_amount_hint"><xliff:g id="currency" example="ZEC">%1$s</xliff:g> Amount</string>
|
||||
<string name="send_usd_amount_hint"><xliff:g id="currency" example="USD">%1$s</xliff:g> Amount</string>
|
||||
<string name="send_amount_insufficient_balance">Insufficient funds</string>
|
||||
<string name="send_amount_invalid">Invalid amount</string>
|
||||
<string name="send_memo_label">Message</string>
|
||||
|
|
Loading…
Reference in New Issue