Business logic implementation

This commit is contained in:
Milan Cerovsky 2024-12-01 13:25:43 +01:00
parent 088b2c82a5
commit c8ac10fcdc
17 changed files with 647 additions and 28 deletions

View File

@ -0,0 +1,23 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.sdk.model.WalletAddress
enum class AddressType {
UNIFIED, TRANSPARENT, SAPLING, TEX;
suspend fun toWalletAddress(address: String) = when (this) {
UNIFIED -> WalletAddress.Unified.new(address)
TRANSPARENT -> WalletAddress.Transparent.new(address)
SAPLING -> WalletAddress.Sapling.new(address)
TEX -> WalletAddress.Tex.new(address)
}
companion object {
fun fromWalletAddress(walletAddress: WalletAddress) = when (walletAddress) {
is WalletAddress.Sapling -> SAPLING
is WalletAddress.Tex -> TEX
is WalletAddress.Transparent -> TRANSPARENT
is WalletAddress.Unified -> UNIFIED
}
}
}

View File

@ -1,6 +1,7 @@
package co.electriccoin.zcash.ui.design.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
@ -21,7 +22,7 @@ import co.electriccoin.zcash.ui.design.util.stringRes
@Composable
fun ZashiBottomBar(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
content: @Composable ColumnScope.() -> Unit,
) {
Surface(
shape = RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp),

View File

@ -38,9 +38,9 @@ class SendViewIntegrationTest {
goToQrScanner = {},
goBack = {},
goBalances = {},
goSettings = {},
goPaymentRequest = { _, _ -> },
goSendConfirmation = {},
goReviewKeystoneTransaction = {},
)
}

View File

@ -7,7 +7,7 @@ import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class SupportInfoTest {
class SupportFinancialInfoStateTest {
@Test
fun filter_time() =
runTest {

View File

@ -8,8 +8,10 @@ import co.electriccoin.zcash.ui.common.usecase.DeriveKeystoneAccountUnifiedAddre
import co.electriccoin.zcash.ui.common.usecase.GetAddressesUseCase
import co.electriccoin.zcash.ui.common.usecase.GetBackupPersistableWalletUseCase
import co.electriccoin.zcash.ui.common.usecase.GetContactByAddressUseCase
import co.electriccoin.zcash.ui.common.usecase.GetLoadedExchangeRateUseCase
import co.electriccoin.zcash.ui.common.usecase.GetPersistableWalletUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSelectedEndpointUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSpendingKeyUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSupportUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase
@ -100,4 +102,6 @@ val useCaseModule =
factoryOf(::CreateKeystoneAccountUseCase)
factoryOf(::DeriveKeystoneAccountUnifiedAddressUseCase)
factoryOf(::DecodeUrToZashiAccountsUseCase)
factoryOf(::GetLoadedExchangeRateUseCase)
factoryOf(::GetSelectedWalletAccountUseCase)
}

View File

@ -22,6 +22,8 @@ import co.electriccoin.zcash.ui.screen.receive.viewmodel.ReceiveViewModel
import co.electriccoin.zcash.ui.screen.request.viewmodel.RequestViewModel
import co.electriccoin.zcash.ui.screen.restore.viewmodel.RestoreViewModel
import co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel.RestoreSuccessViewModel
import co.electriccoin.zcash.ui.screen.reviewtransaction.ReviewKeystoneTransaction
import co.electriccoin.zcash.ui.screen.reviewtransaction.ReviewKeystoneTransactionViewModel
import co.electriccoin.zcash.ui.screen.scan.ScanNavigationArgs
import co.electriccoin.zcash.ui.screen.scan.viewmodel.ScanViewModel
import co.electriccoin.zcash.ui.screen.scankeystone.viewmodel.ScanKeystoneSignInRequestViewModel
@ -113,4 +115,11 @@ val viewModelModule =
decodeUrToZashiAccounts = get()
)
}
viewModel { (args: ReviewKeystoneTransaction) ->
ReviewKeystoneTransactionViewModel(
args = args,
observeContactByAddress = get(),
getLoadedExchangeRate = get(),
)
}
}

View File

