[#1449] Display Synchronizer details in dialog
- Closes #1449 - Synchronizer status details are now available to users by pressing the simple status view. The details are displayed within the predefined Zashi dialog on the Balances and Account screens - As this view also presents information about Zashi app updates available on Google Play, by pressing the view, the app redirects users to Google Play Zashi’s page - As agreed, we’re moving towards more rounded corners in dialogs, which is part of these changes, too - Added also several minor Balances screen UI improvements - Improved biometric flow without any authentication method set on older Android versions - Changelog update
This commit is contained in:
parent
00db536674
commit
7e9e89725b
|
@ -14,6 +14,14 @@ directly impact users rather than highlighting other key architectural updates.*
|
||||||
cases: Send funds, Recovery Phrase, Export Private Data, and Delete Wallet.
|
cases: Send funds, Recovery Phrase, Export Private Data, and Delete Wallet.
|
||||||
- The app entry animation has been reworked to apply on every app access point, i.e. it will be displayed when
|
- The app entry animation has been reworked to apply on every app access point, i.e. it will be displayed when
|
||||||
users return to an already set up app as well.
|
users return to an already set up app as well.
|
||||||
|
- Synchronizer status details are now available to users by pressing the simple status view placed above the
|
||||||
|
synchronization progress bar. The details are displayed within a dialog window on the Balances and Account screens.
|
||||||
|
This view also occasionally presents information about a possible Zashi app update available on Google Play. The
|
||||||
|
app redirects users to the Google Play Zashi page by pressing the view.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- The app dialog window has now a bit more rounded corners
|
||||||
|
- A few more minor UI improvements
|
||||||
|
|
||||||
## [1.0 (650)] - 2024-05-07
|
## [1.0 (650)] - 2024-05-07
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package co.electriccoin.zcash.ui.design.component
|
package co.electriccoin.zcash.ui.design.component
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.shape.CornerSize
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
@ -61,7 +62,7 @@ fun AppAlertDialog(
|
||||||
properties: DialogProperties = DialogProperties()
|
properties: DialogProperties = DialogProperties()
|
||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
shape = RectangleShape,
|
shape = RoundedCornerShape(corner = CornerSize(ZcashTheme.dimens.regularRippleEffectCorner)),
|
||||||
onDismissRequest = onDismissRequest?.let { onDismissRequest } ?: {},
|
onDismissRequest = onDismissRequest?.let { onDismissRequest } ?: {},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
confirmButtonText?.let {
|
confirmButtonText?.let {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.unit.dp
|
||||||
data class Dimens(
|
data class Dimens(
|
||||||
// Default spacings:
|
// Default spacings:
|
||||||
val spacingNone: Dp,
|
val spacingNone: Dp,
|
||||||
|
val spacingMini: Dp,
|
||||||
val spacingXtiny: Dp,
|
val spacingXtiny: Dp,
|
||||||
val spacingMin: Dp,
|
val spacingMin: Dp,
|
||||||
val spacingTiny: Dp,
|
val spacingTiny: Dp,
|
||||||
|
@ -61,6 +62,7 @@ data class Dimens(
|
||||||
private val defaultDimens =
|
private val defaultDimens =
|
||||||
Dimens(
|
Dimens(
|
||||||
spacingNone = 0.dp,
|
spacingNone = 0.dp,
|
||||||
|
spacingMini = 1.dp,
|
||||||
spacingXtiny = 2.dp,
|
spacingXtiny = 2.dp,
|
||||||
spacingTiny = 4.dp,
|
spacingTiny = 4.dp,
|
||||||
spacingMin = 6.dp,
|
spacingMin = 6.dp,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package co.electriccoin.zcash.ui.screen.account
|
package co.electriccoin.zcash.ui.screen.account
|
||||||
|
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||||
|
@ -70,6 +71,10 @@ class AccountTestSetup(
|
||||||
onTransactionItemAction = {
|
onTransactionItemAction = {
|
||||||
onItemClickCount.incrementAndGet()
|
onItemClickCount.incrementAndGet()
|
||||||
},
|
},
|
||||||
|
hideStatusDialog = {},
|
||||||
|
showStatusDialog = null,
|
||||||
|
onStatusClick = {},
|
||||||
|
snackbarHostState = SnackbarHostState(),
|
||||||
walletRestoringState = WalletRestoringState.NONE,
|
walletRestoringState = WalletRestoringState.NONE,
|
||||||
walletSnapshot = WalletSnapshotFixture.new()
|
walletSnapshot = WalletSnapshotFixture.new()
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,6 +30,7 @@ class HistoryTestSetup(
|
||||||
ZcashTheme {
|
ZcashTheme {
|
||||||
HistoryContainer(
|
HistoryContainer(
|
||||||
transactionState = initialHistoryUiState,
|
transactionState = initialHistoryUiState,
|
||||||
|
onStatusClick = {},
|
||||||
onTransactionItemAction = {
|
onTransactionItemAction = {
|
||||||
onItemIdClickCount.incrementAndGet()
|
onItemIdClickCount.incrementAndGet()
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package co.electriccoin.zcash.ui.screen.balances
|
package co.electriccoin.zcash.ui.screen.balances
|
||||||
|
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||||
|
@ -35,7 +36,10 @@ class BalancesTestSetup(
|
||||||
onSettings = {
|
onSettings = {
|
||||||
onSettingsCount.incrementAndGet()
|
onSettingsCount.incrementAndGet()
|
||||||
},
|
},
|
||||||
isDetailedStatus = false,
|
hideStatusDialog = {},
|
||||||
|
showStatusDialog = null,
|
||||||
|
onStatusClick = {},
|
||||||
|
snackbarHostState = SnackbarHostState(),
|
||||||
isFiatConversionEnabled = isShowFiatConversion,
|
isFiatConversionEnabled = isShowFiatConversion,
|
||||||
isUpdateAvailable = false,
|
isUpdateAvailable = false,
|
||||||
isShowingErrorDialog = false,
|
isShowingErrorDialog = false,
|
||||||
|
|
|
@ -31,8 +31,7 @@ class WalletDisplayValuesTest {
|
||||||
WalletDisplayValues.getNextValues(
|
WalletDisplayValues.getNextValues(
|
||||||
context = getAppContext(),
|
context = getAppContext(),
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
isUpdateAvailable = false,
|
isUpdateAvailable = false
|
||||||
isDetailedStatus = false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assertNotNull(values)
|
assertNotNull(values)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package co.electriccoin.zcash.ui.screen.update.util
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import co.electriccoin.zcash.ui.test.getAppContext
|
import co.electriccoin.zcash.ui.test.getAppContext
|
||||||
|
import co.electriccoin.zcash.ui.util.PlayStoreUtil
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -80,6 +80,7 @@ sealed class BalanceState(open val totalBalance: Zatoshi) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("LongMethod")
|
||||||
fun BalanceWidget(
|
fun BalanceWidget(
|
||||||
balanceState: BalanceState,
|
balanceState: BalanceState,
|
||||||
isReferenceToBalances: Boolean,
|
isReferenceToBalances: Boolean,
|
||||||
|
@ -104,11 +105,22 @@ fun BalanceWidget(
|
||||||
text = stringResource(id = co.electriccoin.zcash.ui.R.string.balance_widget_available),
|
text = stringResource(id = co.electriccoin.zcash.ui.R.string.balance_widget_available),
|
||||||
onClick = onReferenceClick,
|
onClick = onReferenceClick,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
modifier = Modifier.padding(all = ZcashTheme.dimens.spacingTiny)
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(
|
||||||
|
vertical = ZcashTheme.dimens.spacingSmall,
|
||||||
|
horizontal = ZcashTheme.dimens.spacingMini,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Body(
|
Body(
|
||||||
text = stringResource(id = co.electriccoin.zcash.ui.R.string.balance_widget_available),
|
text = stringResource(id = co.electriccoin.zcash.ui.R.string.balance_widget_available),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(
|
||||||
|
vertical = ZcashTheme.dimens.spacingSmall,
|
||||||
|
horizontal = ZcashTheme.dimens.spacingMini,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
package co.electriccoin.zcash.ui.common.compose
|
package co.electriccoin.zcash.ui.common.compose
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
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.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Text
|
||||||
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.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
@ -18,11 +24,13 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import cash.z.ecc.sdk.extension.toPercentageWithDecimal
|
import cash.z.ecc.sdk.extension.toPercentageWithDecimal
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||||
|
import co.electriccoin.zcash.ui.design.component.AppAlertDialog
|
||||||
import co.electriccoin.zcash.ui.design.component.BodySmall
|
import co.electriccoin.zcash.ui.design.component.BodySmall
|
||||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||||
import co.electriccoin.zcash.ui.design.component.SmallLinearProgressIndicator
|
import co.electriccoin.zcash.ui.design.component.SmallLinearProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||||
|
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||||
import co.electriccoin.zcash.ui.screen.balances.model.WalletDisplayValues
|
import co.electriccoin.zcash.ui.screen.balances.model.WalletDisplayValues
|
||||||
|
|
||||||
@Preview(device = Devices.PIXEL_4_XL)
|
@Preview(device = Devices.PIXEL_4_XL)
|
||||||
|
@ -34,8 +42,8 @@ private fun BalanceWidgetPreview() {
|
||||||
) {
|
) {
|
||||||
SynchronizationStatus(
|
SynchronizationStatus(
|
||||||
isUpdateAvailable = false,
|
isUpdateAvailable = false,
|
||||||
isDetailedStatus = false,
|
onStatusClick = {},
|
||||||
walletSnapshot = WalletSnapshotFixture.new()
|
walletSnapshot = WalletSnapshotFixture.new(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +52,7 @@ private fun BalanceWidgetPreview() {
|
||||||
@Composable
|
@Composable
|
||||||
fun SynchronizationStatus(
|
fun SynchronizationStatus(
|
||||||
isUpdateAvailable: Boolean,
|
isUpdateAvailable: Boolean,
|
||||||
isDetailedStatus: Boolean,
|
onStatusClick: (StatusAction) -> Unit,
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
testTag: String? = null,
|
testTag: String? = null,
|
||||||
|
@ -54,7 +62,6 @@ fun SynchronizationStatus(
|
||||||
context = LocalContext.current,
|
context = LocalContext.current,
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
isUpdateAvailable = isUpdateAvailable,
|
isUpdateAvailable = isUpdateAvailable,
|
||||||
isDetailedStatus = isDetailedStatus
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
@ -64,11 +71,14 @@ fun SynchronizationStatus(
|
||||||
if (walletDisplayValues.statusText.isNotEmpty()) {
|
if (walletDisplayValues.statusText.isNotEmpty()) {
|
||||||
BodySmall(
|
BodySmall(
|
||||||
text = walletDisplayValues.statusText,
|
text = walletDisplayValues.statusText,
|
||||||
modifier = testTag?.let { Modifier.testTag(testTag) } ?: Modifier,
|
textAlign = TextAlign.Center,
|
||||||
textAlign = TextAlign.Center
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
||||||
|
.clickable { onStatusClick(walletDisplayValues.statusAction) }
|
||||||
|
.padding(all = ZcashTheme.dimens.spacingSmall)
|
||||||
|
.then(testTag?.let { Modifier.testTag(testTag) } ?: Modifier),
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BodySmall(
|
BodySmall(
|
||||||
|
@ -86,8 +96,27 @@ fun SynchronizationStatus(
|
||||||
progress = walletSnapshot.progress.decimal,
|
progress = walletSnapshot.progress.decimal,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.padding(
|
Modifier.padding(
|
||||||
horizontal = ZcashTheme.dimens.spacingUpLarge
|
horizontal = ZcashTheme.dimens.spacingDefault
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StatusDialog(
|
||||||
|
statusAction: StatusAction.Detailed,
|
||||||
|
onDone: () -> Unit
|
||||||
|
) {
|
||||||
|
AppAlertDialog(
|
||||||
|
title = stringResource(id = R.string.balances_status_error_dialog_title),
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
Modifier.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Text(text = statusAction.details)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButtonText = stringResource(id = R.string.balances_status_dialog_button),
|
||||||
|
onConfirmButtonClick = onDone
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -185,8 +185,6 @@ class AuthenticationViewModel(
|
||||||
// Biometric authentication is disabled until the user unlocks with their device credential
|
// Biometric authentication is disabled until the user unlocks with their device credential
|
||||||
// (i.e. PIN, pattern, or password).
|
// (i.e. PIN, pattern, or password).
|
||||||
BiometricPrompt.ERROR_LOCKOUT_PERMANENT,
|
BiometricPrompt.ERROR_LOCKOUT_PERMANENT,
|
||||||
// The user does not have any biometrics enrolled
|
|
||||||
BiometricPrompt.ERROR_NO_BIOMETRICS,
|
|
||||||
// The device does not have the required authentication hardware
|
// The device does not have the required authentication hardware
|
||||||
BiometricPrompt.ERROR_HW_NOT_PRESENT,
|
BiometricPrompt.ERROR_HW_NOT_PRESENT,
|
||||||
// The user pressed the negative button
|
// The user pressed the negative button
|
||||||
|
@ -214,9 +212,12 @@ class AuthenticationViewModel(
|
||||||
// We could consider splitting ERROR_CANCELED from ERROR_USER_CANCELED
|
// We could consider splitting ERROR_CANCELED from ERROR_USER_CANCELED
|
||||||
authenticationResult.value = AuthenticationResult.Canceled
|
authenticationResult.value = AuthenticationResult.Canceled
|
||||||
}
|
}
|
||||||
|
// The user does not have any biometrics enrolled
|
||||||
|
BiometricPrompt.ERROR_NO_BIOMETRICS,
|
||||||
// The device does not have pin, pattern, or password set up
|
// The device does not have pin, pattern, or password set up
|
||||||
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
|
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
|
||||||
// Allow unauthenticated access if no authentication method is available on the device
|
// Allow unauthenticated access if no authentication method is available on the device
|
||||||
|
// These 2 errors can come for a different Android SDK versions, but they mean the same
|
||||||
authenticationResult.value = AuthenticationResult.Success
|
authenticationResult.value = AuthenticationResult.Success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,7 +347,7 @@ class AuthenticationViewModel(
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Twig.error { "Unexpected biometric framework status" }
|
Twig.error { "Unexpected biometric framework status" }
|
||||||
BiometricSupportResult.StatusExpected
|
BiometricSupportResult.StatusUnexpected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,5 +412,5 @@ private sealed class BiometricSupportResult {
|
||||||
|
|
||||||
data object StatusUnknown : BiometricSupportResult()
|
data object StatusUnknown : BiometricSupportResult()
|
||||||
|
|
||||||
data object StatusExpected : BiometricSupportResult()
|
data object StatusUnexpected : BiometricSupportResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,10 @@ package co.electriccoin.zcash.ui.screen.account
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
|
@ -21,6 +24,8 @@ import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||||
import co.electriccoin.zcash.ui.screen.account.view.Account
|
import co.electriccoin.zcash.ui.screen.account.view.Account
|
||||||
import co.electriccoin.zcash.ui.screen.account.view.TrxItemAction
|
import co.electriccoin.zcash.ui.screen.account.view.TrxItemAction
|
||||||
import co.electriccoin.zcash.ui.screen.account.viewmodel.TransactionHistoryViewModel
|
import co.electriccoin.zcash.ui.screen.account.viewmodel.TransactionHistoryViewModel
|
||||||
|
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||||
|
import co.electriccoin.zcash.ui.util.PlayStoreUtil
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.annotations.VisibleForTesting
|
import org.jetbrains.annotations.VisibleForTesting
|
||||||
|
@ -31,8 +36,6 @@ internal fun WrapAccount(
|
||||||
goBalances: () -> Unit,
|
goBalances: () -> Unit,
|
||||||
goSettings: () -> Unit,
|
goSettings: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||||
|
|
||||||
val transactionHistoryViewModel by activity.viewModels<TransactionHistoryViewModel>()
|
val transactionHistoryViewModel by activity.viewModels<TransactionHistoryViewModel>()
|
||||||
|
@ -56,7 +59,6 @@ internal fun WrapAccount(
|
||||||
context = activity.applicationContext,
|
context = activity.applicationContext,
|
||||||
goBalances = goBalances,
|
goBalances = goBalances,
|
||||||
goSettings = goSettings,
|
goSettings = goSettings,
|
||||||
scope = scope,
|
|
||||||
synchronizer = synchronizer,
|
synchronizer = synchronizer,
|
||||||
transactionHistoryViewModel = transactionHistoryViewModel,
|
transactionHistoryViewModel = transactionHistoryViewModel,
|
||||||
transactionsUiState = transactionsUiState,
|
transactionsUiState = transactionsUiState,
|
||||||
|
@ -70,11 +72,10 @@ internal fun WrapAccount(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList", "LongMethod")
|
||||||
internal fun WrapAccount(
|
internal fun WrapAccount(
|
||||||
balanceState: BalanceState,
|
balanceState: BalanceState,
|
||||||
context: Context,
|
context: Context,
|
||||||
scope: CoroutineScope,
|
|
||||||
goBalances: () -> Unit,
|
goBalances: () -> Unit,
|
||||||
goSettings: () -> Unit,
|
goSettings: () -> Unit,
|
||||||
transactionsUiState: TransactionUiState,
|
transactionsUiState: TransactionUiState,
|
||||||
|
@ -83,6 +84,14 @@ internal fun WrapAccount(
|
||||||
walletRestoringState: WalletRestoringState,
|
walletRestoringState: WalletRestoringState,
|
||||||
walletSnapshot: WalletSnapshot?
|
walletSnapshot: WalletSnapshot?
|
||||||
) {
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
// We could also improve this by `rememberSaveable` to preserve the dialog after a configuration change. But the
|
||||||
|
// dialog dismissing in such cases is not critical, and it would require creating StatusAction custom Saver
|
||||||
|
val showStatusDialog = remember { mutableStateOf<StatusAction.Detailed?>(null) }
|
||||||
|
|
||||||
if (null == synchronizer || null == walletSnapshot) {
|
if (null == synchronizer || null == walletSnapshot) {
|
||||||
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer
|
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer
|
||||||
// TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
|
// TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
@ -92,6 +101,23 @@ internal fun WrapAccount(
|
||||||
Account(
|
Account(
|
||||||
balanceState = balanceState,
|
balanceState = balanceState,
|
||||||
transactionsUiState = transactionsUiState,
|
transactionsUiState = transactionsUiState,
|
||||||
|
showStatusDialog = showStatusDialog.value,
|
||||||
|
hideStatusDialog = { showStatusDialog.value = null },
|
||||||
|
onStatusClick = { status ->
|
||||||
|
when (status) {
|
||||||
|
is StatusAction.Detailed -> showStatusDialog.value = status
|
||||||
|
StatusAction.AppUpdate -> {
|
||||||
|
openPlayStoreAppSite(
|
||||||
|
context = context,
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
|
scope = scope
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// No action required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
onTransactionItemAction = { action ->
|
onTransactionItemAction = { action ->
|
||||||
when (action) {
|
when (action) {
|
||||||
is TrxItemAction.TransactionIdClick -> {
|
is TrxItemAction.TransactionIdClick -> {
|
||||||
|
@ -132,8 +158,26 @@ internal fun WrapAccount(
|
||||||
},
|
},
|
||||||
goBalances = goBalances,
|
goBalances = goBalances,
|
||||||
goSettings = goSettings,
|
goSettings = goSettings,
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
walletRestoringState = walletRestoringState,
|
walletRestoringState = walletRestoringState,
|
||||||
walletSnapshot = walletSnapshot
|
walletSnapshot = walletSnapshot
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openPlayStoreAppSite(
|
||||||
|
context: Context,
|
||||||
|
snackbarHostState: SnackbarHostState,
|
||||||
|
scope: CoroutineScope
|
||||||
|
) {
|
||||||
|
val storeIntent = PlayStoreUtil.newActivityIntent(context)
|
||||||
|
runCatching {
|
||||||
|
context.startActivity(storeIntent)
|
||||||
|
}.onFailure {
|
||||||
|
scope.launch {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = context.getString(R.string.unable_to_open_play_store)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
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
|
||||||
|
@ -19,6 +21,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.compose.BalanceState
|
import co.electriccoin.zcash.ui.common.compose.BalanceState
|
||||||
import co.electriccoin.zcash.ui.common.compose.BalanceWidget
|
import co.electriccoin.zcash.ui.common.compose.BalanceWidget
|
||||||
|
import co.electriccoin.zcash.ui.common.compose.StatusDialog
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||||
import co.electriccoin.zcash.ui.common.test.CommonTag
|
import co.electriccoin.zcash.ui.common.test.CommonTag
|
||||||
|
@ -30,6 +33,7 @@ import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||||
import co.electriccoin.zcash.ui.screen.account.AccountTag
|
import co.electriccoin.zcash.ui.screen.account.AccountTag
|
||||||
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
|
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
|
||||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||||
|
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||||
|
|
||||||
@Preview("Account No History")
|
@Preview("Account No History")
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -37,12 +41,16 @@ private fun HistoryLoadingComposablePreview() {
|
||||||
ZcashTheme(forceDarkMode = false) {
|
ZcashTheme(forceDarkMode = false) {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
Account(
|
Account(
|
||||||
|
balanceState = BalanceStateFixture.new(),
|
||||||
goBalances = {},
|
goBalances = {},
|
||||||
goSettings = {},
|
goSettings = {},
|
||||||
|
hideStatusDialog = {},
|
||||||
|
showStatusDialog = null,
|
||||||
|
onStatusClick = {},
|
||||||
onTransactionItemAction = {},
|
onTransactionItemAction = {},
|
||||||
|
snackbarHostState = SnackbarHostState(),
|
||||||
transactionsUiState = TransactionUiState.Loading,
|
transactionsUiState = TransactionUiState.Loading,
|
||||||
walletRestoringState = WalletRestoringState.SYNCING,
|
walletRestoringState = WalletRestoringState.SYNCING,
|
||||||
balanceState = BalanceStateFixture.new(),
|
|
||||||
walletSnapshot = WalletSnapshotFixture.new(),
|
walletSnapshot = WalletSnapshotFixture.new(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -56,10 +64,14 @@ private fun HistoryListComposablePreview() {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
Account(
|
Account(
|
||||||
|
balanceState = BalanceState.Available(Zatoshi(123_000_000L), Zatoshi(123_000_000L)),
|
||||||
goBalances = {},
|
goBalances = {},
|
||||||
goSettings = {},
|
goSettings = {},
|
||||||
balanceState = BalanceState.Available(Zatoshi(123_000_000L), Zatoshi(123_000_000L)),
|
hideStatusDialog = {},
|
||||||
|
showStatusDialog = null,
|
||||||
|
onStatusClick = {},
|
||||||
onTransactionItemAction = {},
|
onTransactionItemAction = {},
|
||||||
|
snackbarHostState = SnackbarHostState(),
|
||||||
transactionsUiState = TransactionUiState.Done(transactions = TransactionsFixture.new()),
|
transactionsUiState = TransactionUiState.Done(transactions = TransactionsFixture.new()),
|
||||||
walletRestoringState = WalletRestoringState.NONE,
|
walletRestoringState = WalletRestoringState.NONE,
|
||||||
walletSnapshot = WalletSnapshotFixture.new(),
|
walletSnapshot = WalletSnapshotFixture.new(),
|
||||||
|
@ -74,20 +86,30 @@ internal fun Account(
|
||||||
balanceState: BalanceState,
|
balanceState: BalanceState,
|
||||||
goBalances: () -> Unit,
|
goBalances: () -> Unit,
|
||||||
goSettings: () -> Unit,
|
goSettings: () -> Unit,
|
||||||
|
hideStatusDialog: () -> Unit,
|
||||||
|
showStatusDialog: StatusAction.Detailed?,
|
||||||
|
onStatusClick: (StatusAction) -> Unit,
|
||||||
onTransactionItemAction: (TrxItemAction) -> Unit,
|
onTransactionItemAction: (TrxItemAction) -> Unit,
|
||||||
|
snackbarHostState: SnackbarHostState,
|
||||||
transactionsUiState: TransactionUiState,
|
transactionsUiState: TransactionUiState,
|
||||||
walletRestoringState: WalletRestoringState,
|
walletRestoringState: WalletRestoringState,
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
) {
|
) {
|
||||||
Scaffold(topBar = {
|
Scaffold(
|
||||||
AccountTopAppBar(
|
topBar = {
|
||||||
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
|
AccountTopAppBar(
|
||||||
onSettings = goSettings
|
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
|
||||||
)
|
onSettings = goSettings
|
||||||
}) { paddingValues ->
|
)
|
||||||
|
},
|
||||||
|
snackbarHost = {
|
||||||
|
SnackbarHost(snackbarHostState)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
AccountMainContent(
|
AccountMainContent(
|
||||||
balanceState = balanceState,
|
balanceState = balanceState,
|
||||||
goBalances = goBalances,
|
goBalances = goBalances,
|
||||||
|
onStatusClick = onStatusClick,
|
||||||
onTransactionItemAction = onTransactionItemAction,
|
onTransactionItemAction = onTransactionItemAction,
|
||||||
transactionState = transactionsUiState,
|
transactionState = transactionsUiState,
|
||||||
walletRestoringState = walletRestoringState,
|
walletRestoringState = walletRestoringState,
|
||||||
|
@ -99,6 +121,14 @@ internal fun Account(
|
||||||
// underlying transaction history composable
|
// underlying transaction history composable
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Show synchronization status popup
|
||||||
|
if (showStatusDialog != null) {
|
||||||
|
StatusDialog(
|
||||||
|
statusAction = showStatusDialog,
|
||||||
|
onDone = hideStatusDialog
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +165,7 @@ private fun AccountMainContent(
|
||||||
balanceState: BalanceState,
|
balanceState: BalanceState,
|
||||||
goBalances: () -> Unit,
|
goBalances: () -> Unit,
|
||||||
onTransactionItemAction: (TrxItemAction) -> Unit,
|
onTransactionItemAction: (TrxItemAction) -> Unit,
|
||||||
|
onStatusClick: (StatusAction) -> Unit,
|
||||||
transactionState: TransactionUiState,
|
transactionState: TransactionUiState,
|
||||||
walletRestoringState: WalletRestoringState,
|
walletRestoringState: WalletRestoringState,
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
|
@ -157,6 +188,7 @@ private fun AccountMainContent(
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||||
|
|
||||||
HistoryContainer(
|
HistoryContainer(
|
||||||
|
onStatusClick = onStatusClick,
|
||||||
onTransactionItemAction = onTransactionItemAction,
|
onTransactionItemAction = onTransactionItemAction,
|
||||||
transactionState = transactionState,
|
transactionState = transactionState,
|
||||||
walletRestoringState = walletRestoringState,
|
walletRestoringState = walletRestoringState,
|
||||||
|
|
|
@ -62,6 +62,7 @@ import co.electriccoin.zcash.ui.screen.account.model.TransactionUi
|
||||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||||
import co.electriccoin.zcash.ui.screen.account.model.TrxItemState
|
import co.electriccoin.zcash.ui.screen.account.model.TrxItemState
|
||||||
import co.electriccoin.zcash.ui.screen.balances.BalancesTag
|
import co.electriccoin.zcash.ui.screen.balances.BalancesTag
|
||||||
|
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||||
import co.electriccoin.zcash.ui.screen.send.view.DEFAULT_LESS_THAN_FEE
|
import co.electriccoin.zcash.ui.screen.send.view.DEFAULT_LESS_THAN_FEE
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
@ -76,6 +77,7 @@ private fun ComposablePreview() {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
HistoryContainer(
|
HistoryContainer(
|
||||||
onTransactionItemAction = {},
|
onTransactionItemAction = {},
|
||||||
|
onStatusClick = {},
|
||||||
transactionState = TransactionUiState.Loading,
|
transactionState = TransactionUiState.Loading,
|
||||||
walletRestoringState = WalletRestoringState.SYNCING,
|
walletRestoringState = WalletRestoringState.SYNCING,
|
||||||
walletSnapshot = WalletSnapshotFixture.new()
|
walletSnapshot = WalletSnapshotFixture.new()
|
||||||
|
@ -92,6 +94,7 @@ private fun ComposableHistoryListPreview() {
|
||||||
HistoryContainer(
|
HistoryContainer(
|
||||||
transactionState = TransactionUiState.Done(transactions = TransactionsFixture.new()),
|
transactionState = TransactionUiState.Done(transactions = TransactionsFixture.new()),
|
||||||
onTransactionItemAction = {},
|
onTransactionItemAction = {},
|
||||||
|
onStatusClick = {},
|
||||||
walletRestoringState = WalletRestoringState.RESTORING,
|
walletRestoringState = WalletRestoringState.RESTORING,
|
||||||
walletSnapshot = WalletSnapshotFixture.new()
|
walletSnapshot = WalletSnapshotFixture.new()
|
||||||
)
|
)
|
||||||
|
@ -107,7 +110,9 @@ private val dateFormat: DateFormat by lazy {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("LongParameterList")
|
||||||
internal fun HistoryContainer(
|
internal fun HistoryContainer(
|
||||||
|
onStatusClick: (StatusAction) -> Unit,
|
||||||
onTransactionItemAction: (TrxItemAction) -> Unit,
|
onTransactionItemAction: (TrxItemAction) -> Unit,
|
||||||
transactionState: TransactionUiState,
|
transactionState: TransactionUiState,
|
||||||
walletRestoringState: WalletRestoringState,
|
walletRestoringState: WalletRestoringState,
|
||||||
|
@ -127,15 +132,19 @@ internal fun HistoryContainer(
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.background(color = ZcashTheme.colors.historySyncingColor)
|
modifier = Modifier.background(color = ZcashTheme.colors.historySyncingColor)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||||
|
|
||||||
// Do not show the app update information and the detailed sync status in the restoring status
|
// Do not calculate and use the app update information here, as the sync bar won't be displayed after
|
||||||
// on Account screen
|
// the wallet is fully restored
|
||||||
SynchronizationStatus(
|
SynchronizationStatus(
|
||||||
isDetailedStatus = false,
|
|
||||||
isUpdateAvailable = false,
|
isUpdateAvailable = false,
|
||||||
|
onStatusClick = onStatusClick,
|
||||||
testTag = BalancesTag.STATUS,
|
testTag = BalancesTag.STATUS,
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = ZcashTheme.dimens.spacingDefault)
|
||||||
|
.animateContentSize()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
|
|
||||||
package co.electriccoin.zcash.ui.screen.balances
|
package co.electriccoin.zcash.ui.screen.balances
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
@ -26,11 +29,14 @@ import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
||||||
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
||||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.balances.model.ShieldState
|
import co.electriccoin.zcash.ui.screen.balances.model.ShieldState
|
||||||
|
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||||
import co.electriccoin.zcash.ui.screen.balances.view.Balances
|
import co.electriccoin.zcash.ui.screen.balances.view.Balances
|
||||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.model.SubmitResult
|
import co.electriccoin.zcash.ui.screen.sendconfirmation.model.SubmitResult
|
||||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.viewmodel.CreateTransactionsViewModel
|
import co.electriccoin.zcash.ui.screen.sendconfirmation.viewmodel.CreateTransactionsViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
|
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
|
||||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||||
|
import co.electriccoin.zcash.ui.util.PlayStoreUtil
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.annotations.VisibleForTesting
|
import org.jetbrains.annotations.VisibleForTesting
|
||||||
|
@ -38,7 +44,6 @@ import org.jetbrains.annotations.VisibleForTesting
|
||||||
@Composable
|
@Composable
|
||||||
internal fun WrapBalances(
|
internal fun WrapBalances(
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
isDetailedSyncStatus: Boolean,
|
|
||||||
goSettings: () -> Unit,
|
goSettings: () -> Unit,
|
||||||
goMultiTrxSubmissionFailure: () -> Unit,
|
goMultiTrxSubmissionFailure: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -69,7 +74,6 @@ internal fun WrapBalances(
|
||||||
checkUpdateViewModel = checkUpdateViewModel,
|
checkUpdateViewModel = checkUpdateViewModel,
|
||||||
goSettings = goSettings,
|
goSettings = goSettings,
|
||||||
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
|
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
|
||||||
isDetailedSyncStatus = isDetailedSyncStatus,
|
|
||||||
spendingKey = spendingKey,
|
spendingKey = spendingKey,
|
||||||
synchronizer = synchronizer,
|
synchronizer = synchronizer,
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
|
@ -81,14 +85,14 @@ const val DEFAULT_SHIELDING_THRESHOLD = 100000L
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@Suppress("LongParameterList", "LongMethod")
|
// This function should be refactored into smaller chunks
|
||||||
|
@Suppress("LongParameterList", "LongMethod", "CyclomaticComplexMethod")
|
||||||
internal fun WrapBalances(
|
internal fun WrapBalances(
|
||||||
balanceState: BalanceState,
|
balanceState: BalanceState,
|
||||||
checkUpdateViewModel: CheckUpdateViewModel,
|
checkUpdateViewModel: CheckUpdateViewModel,
|
||||||
createTransactionsViewModel: CreateTransactionsViewModel,
|
createTransactionsViewModel: CreateTransactionsViewModel,
|
||||||
goSettings: () -> Unit,
|
goSettings: () -> Unit,
|
||||||
goMultiTrxSubmissionFailure: () -> Unit,
|
goMultiTrxSubmissionFailure: () -> Unit,
|
||||||
isDetailedSyncStatus: Boolean,
|
|
||||||
spendingKey: UnifiedSpendingKey?,
|
spendingKey: UnifiedSpendingKey?,
|
||||||
synchronizer: Synchronizer?,
|
synchronizer: Synchronizer?,
|
||||||
walletSnapshot: WalletSnapshot?,
|
walletSnapshot: WalletSnapshot?,
|
||||||
|
@ -98,6 +102,8 @@ internal fun WrapBalances(
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
// To show information about the app update, if available
|
// To show information about the app update, if available
|
||||||
val isUpdateAvailable =
|
val isUpdateAvailable =
|
||||||
checkUpdateViewModel.updateInfo.collectAsStateWithLifecycle().value.let {
|
checkUpdateViewModel.updateInfo.collectAsStateWithLifecycle().value.let {
|
||||||
|
@ -130,6 +136,10 @@ internal fun WrapBalances(
|
||||||
setShowErrorDialog(true)
|
setShowErrorDialog(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We could also improve this by `rememberSaveable` to preserve the dialog after a configuration change. But the
|
||||||
|
// dialog dismissing in such cases is not critical, and it would require creating StatusAction custom Saver
|
||||||
|
val showStatusDialog = remember { mutableStateOf<StatusAction.Detailed?>(null) }
|
||||||
|
|
||||||
if (null == synchronizer || null == walletSnapshot || null == spendingKey) {
|
if (null == synchronizer || null == walletSnapshot || null == spendingKey) {
|
||||||
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer
|
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer
|
||||||
// TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
|
// TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
@ -140,10 +150,12 @@ internal fun WrapBalances(
|
||||||
balanceState = balanceState,
|
balanceState = balanceState,
|
||||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||||
isUpdateAvailable = isUpdateAvailable,
|
isUpdateAvailable = isUpdateAvailable,
|
||||||
isShowingErrorDialog = isShowingErrorDialog,
|
|
||||||
isDetailedStatus = isDetailedSyncStatus,
|
|
||||||
onSettings = goSettings,
|
onSettings = goSettings,
|
||||||
|
isShowingErrorDialog = isShowingErrorDialog,
|
||||||
setShowErrorDialog = setShowErrorDialog,
|
setShowErrorDialog = setShowErrorDialog,
|
||||||
|
showStatusDialog = showStatusDialog.value,
|
||||||
|
hideStatusDialog = { showStatusDialog.value = null },
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
onShielding = {
|
onShielding = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
setShieldState(ShieldState.Running)
|
setShieldState(ShieldState.Running)
|
||||||
|
@ -201,6 +213,21 @@ internal fun WrapBalances(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onStatusClick = { status ->
|
||||||
|
when (status) {
|
||||||
|
is StatusAction.Detailed -> showStatusDialog.value = status
|
||||||
|
StatusAction.AppUpdate -> {
|
||||||
|
openPlayStoreAppSite(
|
||||||
|
context = context,
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
|
scope = scope
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// No action required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
shieldState = shieldState,
|
shieldState = shieldState,
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
walletRestoringState = walletRestoringState,
|
walletRestoringState = walletRestoringState,
|
||||||
|
@ -208,7 +235,7 @@ internal fun WrapBalances(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTransparentBalanceState(
|
private fun updateTransparentBalanceState(
|
||||||
currentShieldState: ShieldState,
|
currentShieldState: ShieldState,
|
||||||
walletSnapshot: WalletSnapshot?
|
walletSnapshot: WalletSnapshot?
|
||||||
): ShieldState {
|
): ShieldState {
|
||||||
|
@ -219,3 +246,20 @@ fun updateTransparentBalanceState(
|
||||||
else -> currentShieldState
|
else -> currentShieldState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openPlayStoreAppSite(
|
||||||
|
context: Context,
|
||||||
|
snackbarHostState: SnackbarHostState,
|
||||||
|
scope: CoroutineScope
|
||||||
|
) {
|
||||||
|
val storeIntent = PlayStoreUtil.newActivityIntent(context)
|
||||||
|
runCatching {
|
||||||
|
context.startActivity(storeIntent)
|
||||||
|
}.onFailure {
|
||||||
|
scope.launch {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = context.getString(R.string.unable_to_open_play_store)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,20 +18,22 @@ data class WalletDisplayValues(
|
||||||
val progress: PercentDecimal,
|
val progress: PercentDecimal,
|
||||||
val zecAmountText: String,
|
val zecAmountText: String,
|
||||||
val statusText: String,
|
val statusText: String,
|
||||||
|
val statusAction: StatusAction = StatusAction.None,
|
||||||
val fiatCurrencyAmountState: FiatCurrencyConversionRateState,
|
val fiatCurrencyAmountState: FiatCurrencyConversionRateState,
|
||||||
val fiatCurrencyAmountText: String
|
val fiatCurrencyAmountText: String
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
@Suppress("MagicNumber", "LongMethod")
|
@Suppress("LongMethod")
|
||||||
internal fun getNextValues(
|
internal fun getNextValues(
|
||||||
context: Context,
|
context: Context,
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
isUpdateAvailable: Boolean = false,
|
isUpdateAvailable: Boolean = false,
|
||||||
isDetailedStatus: Boolean = false,
|
|
||||||
): WalletDisplayValues {
|
): WalletDisplayValues {
|
||||||
var progress = PercentDecimal.ZERO_PERCENT
|
var progress = PercentDecimal.ZERO_PERCENT
|
||||||
val zecAmountText = walletSnapshot.totalBalance().toZecString()
|
val zecAmountText = walletSnapshot.totalBalance().toZecString()
|
||||||
var statusText = ""
|
var statusText = ""
|
||||||
|
var statusAction: StatusAction = StatusAction.None
|
||||||
|
|
||||||
// TODO [#578]: Provide Zatoshi -> USD fiat currency formatting
|
// TODO [#578]: Provide Zatoshi -> USD fiat currency formatting
|
||||||
// TODO [#578]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/578
|
// TODO [#578]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/578
|
||||||
// We'll ideally provide a "fresh" currencyConversion object here
|
// We'll ideally provide a "fresh" currencyConversion object here
|
||||||
|
@ -55,63 +57,63 @@ data class WalletDisplayValues(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
statusText = context.getString(R.string.balances_status_syncing)
|
statusText = context.getString(R.string.balances_status_syncing)
|
||||||
|
statusAction = StatusAction.Syncing
|
||||||
}
|
}
|
||||||
Synchronizer.Status.SYNCED -> {
|
Synchronizer.Status.SYNCED -> {
|
||||||
statusText =
|
if (isUpdateAvailable) {
|
||||||
if (isUpdateAvailable) {
|
statusText =
|
||||||
context.getString(
|
context.getString(
|
||||||
R.string.balances_status_update,
|
R.string.balances_status_update,
|
||||||
context.getString(R.string.app_name)
|
context.getString(R.string.app_name)
|
||||||
)
|
)
|
||||||
} else {
|
statusAction = StatusAction.AppUpdate
|
||||||
context.getString(R.string.balances_status_synced)
|
} else {
|
||||||
}
|
statusText = context.getString(R.string.balances_status_synced)
|
||||||
|
statusAction = StatusAction.Synced
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Synchronizer.Status.DISCONNECTED -> {
|
Synchronizer.Status.DISCONNECTED -> {
|
||||||
if (isDetailedStatus) {
|
|
||||||
statusText =
|
|
||||||
context.getString(
|
|
||||||
R.string.balances_status_error_detailed,
|
|
||||||
context.getString(R.string.balances_status_error_detailed_connection)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
statusText =
|
|
||||||
context.getString(
|
|
||||||
R.string.balances_status_error_simple,
|
|
||||||
context.getString(R.string.app_name)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Synchronizer.Status.STOPPED -> {
|
|
||||||
if (isDetailedStatus) {
|
|
||||||
statusText = context.getString(R.string.balances_status_detailed_stopped)
|
|
||||||
} else {
|
|
||||||
statusText = context.getString(R.string.balances_status_syncing)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// More detailed error message
|
|
||||||
walletSnapshot.synchronizerError?.let {
|
|
||||||
if (isDetailedStatus) {
|
|
||||||
statusText =
|
|
||||||
context.getString(
|
|
||||||
R.string.balances_status_error_detailed,
|
|
||||||
walletSnapshot.synchronizerError.getCauseMessage()
|
|
||||||
?: context.getString(R.string.balances_status_error_detailed_unknown)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
statusText =
|
statusText =
|
||||||
context.getString(
|
context.getString(
|
||||||
R.string.balances_status_error_simple,
|
R.string.balances_status_error_simple,
|
||||||
context.getString(R.string.app_name)
|
context.getString(R.string.app_name)
|
||||||
)
|
)
|
||||||
|
statusAction =
|
||||||
|
StatusAction.Disconnected(
|
||||||
|
details = context.getString(R.string.balances_status_error_dialog_connection)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Synchronizer.Status.STOPPED -> {
|
||||||
|
statusText = context.getString(R.string.balances_status_syncing)
|
||||||
|
statusAction =
|
||||||
|
StatusAction.Stopped(
|
||||||
|
details = context.getString(R.string.balances_status_dialog_stopped)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// More detailed error message
|
||||||
|
walletSnapshot.synchronizerError?.let {
|
||||||
|
statusText =
|
||||||
|
context.getString(
|
||||||
|
R.string.balances_status_error_simple,
|
||||||
|
context.getString(R.string.app_name)
|
||||||
|
)
|
||||||
|
statusAction =
|
||||||
|
StatusAction.Error(
|
||||||
|
details =
|
||||||
|
context.getString(
|
||||||
|
R.string.balances_status_error_dialog_cause,
|
||||||
|
walletSnapshot.synchronizerError.getCauseMessage()
|
||||||
|
?: context.getString(R.string.balances_status_error_dialog_unknown)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return WalletDisplayValues(
|
return WalletDisplayValues(
|
||||||
progress = progress,
|
progress = progress,
|
||||||
zecAmountText = zecAmountText,
|
zecAmountText = zecAmountText,
|
||||||
|
statusAction = statusAction,
|
||||||
statusText = statusText,
|
statusText = statusText,
|
||||||
fiatCurrencyAmountState = fiatCurrencyAmountState,
|
fiatCurrencyAmountState = fiatCurrencyAmountState,
|
||||||
fiatCurrencyAmountText = fiatCurrencyAmountText
|
fiatCurrencyAmountText = fiatCurrencyAmountText
|
||||||
|
@ -120,6 +122,24 @@ data class WalletDisplayValues(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class StatusAction {
|
||||||
|
data object None : StatusAction()
|
||||||
|
|
||||||
|
data object Syncing : StatusAction()
|
||||||
|
|
||||||
|
data object Synced : StatusAction()
|
||||||
|
|
||||||
|
data object AppUpdate : StatusAction()
|
||||||
|
|
||||||
|
sealed class Detailed(open val details: String) : StatusAction()
|
||||||
|
|
||||||
|
data class Disconnected(override val details: String) : Detailed(details)
|
||||||
|
|
||||||
|
data class Stopped(override val details: String) : Detailed(details)
|
||||||
|
|
||||||
|
data class Error(override val details: String) : Detailed(details)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getFiatCurrencyRateValue(
|
private fun getFiatCurrencyRateValue(
|
||||||
context: Context,
|
context: Context,
|
||||||
fiatCurrencyAmountState: FiatCurrencyConversionRateState
|
fiatCurrencyAmountState: FiatCurrencyConversionRateState
|
||||||
|
|
|
@ -26,6 +26,8 @@ import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -55,6 +57,7 @@ import cash.z.ecc.sdk.type.ZcashCurrency
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.compose.BalanceState
|
import co.electriccoin.zcash.ui.common.compose.BalanceState
|
||||||
import co.electriccoin.zcash.ui.common.compose.BalanceWidget
|
import co.electriccoin.zcash.ui.common.compose.BalanceWidget
|
||||||
|
import co.electriccoin.zcash.ui.common.compose.StatusDialog
|
||||||
import co.electriccoin.zcash.ui.common.compose.SynchronizationStatus
|
import co.electriccoin.zcash.ui.common.compose.SynchronizationStatus
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||||
|
@ -81,6 +84,7 @@ import co.electriccoin.zcash.ui.fixture.BalanceStateFixture
|
||||||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||||
import co.electriccoin.zcash.ui.screen.balances.BalancesTag
|
import co.electriccoin.zcash.ui.screen.balances.BalancesTag
|
||||||
import co.electriccoin.zcash.ui.screen.balances.model.ShieldState
|
import co.electriccoin.zcash.ui.screen.balances.model.ShieldState
|
||||||
|
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||||
import co.electriccoin.zcash.ui.screen.balances.model.WalletDisplayValues
|
import co.electriccoin.zcash.ui.screen.balances.model.WalletDisplayValues
|
||||||
|
|
||||||
@Preview("Balances")
|
@Preview("Balances")
|
||||||
|
@ -89,17 +93,20 @@ private fun ComposableBalancesPreview() {
|
||||||
ZcashTheme(forceDarkMode = false) {
|
ZcashTheme(forceDarkMode = false) {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
Balances(
|
Balances(
|
||||||
onSettings = {},
|
balanceState = BalanceStateFixture.new(),
|
||||||
isDetailedStatus = false,
|
|
||||||
isFiatConversionEnabled = false,
|
isFiatConversionEnabled = false,
|
||||||
isUpdateAvailable = false,
|
isUpdateAvailable = false,
|
||||||
isShowingErrorDialog = false,
|
isShowingErrorDialog = false,
|
||||||
|
hideStatusDialog = {},
|
||||||
|
showStatusDialog = null,
|
||||||
setShowErrorDialog = {},
|
setShowErrorDialog = {},
|
||||||
|
onSettings = {},
|
||||||
onShielding = {},
|
onShielding = {},
|
||||||
|
onStatusClick = {},
|
||||||
shieldState = ShieldState.Available,
|
shieldState = ShieldState.Available,
|
||||||
|
snackbarHostState = SnackbarHostState(),
|
||||||
walletSnapshot = WalletSnapshotFixture.new(),
|
walletSnapshot = WalletSnapshotFixture.new(),
|
||||||
walletRestoringState = WalletRestoringState.NONE,
|
walletRestoringState = WalletRestoringState.NONE,
|
||||||
balanceState = BalanceStateFixture.new(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,17 +118,20 @@ private fun ComposableBalancesShieldFailurePreview() {
|
||||||
ZcashTheme(forceDarkMode = false) {
|
ZcashTheme(forceDarkMode = false) {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
Balances(
|
Balances(
|
||||||
onSettings = {},
|
balanceState = BalanceStateFixture.new(),
|
||||||
isDetailedStatus = false,
|
|
||||||
isFiatConversionEnabled = false,
|
isFiatConversionEnabled = false,
|
||||||
isUpdateAvailable = false,
|
isUpdateAvailable = false,
|
||||||
isShowingErrorDialog = true,
|
isShowingErrorDialog = true,
|
||||||
|
hideStatusDialog = {},
|
||||||
|
showStatusDialog = null,
|
||||||
setShowErrorDialog = {},
|
setShowErrorDialog = {},
|
||||||
|
onSettings = {},
|
||||||
onShielding = {},
|
onShielding = {},
|
||||||
|
onStatusClick = {},
|
||||||
shieldState = ShieldState.Available,
|
shieldState = ShieldState.Available,
|
||||||
|
snackbarHostState = SnackbarHostState(),
|
||||||
walletSnapshot = WalletSnapshotFixture.new(),
|
walletSnapshot = WalletSnapshotFixture.new(),
|
||||||
walletRestoringState = WalletRestoringState.NONE,
|
walletRestoringState = WalletRestoringState.NONE,
|
||||||
balanceState = BalanceStateFixture.new(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,33 +153,41 @@ private fun ComposableBalancesShieldErrorDialogPreview() {
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
@Composable
|
@Composable
|
||||||
fun Balances(
|
fun Balances(
|
||||||
onSettings: () -> Unit,
|
balanceState: BalanceState,
|
||||||
isDetailedStatus: Boolean,
|
|
||||||
isFiatConversionEnabled: Boolean,
|
isFiatConversionEnabled: Boolean,
|
||||||
isUpdateAvailable: Boolean,
|
isUpdateAvailable: Boolean,
|
||||||
isShowingErrorDialog: Boolean,
|
isShowingErrorDialog: Boolean,
|
||||||
|
hideStatusDialog: () -> Unit,
|
||||||
|
showStatusDialog: StatusAction.Detailed?,
|
||||||
setShowErrorDialog: (Boolean) -> Unit,
|
setShowErrorDialog: (Boolean) -> Unit,
|
||||||
|
onSettings: () -> Unit,
|
||||||
onShielding: () -> Unit,
|
onShielding: () -> Unit,
|
||||||
|
onStatusClick: (StatusAction) -> Unit,
|
||||||
shieldState: ShieldState,
|
shieldState: ShieldState,
|
||||||
|
snackbarHostState: SnackbarHostState,
|
||||||
walletSnapshot: WalletSnapshot?,
|
walletSnapshot: WalletSnapshot?,
|
||||||
walletRestoringState: WalletRestoringState,
|
walletRestoringState: WalletRestoringState,
|
||||||
balanceState: BalanceState,
|
|
||||||
) {
|
) {
|
||||||
Scaffold(topBar = {
|
Scaffold(
|
||||||
BalancesTopAppBar(
|
topBar = {
|
||||||
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
|
BalancesTopAppBar(
|
||||||
onSettings = onSettings
|
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
|
||||||
)
|
onSettings = onSettings
|
||||||
}) { paddingValues ->
|
)
|
||||||
|
},
|
||||||
|
snackbarHost = {
|
||||||
|
SnackbarHost(snackbarHostState)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
if (null == walletSnapshot) {
|
if (null == walletSnapshot) {
|
||||||
CircularScreenProgressIndicator()
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
BalancesMainContent(
|
BalancesMainContent(
|
||||||
balanceState = balanceState,
|
balanceState = balanceState,
|
||||||
isDetailedStatus = isDetailedStatus,
|
|
||||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||||
isUpdateAvailable = isUpdateAvailable,
|
isUpdateAvailable = isUpdateAvailable,
|
||||||
onShielding = onShielding,
|
onShielding = onShielding,
|
||||||
|
onStatusClick = onStatusClick,
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
shieldState = shieldState,
|
shieldState = shieldState,
|
||||||
modifier =
|
modifier =
|
||||||
|
@ -182,6 +200,14 @@ fun Balances(
|
||||||
walletRestoringState = walletRestoringState
|
walletRestoringState = walletRestoringState
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Show synchronization status popup
|
||||||
|
if (showStatusDialog != null) {
|
||||||
|
StatusDialog(
|
||||||
|
statusAction = showStatusDialog,
|
||||||
|
onDone = hideStatusDialog
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Show shielding error popup
|
// Show shielding error popup
|
||||||
if (isShowingErrorDialog && shieldState is ShieldState.Failed) {
|
if (isShowingErrorDialog && shieldState is ShieldState.Failed) {
|
||||||
ShieldingErrorDialog(
|
ShieldingErrorDialog(
|
||||||
|
@ -257,10 +283,10 @@ private fun BalancesTopAppBar(
|
||||||
@Composable
|
@Composable
|
||||||
private fun BalancesMainContent(
|
private fun BalancesMainContent(
|
||||||
balanceState: BalanceState,
|
balanceState: BalanceState,
|
||||||
isDetailedStatus: Boolean,
|
|
||||||
isFiatConversionEnabled: Boolean,
|
isFiatConversionEnabled: Boolean,
|
||||||
isUpdateAvailable: Boolean,
|
isUpdateAvailable: Boolean,
|
||||||
onShielding: () -> Unit,
|
onShielding: () -> Unit,
|
||||||
|
onStatusClick: (StatusAction) -> Unit,
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
shieldState: ShieldState,
|
shieldState: ShieldState,
|
||||||
walletRestoringState: WalletRestoringState,
|
walletRestoringState: WalletRestoringState,
|
||||||
|
@ -282,7 +308,7 @@ private fun BalancesMainContent(
|
||||||
onReferenceClick = {}
|
onReferenceClick = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||||
|
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
color = ZcashTheme.colors.darkDividerColor,
|
color = ZcashTheme.colors.darkDividerColor,
|
||||||
|
@ -304,11 +330,9 @@ private fun BalancesMainContent(
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f, true))
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
|
||||||
|
|
||||||
if (walletRestoringState == WalletRestoringState.RESTORING) {
|
if (walletRestoringState == WalletRestoringState.RESTORING) {
|
||||||
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||||
|
|
||||||
Small(
|
Small(
|
||||||
text = stringResource(id = R.string.balances_status_restoring_text),
|
text = stringResource(id = R.string.balances_status_restoring_text),
|
||||||
textFontWeight = FontWeight.Medium,
|
textFontWeight = FontWeight.Medium,
|
||||||
|
@ -317,17 +341,20 @@ private fun BalancesMainContent(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = ZcashTheme.dimens.spacingDefault)
|
.padding(horizontal = ZcashTheme.dimens.spacingSmall)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||||
}
|
}
|
||||||
|
|
||||||
SynchronizationStatus(
|
SynchronizationStatus(
|
||||||
walletSnapshot = walletSnapshot,
|
|
||||||
isUpdateAvailable = isUpdateAvailable,
|
isUpdateAvailable = isUpdateAvailable,
|
||||||
isDetailedStatus = isDetailedStatus,
|
onStatusClick = onStatusClick,
|
||||||
testTag = BalancesTag.STATUS
|
testTag = BalancesTag.STATUS,
|
||||||
|
walletSnapshot = walletSnapshot,
|
||||||
|
modifier = Modifier.animateContentSize()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -517,7 +544,6 @@ fun BalancesOverview(
|
||||||
context = LocalContext.current,
|
context = LocalContext.current,
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
isUpdateAvailable = false,
|
isUpdateAvailable = false,
|
||||||
isDetailedStatus = false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(Modifier.testTag(BalancesTag.FIAT_CONVERSION)) {
|
Column(Modifier.testTag(BalancesTag.FIAT_CONVERSION)) {
|
||||||
|
|
|
@ -18,7 +18,6 @@ import cash.z.ecc.android.sdk.model.ZecSend
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.compose.RestoreScreenBrightness
|
import co.electriccoin.zcash.ui.common.compose.RestoreScreenBrightness
|
||||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.HomeViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.HomeViewModel
|
||||||
|
@ -56,13 +55,6 @@ internal fun MainActivity.WrapHome(
|
||||||
|
|
||||||
val isRestoringInitialWarningSeen = homeViewModel.isRestoringInitialWarningSeen.collectAsStateWithLifecycle().value
|
val isRestoringInitialWarningSeen = homeViewModel.isRestoringInitialWarningSeen.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
// Detailed sync status info is used if set in configuration or if the app is built as debuggable
|
|
||||||
// (i.e. mainly in development)
|
|
||||||
val isDetailedSyncStatus =
|
|
||||||
homeViewModel.isDetailedSyncStatus.collectAsStateWithLifecycle().value.run {
|
|
||||||
this ?: false || VersionInfo.new(this@WrapHome).isDebuggable
|
|
||||||
}
|
|
||||||
|
|
||||||
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
|
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
val walletRestoringState = walletViewModel.walletRestoringState.collectAsStateWithLifecycle().value
|
val walletRestoringState = walletViewModel.walletRestoringState.collectAsStateWithLifecycle().value
|
||||||
|
@ -98,7 +90,6 @@ internal fun MainActivity.WrapHome(
|
||||||
goSettings = goSettings,
|
goSettings = goSettings,
|
||||||
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
|
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
|
||||||
homeScreenIndex = homeScreenIndex,
|
homeScreenIndex = homeScreenIndex,
|
||||||
isDetailedSyncStatus = isDetailedSyncStatus,
|
|
||||||
isKeepScreenOnWhileSyncing = isKeepScreenOnWhileSyncing,
|
isKeepScreenOnWhileSyncing = isKeepScreenOnWhileSyncing,
|
||||||
isShowingRestoreInitDialog = isShowingRestoreInitDialog,
|
isShowingRestoreInitDialog = isShowingRestoreInitDialog,
|
||||||
onPageChange = {
|
onPageChange = {
|
||||||
|
@ -120,7 +111,6 @@ internal fun WrapHome(
|
||||||
goScan: () -> Unit,
|
goScan: () -> Unit,
|
||||||
goSendConfirmation: (ZecSend) -> Unit,
|
goSendConfirmation: (ZecSend) -> Unit,
|
||||||
homeScreenIndex: HomeScreenIndex,
|
homeScreenIndex: HomeScreenIndex,
|
||||||
isDetailedSyncStatus: Boolean,
|
|
||||||
isKeepScreenOnWhileSyncing: Boolean?,
|
isKeepScreenOnWhileSyncing: Boolean?,
|
||||||
isShowingRestoreInitDialog: Boolean,
|
isShowingRestoreInitDialog: Boolean,
|
||||||
onPageChange: (HomeScreenIndex) -> Unit,
|
onPageChange: (HomeScreenIndex) -> Unit,
|
||||||
|
@ -203,7 +193,6 @@ internal fun WrapHome(
|
||||||
screenContent = {
|
screenContent = {
|
||||||
WrapBalances(
|
WrapBalances(
|
||||||
activity = activity,
|
activity = activity,
|
||||||
isDetailedSyncStatus = isDetailedSyncStatus,
|
|
||||||
goSettings = goSettings,
|
goSettings = goSettings,
|
||||||
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure
|
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,9 +16,9 @@ import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.CheckUpdateViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.CheckUpdateViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateInfo
|
import co.electriccoin.zcash.ui.screen.update.model.UpdateInfo
|
||||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||||
import co.electriccoin.zcash.ui.screen.update.util.PlayStoreUtil
|
|
||||||
import co.electriccoin.zcash.ui.screen.update.view.Update
|
import co.electriccoin.zcash.ui.screen.update.view.Update
|
||||||
import co.electriccoin.zcash.ui.screen.update.viewmodel.UpdateViewModel
|
import co.electriccoin.zcash.ui.screen.update.viewmodel.UpdateViewModel
|
||||||
|
import co.electriccoin.zcash.ui.util.PlayStoreUtil
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ internal fun WrapUpdate(
|
||||||
},
|
},
|
||||||
onLater = onLaterAction,
|
onLater = onLaterAction,
|
||||||
onReference = {
|
onReference = {
|
||||||
openPlayStoreAppPage(
|
openPlayStoreAppSite(
|
||||||
activity.applicationContext,
|
activity.applicationContext,
|
||||||
snackbarHostState,
|
snackbarHostState,
|
||||||
scope
|
scope
|
||||||
|
@ -120,7 +120,7 @@ internal fun WrapUpdate(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openPlayStoreAppPage(
|
private fun openPlayStoreAppSite(
|
||||||
context: Context,
|
context: Context,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope
|
||||||
|
@ -131,7 +131,7 @@ fun openPlayStoreAppPage(
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState.showSnackbar(
|
snackbarHostState.showSnackbar(
|
||||||
message = context.getString(R.string.update_unable_to_open_play_store)
|
message = context.getString(R.string.unable_to_open_play_store)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,7 +234,7 @@ private fun UpdateContentContent(
|
||||||
} else {
|
} else {
|
||||||
ImageVector.vectorResource(R.drawable.ic_zashi_logo_update_available)
|
ImageVector.vectorResource(R.drawable.ic_zashi_logo_update_available)
|
||||||
},
|
},
|
||||||
contentDescription = stringResource(id = R.string.update_image_content_description)
|
contentDescription = null
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingBig))
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingBig))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package co.electriccoin.zcash.ui.screen.update.util
|
package co.electriccoin.zcash.ui.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
|
@ -22,14 +22,23 @@
|
||||||
<string name="balances_status_synced">Synced</string>
|
<string name="balances_status_synced">Synced</string>
|
||||||
<string name="balances_status_update">Please update <xliff:g id="app_name" example="Zashi">%1$s</xliff:g> using Google Play</string>
|
<string name="balances_status_update">Please update <xliff:g id="app_name" example="Zashi">%1$s</xliff:g> using Google Play</string>
|
||||||
<string name="balances_status_error_simple"><xliff:g id="app_name" example="Zashi">%1$s</xliff:g> encountered an error while syncing, attempting to resolve…</string>
|
<string name="balances_status_error_simple"><xliff:g id="app_name" example="Zashi">%1$s</xliff:g> encountered an error while syncing, attempting to resolve…</string>
|
||||||
<string name="balances_status_error_detailed" formatted="true">Error: <xliff:g id="error_type" example="Lost connection">%1$s</xliff:g></string>
|
|
||||||
<string name="balances_status_error_detailed_connection">Disconnected</string>
|
|
||||||
<string name="balances_status_error_detailed_unknown">Unknown cause</string>
|
|
||||||
<string name="balances_status_detailed_stopped">Synchronizer stopped</string>
|
|
||||||
<string name="balances_status_restoring_text">The restore process can take several hours on lower-powered devices, and even on powerful devices is likely to take more than an hour.</string>
|
<string name="balances_status_restoring_text">The restore process can take several hours on lower-powered devices, and even on powerful devices is likely to take more than an hour.</string>
|
||||||
|
|
||||||
<string name="balances_shielding_successful">Shielding has been successfully submitted</string>
|
<string name="balances_shielding_successful">Shielding has been successfully submitted</string>
|
||||||
|
|
||||||
|
<string name="balances_status_error_dialog_title">Error</string>
|
||||||
|
<string name="balances_status_error_dialog_connection">
|
||||||
|
Disconnected. Please check your internet connection.
|
||||||
|
</string>
|
||||||
|
<string name="balances_status_error_dialog_cause" formatted="true">
|
||||||
|
Error: <xliff:g id="error_cause" example="Block scanning problem">%1$s</xliff:g>
|
||||||
|
</string>
|
||||||
|
<string name="balances_status_error_dialog_unknown">
|
||||||
|
Unknown cause. Please contact our support team if the problem persists.
|
||||||
|
</string>
|
||||||
|
<string name="balances_status_dialog_stopped">Synchronization is stopped. It will resume soon.</string>
|
||||||
|
<string name="balances_status_dialog_button">OK</string>
|
||||||
|
|
||||||
<string name="balances_shielding_dialog_error_title">Failed to shield funds</string>
|
<string name="balances_shielding_dialog_error_title">Failed to shield funds</string>
|
||||||
<string name="balances_shielding_dialog_error_text">Error: The attempt to shield the transparent funds failed. Try it again, please.</string>
|
<string name="balances_shielding_dialog_error_text">Error: The attempt to shield the transparent funds failed. Try it again, please.</string>
|
||||||
<string name="balances_shielding_dialog_error_btn">OK</string>
|
<string name="balances_shielding_dialog_error_btn">OK</string>
|
||||||
|
|
|
@ -7,4 +7,5 @@
|
||||||
<!-- This is replaced by a resource overlay via app/build.gradle.kts -->
|
<!-- This is replaced by a resource overlay via app/build.gradle.kts -->
|
||||||
<string name="support_email_address" />
|
<string name="support_email_address" />
|
||||||
<string name="restoring_wallet_label">[Restoring Your Wallet…]</string>
|
<string name="restoring_wallet_label">[Restoring Your Wallet…]</string>
|
||||||
|
<string name="unable_to_open_play_store">Unable to launch Google Play store app…</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="update_header">Update available</string>
|
<string name="update_header">Update available</string>
|
||||||
<string name="update_critical_header">Update required</string>
|
<string name="update_critical_header">Update required</string>
|
||||||
<string name="update_image_content_description"></string>
|
|
||||||
<string name="update_title_available"><xliff:g id="app_name" example="Zcash">%1$s</xliff:g> here.</string>
|
<string name="update_title_available"><xliff:g id="app_name" example="Zcash">%1$s</xliff:g> here.</string>
|
||||||
<string name="update_title_required">It\'s not you, it\'s me.</string>
|
<string name="update_title_required">It\'s not you, it\'s me.</string>
|
||||||
<string name="update_description_required">
|
<string name="update_description_required">
|
||||||
|
@ -17,5 +16,4 @@
|
||||||
<string name="update_download_button">Update</string>
|
<string name="update_download_button">Update</string>
|
||||||
<string name="update_later_enabled_button">Remind me later</string>
|
<string name="update_later_enabled_button">Remind me later</string>
|
||||||
<string name="update_later_disabled_button">(required)</string>
|
<string name="update_later_disabled_button">(required)</string>
|
||||||
<string name="update_unable_to_open_play_store">Unable to launch Google Play store app.</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue