* [#1492] Restore success dialog refactor Closes #1492 * [#1492] Code cleanup Closes #1492 * Create a separate Android layer * Changelog update * Move restore success string resources * Rename logic related variables + File a follow-up issue * Resources rename * Move drawable to its package --------- Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
parent
1a0b634ab6
commit
0bc7757aa2
|
@ -14,6 +14,8 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
||||||
### Changed
|
### Changed
|
||||||
- The About screen has been redesigned to align with the new design guidelines
|
- The About screen has been redesigned to align with the new design guidelines
|
||||||
- `StyledBalance` text styles have been refactored from `Pair` into `BalanceTextStyle`
|
- `StyledBalance` text styles have been refactored from `Pair` into `BalanceTextStyle`
|
||||||
|
- The Restore Success dialog has been reworked into a separate screen, allowing users to opt out of the Keep screen
|
||||||
|
on while restoring option
|
||||||
|
|
||||||
## [1.1.3 (682)] - 2024-07-03
|
## [1.1.3 (682)] - 2024-07-03
|
||||||
|
|
||||||
|
|
|
@ -14,3 +14,5 @@ directly impact users rather than highlighting other key architectural updates.*
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- The About screen has been redesigned to align with the new design guidelines
|
- The About screen has been redesigned to align with the new design guidelines
|
||||||
|
- The Restore Success dialog has been reworked into a separate screen, allowing users to opt out of the Keep screen
|
||||||
|
on while restoring option
|
||||||
|
|
|
@ -14,6 +14,8 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
@ -30,6 +32,8 @@ class AndroidPreferenceProvider(
|
||||||
private val sharedPreferences: SharedPreferences,
|
private val sharedPreferences: SharedPreferences,
|
||||||
private val dispatcher: CoroutineDispatcher
|
private val dispatcher: CoroutineDispatcher
|
||||||
) : PreferenceProvider {
|
) : PreferenceProvider {
|
||||||
|
private val mutex = Mutex()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Implementation note: EncryptedSharedPreferences are not thread-safe, so this implementation
|
* Implementation note: EncryptedSharedPreferences are not thread-safe, so this implementation
|
||||||
* confines them to a single background thread.
|
* confines them to a single background thread.
|
||||||
|
@ -45,6 +49,7 @@ class AndroidPreferenceProvider(
|
||||||
key: PreferenceKey,
|
key: PreferenceKey,
|
||||||
value: String?
|
value: String?
|
||||||
) = withContext(dispatcher) {
|
) = withContext(dispatcher) {
|
||||||
|
mutex.withLock {
|
||||||
val editor = sharedPreferences.edit()
|
val editor = sharedPreferences.edit()
|
||||||
|
|
||||||
editor.putString(key.key, value)
|
editor.putString(key.key, value)
|
||||||
|
@ -53,6 +58,7 @@ class AndroidPreferenceProvider(
|
||||||
|
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getString(key: PreferenceKey) =
|
override suspend fun getString(key: PreferenceKey) =
|
||||||
withContext(dispatcher) {
|
withContext(dispatcher) {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package co.electriccoin.zcash.ui.design.component
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.CheckboxDefaults
|
import androidx.compose.material3.CheckboxDefaults
|
||||||
|
@ -76,7 +75,6 @@ fun LabeledCheckBox(
|
||||||
modifier =
|
modifier =
|
||||||
modifier.then(
|
modifier.then(
|
||||||
Modifier
|
Modifier
|
||||||
.wrapContentSize()
|
|
||||||
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
||||||
.clickable {
|
.clickable {
|
||||||
setCheckedState(!checkedState)
|
setCheckedState(!checkedState)
|
||||||
|
|
|
@ -44,6 +44,7 @@ android {
|
||||||
"src/main/res/ui/onboarding",
|
"src/main/res/ui/onboarding",
|
||||||
"src/main/res/ui/receive",
|
"src/main/res/ui/receive",
|
||||||
"src/main/res/ui/restore",
|
"src/main/res/ui/restore",
|
||||||
|
"src/main/res/ui/restore_success",
|
||||||
"src/main/res/ui/scan",
|
"src/main/res/ui/scan",
|
||||||
"src/main/res/ui/security_warning",
|
"src/main/res/ui/security_warning",
|
||||||
"src/main/res/ui/seed_recovery",
|
"src/main/res/ui/seed_recovery",
|
||||||
|
|
|
@ -33,9 +33,9 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
booleanStateFlow(StandardPreferenceKeys.IS_KEEP_SCREEN_ON_DURING_SYNC)
|
booleanStateFlow(StandardPreferenceKeys.IS_KEEP_SCREEN_ON_DURING_SYNC)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flow of whether the app presented the user with an initial restoring dialog
|
* A flow of whether the app presented the user with restore success screem
|
||||||
*/
|
*/
|
||||||
val isRestoringInitialWarningSeen: StateFlow<Boolean?> =
|
val isRestoreSuccessSeen: StateFlow<Boolean?> =
|
||||||
booleanStateFlow(StandardPreferenceKeys.IS_RESTORING_INITIAL_WARNING_SEEN)
|
booleanStateFlow(StandardPreferenceKeys.IS_RESTORING_INITIAL_WARNING_SEEN)
|
||||||
|
|
||||||
fun setRestoringInitialWarningSeen() {
|
fun setRestoringInitialWarningSeen() {
|
||||||
|
|
|
@ -55,8 +55,6 @@ internal fun WrapHome(
|
||||||
|
|
||||||
val isKeepScreenOnWhileSyncing = homeViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value
|
val isKeepScreenOnWhileSyncing = homeViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
val isRestoringInitialWarningSeen = homeViewModel.isRestoringInitialWarningSeen.collectAsStateWithLifecycle().value
|
|
||||||
|
|
||||||
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
|
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
val walletRestoringState = walletViewModel.walletRestoringState.collectAsStateWithLifecycle().value
|
val walletRestoringState = walletViewModel.walletRestoringState.collectAsStateWithLifecycle().value
|
||||||
|
@ -66,20 +64,24 @@ internal fun WrapHome(
|
||||||
walletViewModel.persistWalletRestoringState(WalletRestoringState.SYNCING)
|
walletViewModel.persistWalletRestoringState(WalletRestoringState.SYNCING)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isShowingRestoreInitDialog by rememberSaveable { mutableStateOf(false) }
|
// TODO [#1523]: Refactor RestoreSuccess screen navigation
|
||||||
val setShowingRestoreInitDialog = {
|
// TODO [#1523]: https://github.com/Electric-Coin-Company/zashi-android/issues/1523
|
||||||
|
val isRestoreSuccessSeen = homeViewModel.isRestoreSuccessSeen.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
|
var isShowingRestoreSuccess by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val setShowingRestoreSuccess = {
|
||||||
homeViewModel.setRestoringInitialWarningSeen()
|
homeViewModel.setRestoringInitialWarningSeen()
|
||||||
isShowingRestoreInitDialog = false
|
isShowingRestoreSuccess = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show initial restoring warn dialog
|
// Show initial restore success screen
|
||||||
isRestoringInitialWarningSeen?.let { restoringWarningSeen ->
|
isRestoreSuccessSeen?.let { restoreSuccessSeen ->
|
||||||
if (!restoringWarningSeen && walletRestoringState == WalletRestoringState.RESTORING) {
|
if (!restoreSuccessSeen && walletRestoringState == WalletRestoringState.RESTORING) {
|
||||||
LaunchedEffect(key1 = isShowingRestoreInitDialog) {
|
LaunchedEffect(key1 = isShowingRestoreSuccess) {
|
||||||
// Adding an extra little delay before displaying the dialog for a better UX
|
// Adding an extra little delay before displaying for a better UX
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
delay(1500)
|
delay(1500)
|
||||||
isShowingRestoreInitDialog = true
|
isShowingRestoreSuccess = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,9 +92,9 @@ internal fun WrapHome(
|
||||||
goSettings = goSettings,
|
goSettings = goSettings,
|
||||||
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
|
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
|
||||||
isKeepScreenOnWhileSyncing = isKeepScreenOnWhileSyncing,
|
isKeepScreenOnWhileSyncing = isKeepScreenOnWhileSyncing,
|
||||||
isShowingRestoreInitDialog = isShowingRestoreInitDialog,
|
isShowingRestoreSuccess = isShowingRestoreSuccess,
|
||||||
sendArguments = sendArguments,
|
sendArguments = sendArguments,
|
||||||
setShowingRestoreInitDialog = setShowingRestoreInitDialog,
|
setShowingRestoreSuccess = setShowingRestoreSuccess,
|
||||||
walletSnapshot = walletSnapshot
|
walletSnapshot = walletSnapshot
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -106,9 +108,9 @@ internal fun WrapHome(
|
||||||
goScan: () -> Unit,
|
goScan: () -> Unit,
|
||||||
goSendConfirmation: (ZecSend) -> Unit,
|
goSendConfirmation: (ZecSend) -> Unit,
|
||||||
isKeepScreenOnWhileSyncing: Boolean?,
|
isKeepScreenOnWhileSyncing: Boolean?,
|
||||||
isShowingRestoreInitDialog: Boolean,
|
isShowingRestoreSuccess: Boolean,
|
||||||
sendArguments: SendArguments,
|
sendArguments: SendArguments,
|
||||||
setShowingRestoreInitDialog: () -> Unit,
|
setShowingRestoreSuccess: () -> Unit,
|
||||||
walletSnapshot: WalletSnapshot?,
|
walletSnapshot: WalletSnapshot?,
|
||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
@ -209,8 +211,8 @@ internal fun WrapHome(
|
||||||
Home(
|
Home(
|
||||||
subScreens = tabs,
|
subScreens = tabs,
|
||||||
isKeepScreenOnWhileSyncing = isKeepScreenOnWhileSyncing,
|
isKeepScreenOnWhileSyncing = isKeepScreenOnWhileSyncing,
|
||||||
isShowingRestoreInitDialog = isShowingRestoreInitDialog,
|
isShowingRestoreSuccess = isShowingRestoreSuccess,
|
||||||
setShowingRestoreInitDialog = setShowingRestoreInitDialog,
|
setShowingRestoreSuccess = setShowingRestoreSuccess,
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
pagerState = pagerState,
|
pagerState = pagerState,
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,21 +19,19 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
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
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import androidx.constraintlayout.compose.Dimension
|
import androidx.constraintlayout.compose.Dimension
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import co.electriccoin.zcash.ui.R
|
|
||||||
import co.electriccoin.zcash.ui.common.compose.DisableScreenTimeout
|
import co.electriccoin.zcash.ui.common.compose.DisableScreenTimeout
|
||||||
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.BlankSurface
|
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||||
import co.electriccoin.zcash.ui.design.component.NavigationTabText
|
import co.electriccoin.zcash.ui.design.component.NavigationTabText
|
||||||
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.home.model.TabItem
|
import co.electriccoin.zcash.ui.screen.home.model.TabItem
|
||||||
|
import co.electriccoin.zcash.ui.screen.restoresuccess.WrapRestoreSuccess
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -46,8 +44,8 @@ private fun ComposablePreview() {
|
||||||
BlankSurface {
|
BlankSurface {
|
||||||
Home(
|
Home(
|
||||||
isKeepScreenOnWhileSyncing = false,
|
isKeepScreenOnWhileSyncing = false,
|
||||||
isShowingRestoreInitDialog = false,
|
isShowingRestoreSuccess = false,
|
||||||
setShowingRestoreInitDialog = {},
|
setShowingRestoreSuccess = {},
|
||||||
subScreens = persistentListOf(),
|
subScreens = persistentListOf(),
|
||||||
walletSnapshot = WalletSnapshotFixture.new(),
|
walletSnapshot = WalletSnapshotFixture.new(),
|
||||||
)
|
)
|
||||||
|
@ -60,8 +58,8 @@ private fun ComposablePreview() {
|
||||||
@Composable
|
@Composable
|
||||||
fun Home(
|
fun Home(
|
||||||
isKeepScreenOnWhileSyncing: Boolean?,
|
isKeepScreenOnWhileSyncing: Boolean?,
|
||||||
isShowingRestoreInitDialog: Boolean,
|
isShowingRestoreSuccess: Boolean,
|
||||||
setShowingRestoreInitDialog: () -> Unit,
|
setShowingRestoreSuccess: () -> Unit,
|
||||||
subScreens: ImmutableList<TabItem>,
|
subScreens: ImmutableList<TabItem>,
|
||||||
walletSnapshot: WalletSnapshot?,
|
walletSnapshot: WalletSnapshot?,
|
||||||
pagerState: PagerState =
|
pagerState: PagerState =
|
||||||
|
@ -76,8 +74,8 @@ fun Home(
|
||||||
subScreens = subScreens,
|
subScreens = subScreens,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isShowingRestoreInitDialog) {
|
if (isShowingRestoreSuccess) {
|
||||||
HomeRestoringInitialDialog(setShowingRestoreInitDialog)
|
WrapRestoreSuccess(onDone = setShowingRestoreSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isKeepScreenOnWhileSyncing == true &&
|
if (isKeepScreenOnWhileSyncing == true &&
|
||||||
|
@ -182,13 +180,3 @@ private fun HomeContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun HomeRestoringInitialDialog(setShowingRestoreInitDialog: () -> Unit) {
|
|
||||||
AppAlertDialog(
|
|
||||||
title = stringResource(id = R.string.restoring_initial_dialog_title),
|
|
||||||
text = stringResource(id = R.string.restoring_initial_dialog_description),
|
|
||||||
confirmButtonText = stringResource(id = R.string.restoring_initial_dialog_positive_button),
|
|
||||||
onConfirmButtonClick = setShowingRestoreInitDialog
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.restoresuccess
|
||||||
|
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import co.electriccoin.zcash.ui.common.compose.LocalActivity
|
||||||
|
import co.electriccoin.zcash.ui.screen.restoresuccess.view.RestoreSuccess
|
||||||
|
import co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel.RestoreSuccessViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WrapRestoreSuccess(onDone: () -> Unit) {
|
||||||
|
val activity = LocalActivity.current
|
||||||
|
|
||||||
|
val viewModel by activity.viewModels<RestoreSuccessViewModel>()
|
||||||
|
|
||||||
|
val state = viewModel.state.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
|
RestoreSuccess(
|
||||||
|
state =
|
||||||
|
state.copy(
|
||||||
|
onPositiveClick = {
|
||||||
|
state.onPositiveClick()
|
||||||
|
onDone()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.restoresuccess.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
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.ColorFilter
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.Devices
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||||
|
import co.electriccoin.zcash.ui.design.component.GridBgScaffold
|
||||||
|
import co.electriccoin.zcash.ui.design.component.LabeledCheckBox
|
||||||
|
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RestoreSuccess(state: RestoreSuccessViewState) {
|
||||||
|
GridBgScaffold { paddingValues ->
|
||||||
|
RestoreSuccessContent(
|
||||||
|
state = state,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(
|
||||||
|
top = paddingValues.calculateTopPadding(),
|
||||||
|
bottom = paddingValues.calculateBottomPadding()
|
||||||
|
)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
private fun RestoreSuccessContent(
|
||||||
|
state: RestoreSuccessViewState,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
modifier.then(
|
||||||
|
Modifier.padding(
|
||||||
|
start = ZcashTheme.dimens.screenHorizontalSpacingBig,
|
||||||
|
end = ZcashTheme.dimens.screenHorizontalSpacingBig
|
||||||
|
)
|
||||||
|
),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingBig))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.restore_success_title),
|
||||||
|
style = ZcashTheme.typography.secondary.headlineMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||||
|
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.img_success_dialog),
|
||||||
|
contentDescription = stringResource(id = R.string.restore_success_subtitle),
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.restore_success_subtitle),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = ZcashTheme.typography.secondary.headlineSmall,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.restore_success_description),
|
||||||
|
style = ZcashTheme.typography.secondary.bodySmall,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||||
|
|
||||||
|
LabeledCheckBox(
|
||||||
|
modifier = Modifier.align(Alignment.Start),
|
||||||
|
checked = state.isKeepScreenOnChecked,
|
||||||
|
onCheckedChange = { state.onCheckboxClick() },
|
||||||
|
text = stringResource(id = R.string.restoring_initial_dialog_checkbox)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||||
|
append(stringResource(id = R.string.restore_success_note_part_1))
|
||||||
|
}
|
||||||
|
append(" ")
|
||||||
|
append(stringResource(id = R.string.restore_success_note_part_2))
|
||||||
|
},
|
||||||
|
style = ZcashTheme.extendedTypography.footnote,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(ZcashTheme.dimens.spacingBig))
|
||||||
|
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
|
PrimaryButton(
|
||||||
|
onClick = state.onPositiveClick,
|
||||||
|
text = stringResource(id = R.string.restore_success_button)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingBig))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RestoreSuccessViewState(
|
||||||
|
val isKeepScreenOnChecked: Boolean,
|
||||||
|
val onCheckboxClick: () -> Unit,
|
||||||
|
val onPositiveClick: () -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RestoreSuccessViewPreview() =
|
||||||
|
BlankSurface {
|
||||||
|
RestoreSuccess(
|
||||||
|
state =
|
||||||
|
RestoreSuccessViewState(
|
||||||
|
isKeepScreenOnChecked = true,
|
||||||
|
onCheckboxClick = {},
|
||||||
|
onPositiveClick = {},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = Devices.PIXEL_7_PRO)
|
||||||
|
@Composable
|
||||||
|
private fun RestoreSuccessViewPreviewLight() =
|
||||||
|
ZcashTheme(false) {
|
||||||
|
RestoreSuccessViewPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = Devices.PIXEL_7_PRO)
|
||||||
|
@Composable
|
||||||
|
private fun RestoreSuccessViewPreviewDark() =
|
||||||
|
ZcashTheme(true) {
|
||||||
|
RestoreSuccessViewPreview()
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import co.electriccoin.zcash.preference.model.entry.BooleanPreferenceDefault
|
||||||
|
import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT
|
||||||
|
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
|
||||||
|
import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
|
||||||
|
import co.electriccoin.zcash.ui.screen.restoresuccess.view.RestoreSuccessViewState
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.WhileSubscribed
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class RestoreSuccessViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
private val keepScreenOn = MutableStateFlow(DEFAULT_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
|
val state =
|
||||||
|
keepScreenOn
|
||||||
|
.map { createState(keepScreenOn = it) }
|
||||||
|
.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||||
|
createState(keepScreenOn = DEFAULT_KEEP_SCREEN_ON)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createState(keepScreenOn: Boolean) =
|
||||||
|
RestoreSuccessViewState(
|
||||||
|
isKeepScreenOnChecked = keepScreenOn,
|
||||||
|
onCheckboxClick = { this.keepScreenOn.update { !keepScreenOn } },
|
||||||
|
onPositiveClick = {
|
||||||
|
setKeepScreenOnWhileSyncing(keepScreenOn)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun setKeepScreenOnWhileSyncing(enabled: Boolean) {
|
||||||
|
setBooleanPreference(StandardPreferenceKeys.IS_KEEP_SCREEN_ON_DURING_SYNC, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setBooleanPreference(
|
||||||
|
default: BooleanPreferenceDefault,
|
||||||
|
newState: Boolean
|
||||||
|
) = viewModelScope.launch {
|
||||||
|
val prefs = StandardPreferenceSingleton.getInstance(getApplication())
|
||||||
|
default.putValue(prefs, newState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val DEFAULT_KEEP_SCREEN_ON = true
|
|
@ -14,12 +14,8 @@ import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
class SettingsViewModel(application: Application) : AndroidViewModel(application) {
|
class SettingsViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
val isAnalyticsEnabled: StateFlow<Boolean?> = booleanStateFlow(StandardPreferenceKeys.IS_ANALYTICS_ENABLED)
|
val isAnalyticsEnabled: StateFlow<Boolean?> = booleanStateFlow(StandardPreferenceKeys.IS_ANALYTICS_ENABLED)
|
||||||
|
|
||||||
val isBackgroundSync: StateFlow<Boolean?> = booleanStateFlow(StandardPreferenceKeys.IS_BACKGROUND_SYNC_ENABLED)
|
val isBackgroundSync: StateFlow<Boolean?> = booleanStateFlow(StandardPreferenceKeys.IS_BACKGROUND_SYNC_ENABLED)
|
||||||
|
@ -51,9 +47,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val prefs = StandardPreferenceSingleton.getInstance(getApplication())
|
val prefs = StandardPreferenceSingleton.getInstance(getApplication())
|
||||||
mutex.withLock {
|
|
||||||
default.putValue(prefs, newState)
|
default.putValue(prefs, newState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,4 @@
|
||||||
<string name="home_tab_send">Send</string>
|
<string name="home_tab_send">Send</string>
|
||||||
<string name="home_tab_receive">Receive</string>
|
<string name="home_tab_receive">Receive</string>
|
||||||
<string name="home_tab_balances">Balances</string>
|
<string name="home_tab_balances">Balances</string>
|
||||||
|
|
||||||
<string name="restoring_initial_dialog_title">Success</string>
|
|
||||||
<string name="restoring_initial_dialog_description">Your wallet has been successfully restored! During the initial sync, your funds cannot be spent or sent. Depending on the age of your wallet, it may take a few hours to fully sync.</string>
|
|
||||||
<string name="restoring_initial_dialog_positive_button">OK</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="78dp"
|
||||||
|
android:height="175dp"
|
||||||
|
android:viewportWidth="78"
|
||||||
|
android:viewportHeight="175">
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M40.14,128.56l-6.2,2.5l4,9.72l6.2,-2.5z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M42.35,133.92L39.75,134.96C39.66,135 39.58,135.08 39.55,135.17C39.51,135.26 39.52,135.36 39.57,135.45L41.84,139.22L35.84,135.68L38.53,134.59C38.62,134.55 38.69,134.48 38.73,134.39C38.76,134.3 38.76,134.2 38.71,134.11L36.44,130.06L42.35,133.92Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</group>
|
||||||
|
<path
|
||||||
|
android:pathData="M44.49,69.32L35.54,65.32L42.3,59.57L30.59,44.77L48.54,54.48L49.7,58.58L41.31,62.95L44.49,69.33V69.32ZM36.78,65.29L43.34,68.22L40.83,63.18L36.78,65.29ZM48.17,55.14L38.01,64.04L49.06,58.29L48.17,55.14ZM32.63,46.47L42.68,59.18L47.83,54.7L32.63,46.47Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M12.93,21.03L19.85,13.15C19.96,13.03 19.95,12.83 19.83,12.73C19.7,12.62 19.5,12.63 19.39,12.76L17.67,14.72C17.15,14.51 16.62,14.4 16.07,14.4C13.55,14.4 11.79,16.74 11.72,16.83C11.63,16.94 11.64,17.07 11.72,17.19C12.4,18.03 13.11,18.65 13.85,19.04L12.46,20.63C12.35,20.75 12.36,20.95 12.48,21.05C12.55,21.1 12.61,21.12 12.68,21.12C12.77,21.12 12.85,21.08 12.92,21.01L12.93,21.03ZM12.36,17C12.81,16.48 14.24,15 16.09,15C16.48,15 16.88,15.06 17.25,15.19L16.78,15.74C16.56,15.6 16.3,15.54 16.02,15.54C15.21,15.54 14.54,16.21 14.54,17.01C14.54,17.36 14.66,17.68 14.86,17.93L14.28,18.6C13.62,18.26 12.99,17.73 12.37,17H12.36ZM20.32,16.81C20.41,16.93 20.41,17.07 20.32,17.19C20.25,17.29 18.49,19.61 15.96,19.61C15.67,19.61 15.36,19.57 15.07,19.5L15.54,18.97C15.69,18.99 15.84,18.99 15.98,18.99C17.8,18.99 19.23,17.52 19.68,17C19.24,16.49 18.79,16.08 18.34,15.76L18.74,15.31C19.27,15.69 19.8,16.18 20.31,16.8L20.32,16.81Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M65.52,1.35H12.47C6.28,1.35 1.25,6.37 1.25,12.57V134.04C1.25,140.23 6.28,145.25 12.47,145.25H65.52C71.71,145.25 76.73,140.23 76.73,134.04V12.57C76.73,6.37 71.71,1.35 65.52,1.35Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M39.74,78.89C38.1,78.89 36.45,78.7 34.84,78.34C31.2,77.51 27.81,75.77 25.03,73.27V73.14L24.32,72.45C21.88,70.06 20.02,67.12 18.92,63.89L23.81,66.45L22.28,69.67L23.26,70.83C27.37,75.68 33.38,78.46 39.77,78.46C44.87,78.46 49.83,76.66 53.71,73.37C57.24,70.37 59.77,66.25 60.81,61.73C60.83,61.62 60.92,61.54 61.03,61.54C61.13,61.54 61.21,61.63 61.21,61.73C58.95,71.68 49.91,78.89 39.74,78.89ZM18.24,51.92C18.14,51.91 18.06,51.81 18.06,51.71C20.35,42.28 28.79,35.3 38.56,34.81C38.92,34.79 39.27,34.78 39.62,34.78C45.06,34.78 50.26,36.76 54.31,40.37V40.5L55.01,41.19C57.47,43.59 59.32,46.54 60.43,49.78L55.42,47.19L56.92,43.97L55.94,42.82C52.97,39.32 48.86,36.82 44.38,35.77C42.77,35.4 41.12,35.2 39.48,35.2C29.41,35.2 20.77,42.04 18.45,51.83C18.45,51.86 18.35,51.91 18.27,51.91H18.23L18.24,51.92Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M43.13,145.25H43.63C43.91,145.25 44.13,145.47 44.13,145.75V156.75C44.13,157.58 43.46,158.25 42.63,158.25H42.13V167.03C42.13,167.31 41.91,167.53 41.63,167.53C41.35,167.53 41.13,167.31 41.13,167.03V157.75C41.13,157.47 41.35,157.25 41.63,157.25H42.63C42.91,157.25 43.13,157.03 43.13,156.75V146.25H36.13V156.75C36.13,157.03 36.35,157.25 36.63,157.25H37.63C37.91,157.25 38.13,157.47 38.13,157.75V171.03C38.13,171.31 37.91,171.53 37.63,171.53C37.35,171.53 37.13,171.31 37.13,171.03V158.25H36.63C35.8,158.25 35.13,157.58 35.13,156.75V145.75C35.13,145.47 35.35,145.25 35.63,145.25H36.13"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:pathData="M34.78,174.03L45.76,163.05"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M27.9,152.55C28.11,152.45 28.19,152.18 28.07,151.94C27.96,151.7 27.7,151.58 27.5,151.68L22.77,153.91C22.56,154.01 22.49,154.28 22.6,154.52C22.71,154.76 22.97,154.88 23.18,154.78L27.9,152.55Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M51.36,152.55C51.15,152.45 51.07,152.18 51.19,151.94C51.3,151.7 51.56,151.58 51.76,151.68L56.49,153.91C56.7,154.01 56.78,154.28 56.66,154.52C56.55,154.76 56.29,154.88 56.08,154.78L51.36,152.55Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M26.63,148.85C26.87,148.84 27.03,148.62 27,148.35C26.99,148.3 26.98,148.24 26.95,148.2C26.87,148.02 26.7,147.89 26.51,147.91L23.11,148.05C22.87,148.06 22.71,148.28 22.74,148.54C22.78,148.8 23,149.01 23.23,149L26.63,148.85Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M52.63,148.85C52.39,148.84 52.23,148.62 52.27,148.35C52.28,148.3 52.28,148.24 52.31,148.2C52.39,148.02 52.56,147.89 52.75,147.91L56.16,148.05C56.39,148.06 56.55,148.28 56.52,148.54C56.48,148.8 56.26,149.01 56.03,149L52.63,148.85Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M27.9,158.43L30.33,155.71C30.45,155.58 30.47,155.36 30.39,155.19C30.36,155.13 30.34,155.09 30.29,155.05C30.12,154.85 29.84,154.84 29.69,155.01L27.26,157.73C27.1,157.91 27.11,158.21 27.29,158.4C27.47,158.59 27.74,158.6 27.9,158.43Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M51.37,158.43L48.93,155.71C48.81,155.58 48.79,155.36 48.87,155.19C48.9,155.13 48.92,155.09 48.97,155.05C49.14,154.85 49.42,154.84 49.57,155.01L52.01,157.73C52.17,157.91 52.15,158.21 51.97,158.4C51.8,158.59 51.52,158.6 51.37,158.43Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="restore_success_title">Keep Zashi open!</string>
|
||||||
|
<string name="restore_success_subtitle">Your wallet has been successfully restored and is now syncing</string>
|
||||||
|
<string name="restore_success_description">To prevent interruption, keep your screen awake, plug your device into a power source, and keep it in a secure place.</string>
|
||||||
|
<string name="restoring_initial_dialog_checkbox">Keep screen on while restoring.</string>
|
||||||
|
<string name="restore_success_note_part_1">Note:</string>
|
||||||
|
<string name="restore_success_note_part_2">During the initial sync your funds cannot be sent or
|
||||||
|
spent. Depending on the age of your wallet, it may take a few hours to fully sync.</string>
|
||||||
|
<string name="restore_success_button">GOT IT!</string>
|
||||||
|
</resources>
|
Loading…
Reference in New Issue