@ -93,6 +93,8 @@ import co.electriccoin.zcash.ui.screen.paymentrequest.model.PaymentRequestArgume
import co.electriccoin.zcash.ui.screen.qrcode.WrapQrCode
import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType
import co.electriccoin.zcash.ui.screen.request.WrapRequest
import co.electriccoin.zcash.ui.screen.reviewtransaction.AndroidReviewKeystoneTransaction
import co.electriccoin.zcash.ui.screen.reviewtransaction.ReviewKeystoneTransaction
import co.electriccoin.zcash.ui.screen.scan.ScanNavigationArgs
import co.electriccoin.zcash.ui.screen.scan.WrapScanValidator
import co.electriccoin.zcash.ui.screen.scankeystone.ScanKeystoneNavigationArgs
@ -394,9 +396,11 @@ internal fun MainActivity.Navigation() {
composable(ConnectKeystoneArgs.PATH) {
AndroidConnectKeystone()
}
composable<SelectKeystoneAccount> { backStackEntry ->
val args = backStackEntry.toRoute<SelectKeystoneAccount>()
AndroidSelectKeystoneAccount(args)
composable<SelectKeystoneAccount> {
AndroidSelectKeystoneAccount(it.toRoute())
}
composable<ReviewKeystoneTransaction> {
AndroidReviewKeystoneTransaction(it.toRoute())
}
}
}
@ -417,6 +421,9 @@ private fun MainActivity.NavigationHome(
}
navController.navigateJustOnce(SEND_CONFIRMATION)
},
goReviewKeystoneTransaction = {
navController.navigate(it)
},
goPaymentRequest = { zecSend, zip321Uri ->
navController.currentBackStackEntry?.savedStateHandle?.let { handle ->
fillInHandleForPaymentRequest(handle, zecSend, zip321Uri)

View File

@ -1,6 +1,8 @@
package co.electriccoin.zcash.ui.common.datasource
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.WalletCoordinator
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletAddress
@ -14,6 +16,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@ -23,10 +26,12 @@ import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
interface AccountDataSource {
@ -73,7 +78,7 @@ class AccountDataSourceImpl(
if (synchronizer == null || walletBalances == null || persistableWallet == null) {
null
} else {
synchronizer.getAccounts().mapIndexed { index, account ->
synchronizer.getAccountsSafe().mapIndexed { index, account ->
val balance = walletBalances.getValue(account)
val spendingKey = deriveSpendingKey(persistableWallet)
@ -91,11 +96,26 @@ class AccountDataSourceImpl(
)
}
}
}.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT, Duration.ZERO),
initialValue = null
)
}.flowOn(Dispatchers.Default)
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT, Duration.ZERO),
initialValue = null
)
private suspend fun Synchronizer.getAccountsSafe(): List<Account> {
var accounts: List<Account>? = null
while (accounts == null) {
try {
accounts = getAccounts()
} catch (_: Throwable) {
delay(1.seconds)
}
}
return accounts
}
private suspend fun deriveSpendingKey(persistableWallet: PersistableWallet): UnifiedSpendingKey? {
// crashes currently

View File

@ -0,0 +1,17 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepository
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import kotlinx.coroutines.flow.first
class GetLoadedExchangeRateUseCase(
private val exchangeRateRepository: ExchangeRateRepository
) {
suspend operator fun invoke() = exchangeRateRepository.state.first {
when (it) {
is ExchangeRateState.Data -> it.isLoading
is ExchangeRateState.OptIn -> true
ExchangeRateState.OptedOut -> true
}
}
}

View File

@ -0,0 +1,9 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.common.datasource.AccountDataSource
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
class GetSelectedWalletAccountUseCase(private val accountDataSource: AccountDataSource) {
suspend operator fun invoke() = accountDataSource.getSelectedAccount()
}

View File

@ -31,6 +31,7 @@ import co.electriccoin.zcash.ui.screen.balances.WrapBalances
import co.electriccoin.zcash.ui.screen.home.model.TabItem
import co.electriccoin.zcash.ui.screen.home.view.Home
import co.electriccoin.zcash.ui.screen.receive.WrapReceive
import co.electriccoin.zcash.ui.screen.reviewtransaction.ReviewKeystoneTransaction
import co.electriccoin.zcash.ui.screen.send.WrapSend
import co.electriccoin.zcash.ui.screen.send.model.SendArguments
import kotlinx.collections.immutable.persistentListOf
@ -43,6 +44,7 @@ internal fun WrapHome(
goMultiTrxSubmissionFailure: () -> Unit,
goScan: () -> Unit,
goSendConfirmation: (ZecSend) -> Unit,
goReviewKeystoneTransaction: (ReviewKeystoneTransaction) -> Unit,
goPaymentRequest: (ZecSend, String) -> Unit,
sendArguments: SendArguments
) {
@ -92,7 +94,8 @@ internal fun WrapHome(
isShowingRestoreSuccess = isShowingRestoreSuccess,
sendArguments = sendArguments,
setShowingRestoreSuccess = setShowingRestoreSuccess,
walletSnapshot = walletSnapshot
walletSnapshot = walletSnapshot,
goReviewKeystoneTransaction = goReviewKeystoneTransaction,
)
}
@ -102,6 +105,7 @@ internal fun WrapHome(
goMultiTrxSubmissionFailure: () -> Unit,
goScan: () -> Unit,
goSendConfirmation: (ZecSend) -> Unit,
goReviewKeystoneTransaction: (ReviewKeystoneTransaction) -> Unit,
goPaymentRequest: (ZecSend, String) -> Unit,
isKeepScreenOnWhileSyncing: Boolean?,
isShowingRestoreSuccess: Boolean,
@ -180,7 +184,8 @@ internal fun WrapHome(
},
goSendConfirmation = goSendConfirmation,
goPaymentRequest = goPaymentRequest,
sendArguments = sendArguments
sendArguments = sendArguments,
goReviewKeystoneTransaction = goReviewKeystoneTransaction
)
}
),

View File

@ -0,0 +1,23 @@
package co.electriccoin.zcash.ui.screen.reviewtransaction
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf
@Composable
fun AndroidReviewKeystoneTransaction(args: ReviewKeystoneTransaction) {
val viewModel = koinViewModel<ReviewKeystoneTransactionViewModel> { parametersOf(args) }
val state by viewModel.state.collectAsStateWithLifecycle()
BackHandler {
state?.onBack
}
state?.let {
ReviewTransactionView(it)
}
}

View File

@ -0,0 +1,20 @@
package co.electriccoin.zcash.ui.screen.reviewtransaction
import cash.z.ecc.android.sdk.model.Memo
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.model.AddressType
import kotlinx.serialization.Serializable
@Serializable
data class ReviewKeystoneTransaction(
val addressString: String,
val addressType: AddressType,
val amountLong: Long,
val memoString: String?
) {
val amount: Zatoshi
get() = Zatoshi(amountLong)
val memo: Memo?
get() = memoString?.let { Memo(it) }
}

View File

@ -0,0 +1,78 @@
package co.electriccoin.zcash.ui.screen.reviewtransaction
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.ui.common.usecase.GetLoadedExchangeRateUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveContactByAddressUseCase
import co.electriccoin.zcash.ui.design.R
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.util.stringRes
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class ReviewKeystoneTransactionViewModel(
args: ReviewKeystoneTransaction,
observeContactByAddress: ObserveContactByAddressUseCase,
private val getLoadedExchangeRate: GetLoadedExchangeRateUseCase
) : ViewModel() {
val state = observeContactByAddress(args.addressString).map { addressBookContact ->
ReviewTransactionState(
title = stringRes("Review"),
items = listOfNotNull(
AmountState(
title = stringRes("Total Amount"),
amount = args.amount,
exchangeRate = getLoadedExchangeRate(),
),
ReceiverState(
title = stringRes("Sending to"),
name = addressBookContact?.name?.let { stringRes(it) },
address = stringRes(args.addressString)
),
SenderState(
title = stringRes("Sending from"),
icon = R.drawable.ic_item_keystone,
name = stringRes("Keystone wallet"),
),
FinancialInfoState(
title = stringRes("Amount"),
amount = args.amount
),
FinancialInfoState(
title = stringRes("Fee"),
amount = args.amount
),
args.memo?.let {
MessageState(
title = stringRes("Message"),
message = stringRes(it.value)
)
}
),
primaryButton = ButtonState(
stringRes("Confirm with Keystone"),
onClick = ::onConfirmClick
),
negativeButton = ButtonState(
stringRes("Cancel"),
onClick = ::onCancelClick
),
onBack = ::onBack,
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null)
private fun onBack() {
TODO("Not yet implemented")
}
private fun onCancelClick() {
TODO("Not yet implemented")
}
private fun onConfirmClick() {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,44 @@
package co.electriccoin.zcash.ui.screen.reviewtransaction
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.util.StringResource
data class ReviewTransactionState(
val title: StringResource,
val items: List<ReviewTransactionItemState>,
val primaryButton: ButtonState,
val negativeButton: ButtonState,
val onBack: () -> Unit,
)
sealed interface ReviewTransactionItemState
data class AmountState(
val title: StringResource,
val amount: Zatoshi,
val exchangeRate: ExchangeRateState,
) : ReviewTransactionItemState
data class ReceiverState(
val title: StringResource,
val name: StringResource?,
val address: StringResource,
) : ReviewTransactionItemState
data class SenderState(
val title: StringResource,
val icon: Int,
val name: StringResource
) : ReviewTransactionItemState
data class FinancialInfoState(
val title: StringResource,
val amount: Zatoshi,
) : ReviewTransactionItemState
data class MessageState(
val title: StringResource,
val message: StringResource
) : ReviewTransactionItemState

View File

@ -0,0 +1,321 @@
package co.electriccoin.zcash.ui.screen.reviewtransaction
import androidx.compose.foundation.Image
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
import cash.z.ecc.sdk.extension.toZecStringFull
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import co.electriccoin.zcash.ui.common.compose.BalanceWidgetBigLineOnly
import co.electriccoin.zcash.ui.common.extension.asZecAmountTriple
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import co.electriccoin.zcash.ui.design.R
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.StyledBalance
import co.electriccoin.zcash.ui.design.component.StyledBalanceDefaults
import co.electriccoin.zcash.ui.design.component.TextFieldState
import co.electriccoin.zcash.ui.design.component.ZashiBottomBar
import co.electriccoin.zcash.ui.design.component.ZashiButton
import co.electriccoin.zcash.ui.design.component.ZashiButtonDefaults
import co.electriccoin.zcash.ui.design.component.ZashiSmallTopAppBar
import co.electriccoin.zcash.ui.design.component.ZashiTextField
import co.electriccoin.zcash.ui.design.component.ZashiTextFieldDefaults
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.getValue
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
import co.electriccoin.zcash.ui.design.util.stringRes
import co.electriccoin.zcash.ui.screen.exchangerate.widget.StyledExchangeLabel
import kotlinx.datetime.Clock
@Composable
fun ReviewTransactionView(state: ReviewTransactionState) {
BlankBgScaffold(
topBar = {
ZashiSmallTopAppBar(
title = state.title.getValue()
)
}
) {
Column(
modifier = Modifier
.fillMaxSize()
) {
Column(
modifier = Modifier
.weight(1f)
.verticalScroll(rememberScrollState())
.scaffoldPadding(it)
) {
state.items.forEachIndexed { index, item ->
when (item) {
is AmountState -> {
AmountWidget(item)
}
is ReceiverState -> {
Spacer(Modifier.height(24.dp))
ReceiverWidget(item)
}
is SenderState -> {
Spacer(Modifier.height(20.dp))
SenderWidget(item)
Spacer(Modifier.height(16.dp))
}
is FinancialInfoState -> {
Spacer(Modifier.height(16.dp))
FinancialInfoWidget(item)
}
is MessageState -> {
Spacer(Modifier.height(16.dp))
MessageWidget(item)
}
}
}
Spacer(Modifier.height(32.dp))
}
BottomBar(state)
}
}
}
@Composable
fun SenderWidget(state: SenderState) {
Column {
Text(
text = state.title.getValue(),
style = ZashiTypography.textSm,
color = ZashiColors.Text.textTertiary,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(6.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier.size(32.dp),
painter = painterResource(id = state.icon),
contentDescription = null
)
Spacer(Modifier.width(16.dp))
Text(
text = state.name.getValue(),
style = ZashiTypography.textMd,
color = ZashiColors.Text.textPrimary,
fontWeight = FontWeight.SemiBold
)
}
}
}
@Composable
fun ReceiverWidget(state: ReceiverState) {
Column {
Text(
state.title.getValue(),
style = ZashiTypography.textSm,
color = ZashiColors.Text.textTertiary,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(8.dp))
if (state.name != null) {
Text(
state.name.getValue(),
style = ZashiTypography.textSm,
color = ZashiColors.Inputs.Filled.label,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(4.dp))
}
Text(
state.address.getValue(),
style = ZashiTypography.textXs,
color = ZashiColors.Text.textPrimary
)
}
}
@Composable
fun MessageWidget(state: MessageState) {
Column {
Text(
state.title.getValue(),
style = ZashiTypography.textSm,
fontWeight = FontWeight.Medium,
color = ZashiColors.Text.textTertiary
)
Spacer(modifier = Modifier.height(8.dp))
ZashiTextField(
state = TextFieldState(value = state.message, isEnabled = false) {},
modifier =
Modifier
.fillMaxWidth(),
colors =
ZashiTextFieldDefaults.defaultColors(
disabledTextColor = ZashiColors.Inputs.Filled.text,
disabledHintColor = ZashiColors.Inputs.Disabled.hint,
disabledBorderColor = Color.Unspecified,
disabledContainerColor = ZashiColors.Inputs.Disabled.bg,
disabledPlaceholderColor = ZashiColors.Inputs.Disabled.text,
),
minLines = 4
)
}
}
@Composable
fun FinancialInfoWidget(state: FinancialInfoState) {
Row {
Text(
modifier = Modifier.weight(1f),
text = state.title.getValue(),
style = ZashiTypography.textSm,
fontWeight = FontWeight.Medium,
color = ZashiColors.Text.textTertiary
)
StyledBalance(
balanceParts = state.amount.toZecStringFull().asZecAmountTriple(),
isHideBalances = false,
textStyle =
StyledBalanceDefaults.textStyles(
mostSignificantPart = ZashiTypography.textSm.copy(fontWeight = FontWeight.SemiBold),
leastSignificantPart = ZashiTypography.textXxs.copy(fontWeight = FontWeight.SemiBold, fontSize = 8.sp)
),
)
}
}
@Composable
fun AmountWidget(state: AmountState) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = state.title.getValue(),
style = ZashiTypography.textSm,
color = ZashiColors.Text.textPrimary
)
BalanceWidgetBigLineOnly(
parts = state.amount.toZecStringFull().asZecAmountTriple(),
isHideBalances = false
)
StyledExchangeLabel(
zatoshi = state.amount,
state = state.exchangeRate,
isHideBalances = false,
style = ZashiTypography.textMd.copy(fontWeight = FontWeight.SemiBold),
textColor = ZashiColors.Text.textPrimary
)
}
}
@Composable
private fun BottomBar(state: ReviewTransactionState) {
ZashiBottomBar {
ZashiButton(
state = state.primaryButton,
modifier =
Modifier
.padding(horizontal = 24.dp)
.fillMaxWidth()
)
ZashiButton(
state = state.negativeButton,
colors = ZashiButtonDefaults.secondaryColors(),
modifier =
Modifier
.padding(horizontal = 24.dp)
.fillMaxWidth()
)
}
}
@PreviewScreens
@Composable
private fun Preview() = ZcashTheme {
ReviewTransactionView(
state = ReviewTransactionState(
title = stringRes("Review"),
items = listOf(
AmountState(
title = stringRes("Total Amount"),
amount = ZatoshiFixture.new(),
exchangeRate = ExchangeRateState.Data(
currencyConversion = FiatCurrencyConversion(
timestamp = Clock.System.now(),
priceOfZec = 50.0
),
isLoading = false,
isStale = false,
isRefreshEnabled = false,
onRefresh = {}
),
),
ReceiverState(
title = stringRes("Total Amount"),
name = stringRes("Receiver Name"),
address = stringRes("Receiver Address")
),
SenderState(
title = stringRes("Sending from"),
icon = R.drawable.ic_item_keystone,
name = stringRes("Keystone wallet"),
),
FinancialInfoState(
title = stringRes("Amount"),
amount = ZatoshiFixture.new()
),
FinancialInfoState(
title = stringRes("Fee"),
amount = ZatoshiFixture.new()
),
MessageState(
title = stringRes("Message"),
message = stringRes("Message")
)
),
primaryButton = ButtonState(
stringRes("Confirm with Keystone")
),
negativeButton = ButtonState(
stringRes("Cancel")
),
onBack = {},
)
)
}

View File

@ -28,12 +28,16 @@ import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.common.compose.BalanceState
import co.electriccoin.zcash.ui.common.compose.LocalActivity
import co.electriccoin.zcash.ui.common.compose.LocalNavController
import co.electriccoin.zcash.ui.common.model.KeystoneAccount
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.model.ZashiAccount
import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase
import co.electriccoin.zcash.ui.common.viewmodel.HomeViewModel
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.common.viewmodel.ZashiMainTopAppBarViewModel
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
import co.electriccoin.zcash.ui.screen.reviewtransaction.ReviewKeystoneTransaction
import co.electriccoin.zcash.ui.screen.send.ext.Saver
import co.electriccoin.zcash.ui.screen.send.model.AmountState
import co.electriccoin.zcash.ui.screen.send.model.MemoState
@ -43,6 +47,7 @@ import co.electriccoin.zcash.ui.screen.send.model.SendStage
import co.electriccoin.zcash.ui.screen.send.view.Send
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
import org.zecdev.zip321.ZIP321
import java.util.Locale
@ -54,6 +59,7 @@ internal fun WrapSend(
goBack: () -> Unit,
goBalances: () -> Unit,
goSendConfirmation: (ZecSend) -> Unit,
goReviewKeystoneTransaction: (ReviewKeystoneTransaction) -> Unit,
goPaymentRequest: (ZecSend, String) -> Unit,
) {
val activity = LocalActivity.current
@ -93,6 +99,7 @@ internal fun WrapSend(
hasCameraFeature = hasCameraFeature,
monetarySeparators = monetarySeparators,
exchangeRateState = exchangeRateState,
goReviewKeystoneTransaction = goReviewKeystoneTransaction
)
}
@ -107,6 +114,7 @@ internal fun WrapSend(
goBack: () -> Unit,
goBalances: () -> Unit,
goSendConfirmation: (ZecSend) -> Unit,
goReviewKeystoneTransaction: (ReviewKeystoneTransaction) -> Unit,
goPaymentRequest: (ZecSend, String) -> Unit,
hasCameraFeature: Boolean,
monetarySeparators: MonetarySeparators,
@ -121,6 +129,8 @@ internal fun WrapSend(
val viewModel = koinViewModel<SendViewModel>()
val getSelectedWalletAccount = koinInject<GetSelectedWalletAccountUseCase>()
LaunchedEffect(Unit) {
viewModel.navigateCommand.collect {
navController.navigate(it)
@ -263,21 +273,49 @@ internal fun WrapSend(
isHideBalances = isHideBalances,
sendStage = sendStage,
onCreateZecSend = { newZecSend ->
goReviewKeystoneTransaction(
ReviewKeystoneTransaction(
addressString = newZecSend.destination.address,
addressType = cash.z.ecc.sdk.model.AddressType.fromWalletAddress(newZecSend.destination),
amountLong = newZecSend.amount.value,
memoString = newZecSend.memo.value.takeIf { it.isNotEmpty() },
)
)
scope.launch {
spendingKey?.let {
Twig.debug { "Getting send transaction proposal" }
runCatching {
synchronizer.proposeSend(spendingKey.account, newZecSend)
}.onSuccess { proposal ->
Twig.debug { "Transaction proposal successful: ${proposal.toPrettyString()}" }
val enrichedZecSend = newZecSend.copy(proposal = proposal)
setZecSend(enrichedZecSend)
goSendConfirmation(enrichedZecSend)
}.onFailure {
Twig.error(it) { "Transaction proposal failed" }
setSendStage(SendStage.SendFailure(it.message ?: ""))
}
}
// goReviewKeystoneTransaction(
// ReviewKeystoneTransaction(
// addressString = newZecSend.destination.address,
// addressType = cash.z.ecc.sdk.model.AddressType.fromWalletAddress(newZecSend.destination),
// amountLong = newZecSend.amount.value,
// memoString = newZecSend.memo.value,
// )
// )
// when (getSelectedWalletAccount()) {
// is KeystoneAccount -> {
// goReviewKeystoneTransaction(
// ReviewKeystoneTransaction(
// addressString = newZecSend.destination.address,
// addressType = cash.z.ecc.sdk.model.AddressType.fromWalletAddress(newZecSend.destination),
// amountLong = newZecSend.amount.value,
// memoString = newZecSend.memo.value,
// )
// )
// }
// is ZashiAccount -> spendingKey?.let {
// Twig.debug { "Getting send transaction proposal" }
// runCatching {
// synchronizer.proposeSend(spendingKey.account, newZecSend)
// }.onSuccess { proposal ->
// Twig.debug { "Transaction proposal successful: ${proposal.toPrettyString()}" }
// val enrichedZecSend = newZecSend.copy(proposal = proposal)
// setZecSend(enrichedZecSend)
// goSendConfirmation(enrichedZecSend)
// }.onFailure {
// Twig.error(it) { "Transaction proposal failed" }
// setSendStage(SendStage.SendFailure(it.message ?: ""))
// }
// }
// }
}
},
onBack = onBackAction,