[#1234] Advanced settings screen (#1272)

- Changelog update
- Closes #1234
This commit is contained in:
Honza Rychnovský 2024-03-02 18:57:49 +01:00 committed by GitHub
parent 77c9f3198a
commit c8e3a05eb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 253 additions and 191 deletions

View File

@ -9,6 +9,10 @@ directly impact users rather than highlighting other key architectural updates.*
## [Unreleased]
### Added
- Advanced Settings screen that provides more technical options like Export private data, Recovery phrase, or
Choose server
## [0.2.0 (560)] - 2024-02-27
### Added

View File

@ -31,6 +31,7 @@ android {
res.setSrcDirs(
setOf(
"src/main/res/ui/about",
"src/main/res/ui/advanced_settings",
"src/main/res/ui/account",
"src/main/res/ui/balances",
"src/main/res/ui/common",

View File

@ -11,11 +11,8 @@ class SettingsViewTestSetup(
private val troubleshootingParameters: TroubleshootingParameters
) {
private val onBackCount = AtomicInteger(0)
private val onBackupCount = AtomicInteger(0)
private val onDocumentationCount = AtomicInteger(0)
private val onPrivacyPolicyCount = AtomicInteger(0)
private val onFeedbackCount = AtomicInteger(0)
private val onExportPrivateData = AtomicInteger(0)
private val onAdvancedSettingsCount = AtomicInteger(0)
private val onAboutCount = AtomicInteger(0)
private val onRescanCount = AtomicInteger(0)
private val onBackgroundSyncChangedCount = AtomicInteger(0)
@ -27,29 +24,14 @@ class SettingsViewTestSetup(
return onBackCount.get()
}
fun getBackupCount(): Int {
composeTestRule.waitForIdle()
return onBackupCount.get()
}
fun getDocumentationCount(): Int {
composeTestRule.waitForIdle()
return onDocumentationCount.get()
}
fun getPrivacyPolicyCount(): Int {
composeTestRule.waitForIdle()
return onPrivacyPolicyCount.get()
}
fun getFeedbackCount(): Int {
composeTestRule.waitForIdle()
return onFeedbackCount.get()
}
fun getExportPrivateDataCount(): Int {
fun getAdvancedSettingsCount(): Int {
composeTestRule.waitForIdle()
return onExportPrivateData.get()
return onAdvancedSettingsCount.get()
}
fun getAboutCount(): Int {
@ -85,20 +67,11 @@ class SettingsViewTestSetup(
onBack = {
onBackCount.incrementAndGet()
},
onSeedRecovery = {
onBackupCount.incrementAndGet()
},
onDocumentation = {
onDocumentationCount.incrementAndGet()
},
onPrivacyPolicy = {
onPrivacyPolicyCount.incrementAndGet()
},
onFeedback = {
onFeedbackCount.incrementAndGet()
},
onExportPrivateData = {
onExportPrivateData.incrementAndGet()
onAdvancedSettings = {
onAdvancedSettingsCount.incrementAndGet()
},
onAbout = {
onAboutCount.incrementAndGet()

View File

@ -58,70 +58,19 @@ class SettingsViewTest : UiTestPrerequisites() {
@Test
@MediumTest
fun on_backup_test() {
fun on_advanced_settings_test() {
val testSetup = SettingsViewTestSetup(composeTestRule, TroubleshootingParametersFixture.new())
assertEquals(0, testSetup.getBackupCount())
assertEquals(0, testSetup.getAdvancedSettingsCount())
composeTestRule.onNodeWithText(
text = getStringResource(R.string.settings_backup_wallet),
text = getStringResource(R.string.settings_advanced_settings),
ignoreCase = true
).also {
it.performClick()
}
assertEquals(1, testSetup.getBackupCount())
}
@Test
@MediumTest
fun on_documentation_test() {
val testSetup = SettingsViewTestSetup(composeTestRule, TroubleshootingParametersFixture.new())
assertEquals(0, testSetup.getDocumentationCount())
composeTestRule.onNodeWithText(
text = getStringResource(R.string.settings_documentation),
ignoreCase = true
).also {
it.performClick()
}
assertEquals(1, testSetup.getDocumentationCount())
}
@Test
@MediumTest
fun on_privacy_policy_test() {
val testSetup = SettingsViewTestSetup(composeTestRule, TroubleshootingParametersFixture.new())
assertEquals(0, testSetup.getPrivacyPolicyCount())
composeTestRule.onNodeWithText(
text = getStringResource(R.string.settings_privacy_policy),
ignoreCase = true
).also {
it.performClick()
}
assertEquals(1, testSetup.getPrivacyPolicyCount())
}
@Test
@MediumTest
fun on_export_private_data_test() {
val testSetup = SettingsViewTestSetup(composeTestRule, TroubleshootingParametersFixture.new())
assertEquals(0, testSetup.getExportPrivateDataCount())
composeTestRule.onNodeWithText(
text = getStringResource(R.string.settings_export_private_data),
ignoreCase = true
).also {
it.performClick()
}
assertEquals(1, testSetup.getExportPrivateDataCount())
assertEquals(1, testSetup.getAdvancedSettingsCount())
}
@Test

View File

@ -1,8 +1,6 @@
package co.electriccoin.zcash.ui
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavHostController
import androidx.navigation.NavOptionsBuilder
import androidx.navigation.compose.NavHost
@ -12,6 +10,7 @@ import co.electriccoin.zcash.ui.NavigationArguments.SEND_AMOUNT
import co.electriccoin.zcash.ui.NavigationArguments.SEND_MEMO
import co.electriccoin.zcash.ui.NavigationArguments.SEND_RECIPIENT_ADDRESS
import co.electriccoin.zcash.ui.NavigationTargets.ABOUT
import co.electriccoin.zcash.ui.NavigationTargets.ADVANCED_SETTINGS
import co.electriccoin.zcash.ui.NavigationTargets.EXPORT_PRIVATE_DATA
import co.electriccoin.zcash.ui.NavigationTargets.HISTORY
import co.electriccoin.zcash.ui.NavigationTargets.HOME
@ -23,6 +22,7 @@ import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
import co.electriccoin.zcash.ui.configuration.RemoteConfig
import co.electriccoin.zcash.ui.screen.about.WrapAbout
import co.electriccoin.zcash.ui.screen.advancedsettings.WrapAdvancedSettings
import co.electriccoin.zcash.ui.screen.exportdata.WrapExportPrivateData
import co.electriccoin.zcash.ui.screen.history.WrapHistory
import co.electriccoin.zcash.ui.screen.home.WrapHome
@ -39,7 +39,6 @@ import kotlinx.serialization.json.Json
@Composable
@Suppress("LongMethod")
internal fun MainActivity.Navigation() {
val context = LocalContext.current
val navController =
rememberNavController().also {
navControllerForTesting = it
@ -80,27 +79,31 @@ internal fun MainActivity.Navigation() {
goAbout = {
navController.navigateJustOnce(ABOUT)
},
goAdvancedSettings = {
navController.navigateJustOnce(ADVANCED_SETTINGS)
},
goBack = {
navController.popBackStackJustOnce(SETTINGS)
},
goDocumentation = {
// TODO [#1084]: Documentation screen
// TODO [#1084]: https://github.com/Electric-Coin-Company/zashi-android/issues/1084
Toast.makeText(context, context.getString(R.string.not_implemented_yet), Toast.LENGTH_SHORT).show()
},
goExportPrivateData = {
navController.navigateJustOnce(EXPORT_PRIVATE_DATA)
},
goFeedback = {
navController.navigateJustOnce(SUPPORT)
},
goPrivacyPolicy = {
// TODO [#1083]: Privacy Policy screen
// TODO [#1083]: https://github.com/Electric-Coin-Company/zashi-android/issues/1083
Toast.makeText(context, context.getString(R.string.not_implemented_yet), Toast.LENGTH_SHORT).show()
)
}
composable(ADVANCED_SETTINGS) {
WrapAdvancedSettings(
goBack = {
navController.popBackStackJustOnce(ADVANCED_SETTINGS)
},
goExportPrivateData = {
navController.navigateJustOnce(EXPORT_PRIVATE_DATA)
},
goSeedRecovery = {
navController.navigateJustOnce(SEED_RECOVERY)
},
goChooseServer = {
// TODO [#1235]: Create screen for selecting the lightwalletd server
// TODO [#1235]: https://github.com/Electric-Coin-Company/zashi-android/issues/1235
}
)
}
@ -187,6 +190,7 @@ object NavigationArguments {
object NavigationTargets {
const val ABOUT = "about"
const val ACCOUNT = "account"
const val ADVANCED_SETTINGS = "advanced_settings"
const val EXPORT_PRIVATE_DATA = "export_private_data"
const val HISTORY = "history"
const val HOME = "home"

View File

@ -0,0 +1,8 @@
package co.electriccoin.zcash.ui.screen.advancedsettings
/**
* These are only used for automated testing.
*/
object AdvancedSettingsTag {
const val ADVANCED_SETTINGS_TOP_APP_BAR = "advanced_settings_top_app_bar"
}

View File

@ -0,0 +1,42 @@
@file:Suppress("ktlint:standard:filename")
package co.electriccoin.zcash.ui.screen.advancedsettings
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import co.electriccoin.zcash.ui.screen.advancedsettings.view.AdvancedSettings
@Composable
internal fun WrapAdvancedSettings(
goBack: () -> Unit,
goExportPrivateData: () -> Unit,
goSeedRecovery: () -> Unit,
goChooseServer: () -> Unit,
) {
WrapSettings(
goBack = goBack,
goExportPrivateData = goExportPrivateData,
goChooseServer = goChooseServer,
goSeedRecovery = goSeedRecovery,
)
}
@Composable
@Suppress("LongParameterList")
private fun WrapSettings(
goBack: () -> Unit,
goExportPrivateData: () -> Unit,
goChooseServer: () -> Unit,
goSeedRecovery: () -> Unit,
) {
BackHandler {
goBack()
}
AdvancedSettings(
onBack = goBack,
onSeedRecovery = goSeedRecovery,
onExportPrivateData = goExportPrivateData,
onChooseServer = goChooseServer
)
}

View File

@ -0,0 +1,119 @@
package co.electriccoin.zcash.ui.screen.advancedsettings.view
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.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
import co.electriccoin.zcash.ui.screen.advancedsettings.AdvancedSettingsTag
// TODO [#1271]: Add AdvancedSettingsView Tests
// TODO [#1271]: https://github.com/Electric-Coin-Company/zashi-android/issues/1271
@Preview("Advanced Settings")
@Composable
private fun PreviewAdvancedSettings() {
ZcashTheme(forceDarkMode = false) {
GradientSurface {
AdvancedSettings(
onBack = {},
onExportPrivateData = {},
onSeedRecovery = {},
onChooseServer = {},
)
}
}
}
@Composable
fun AdvancedSettings(
onBack: () -> Unit,
onExportPrivateData: () -> Unit,
onChooseServer: () -> Unit,
onSeedRecovery: () -> Unit,
) {
Scaffold(topBar = {
AdvancedSettingsTopAppBar(
onBack = onBack,
)
}) { paddingValues ->
AdvancedSettingsMainContent(
modifier =
Modifier
.verticalScroll(
rememberScrollState()
)
.padding(
top = paddingValues.calculateTopPadding() + dimens.spacingHuge,
bottom = paddingValues.calculateBottomPadding(),
start = dimens.screenHorizontalSpacingBig,
end = dimens.screenHorizontalSpacingBig
),
onExportPrivateData = onExportPrivateData,
onSeedRecovery = onSeedRecovery,
onChooseServer = onChooseServer,
)
}
}
@Composable
private fun AdvancedSettingsTopAppBar(onBack: () -> Unit) {
SmallTopAppBar(
backText = stringResource(id = R.string.advanced_settings_back).uppercase(),
backContentDescriptionText = stringResource(R.string.advanced_settings_back_content_description),
onBack = onBack,
showTitleLogo = true,
modifier = Modifier.testTag(AdvancedSettingsTag.ADVANCED_SETTINGS_TOP_APP_BAR)
)
}
@Composable
private fun AdvancedSettingsMainContent(
onSeedRecovery: () -> Unit,
onExportPrivateData: () -> Unit,
onChooseServer: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
Modifier
.fillMaxSize()
.then(modifier),
horizontalAlignment = Alignment.CenterHorizontally
) {
PrimaryButton(
onClick = onSeedRecovery,
text = stringResource(R.string.advanced_settings_backup_wallet)
)
Spacer(modifier = Modifier.height(dimens.spacingDefault))
PrimaryButton(
onClick = onExportPrivateData,
text = stringResource(R.string.advanced_settings_export_private_data)
)
Spacer(modifier = Modifier.height(dimens.spacingDefault))
PrimaryButton(
onClick = onChooseServer,
text = stringResource(R.string.advanced_settings_choose_server)
)
Spacer(modifier = Modifier.height(dimens.spacingHuge))
}
}

View File

@ -18,39 +18,28 @@ import co.electriccoin.zcash.ui.screen.settings.view.Settings
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
@Composable
@Suppress("LongParameterList")
internal fun MainActivity.WrapSettings(
goAbout: () -> Unit,
goAdvancedSettings: () -> Unit,
goBack: () -> Unit,
goDocumentation: () -> Unit,
goExportPrivateData: () -> Unit,
goFeedback: () -> Unit,
goPrivacyPolicy: () -> Unit,
goSeedRecovery: () -> Unit,
) {
WrapSettings(
activity = this,
goAbout = goAbout,
goAdvancedSettings = goAdvancedSettings,
goBack = goBack,
goDocumentation = goDocumentation,
goExportPrivateData = goExportPrivateData,
goFeedback = goFeedback,
goPrivacyPolicy = goPrivacyPolicy,
goSeedRecovery = goSeedRecovery
)
}
@Composable
@Suppress("LongParameterList")
private fun WrapSettings(
activity: ComponentActivity,
goAbout: () -> Unit,
goAdvancedSettings: () -> Unit,
goBack: () -> Unit,
goDocumentation: () -> Unit,
goExportPrivateData: () -> Unit,
goFeedback: () -> Unit,
goPrivacyPolicy: () -> Unit,
goSeedRecovery: () -> Unit,
) {
val walletViewModel by activity.viewModels<WalletViewModel>()
val settingsViewModel by activity.viewModels<SettingsViewModel>()
@ -78,20 +67,18 @@ private fun WrapSettings(
CircularScreenProgressIndicator()
} else {
Settings(
TroubleshootingParameters(
isEnabled = versionInfo.isDebuggable && !versionInfo.isRunningUnderTestService,
isBackgroundSyncEnabled = isBackgroundSyncEnabled,
isKeepScreenOnDuringSyncEnabled = isKeepScreenOnWhileSyncing,
isAnalyticsEnabled = isAnalyticsEnabled,
isRescanEnabled = ConfigurationEntries.IS_RESCAN_ENABLED.getValue(RemoteConfig.current),
),
onBack = goBack,
onSeedRecovery = goSeedRecovery,
onDocumentation = goDocumentation,
onPrivacyPolicy = goPrivacyPolicy,
onFeedback = goFeedback,
onAbout = goAbout,
onExportPrivateData = goExportPrivateData,
onAdvancedSettings = goAdvancedSettings,
onBack = goBack,
onFeedback = goFeedback,
troubleshootingParameters =
TroubleshootingParameters(
isEnabled = versionInfo.isDebuggable && !versionInfo.isRunningUnderTestService,
isBackgroundSyncEnabled = isBackgroundSyncEnabled,
isKeepScreenOnDuringSyncEnabled = isKeepScreenOnWhileSyncing,
isAnalyticsEnabled = isAnalyticsEnabled,
isRescanEnabled = ConfigurationEntries.IS_RESCAN_ENABLED.getValue(RemoteConfig.current),
),
onRescanWallet = {
walletViewModel.rescanBlockchain()
},

View File

@ -44,24 +44,22 @@ private fun PreviewSettings() {
ZcashTheme(forceDarkMode = false) {
GradientSurface {
Settings(
TroubleshootingParameters(
isEnabled = false,
isBackgroundSyncEnabled = false,
isKeepScreenOnDuringSyncEnabled = false,
isAnalyticsEnabled = false,
isRescanEnabled = false
),
onBack = {},
onSeedRecovery = {},
onDocumentation = {},
onPrivacyPolicy = {},
onFeedback = {},
onExportPrivateData = {},
onAbout = {},
onAdvancedSettings = {},
onBack = {},
onFeedback = {},
onRescanWallet = {},
onBackgroundSyncSettingsChanged = {},
onKeepScreenOnDuringSyncSettingsChanged = {},
onAnalyticsSettingsChanged = {}
onAnalyticsSettingsChanged = {},
troubleshootingParameters =
TroubleshootingParameters(
isEnabled = false,
isBackgroundSyncEnabled = false,
isKeepScreenOnDuringSyncEnabled = false,
isAnalyticsEnabled = false,
isRescanEnabled = false
),
)
}
}
@ -70,18 +68,15 @@ private fun PreviewSettings() {
@Composable
@Suppress("LongParameterList")
fun Settings(
troubleshootingParameters: TroubleshootingParameters,
onBack: () -> Unit,
onSeedRecovery: () -> Unit,
onDocumentation: () -> Unit,
onPrivacyPolicy: () -> Unit,
onFeedback: () -> Unit,
onExportPrivateData: () -> Unit,
onAbout: () -> Unit,
onAdvancedSettings: () -> Unit,
onBack: () -> Unit,
onFeedback: () -> Unit,
onRescanWallet: () -> Unit,
onBackgroundSyncSettingsChanged: (Boolean) -> Unit,
onKeepScreenOnDuringSyncSettingsChanged: (Boolean) -> Unit,
onAnalyticsSettingsChanged: (Boolean) -> Unit
onAnalyticsSettingsChanged: (Boolean) -> Unit,
troubleshootingParameters: TroubleshootingParameters,
) {
Scaffold(topBar = {
SettingsTopAppBar(
@ -105,12 +100,9 @@ fun Settings(
start = dimens.screenHorizontalSpacingBig,
end = dimens.screenHorizontalSpacingBig
),
onSeedRecovery = onSeedRecovery,
onDocumentation = onDocumentation,
onPrivacyPolicy = onPrivacyPolicy,
onFeedback = onFeedback,
onExportPrivateData = onExportPrivateData,
onAbout = onAbout,
onAdvancedSettings = onAdvancedSettings,
onFeedback = onFeedback,
)
}
}
@ -227,15 +219,11 @@ private fun TroubleshootingMenu(
}
@Composable
@Suppress("LongParameterList", "LongMethod")
private fun SettingsMainContent(
onSeedRecovery: () -> Unit,
onDocumentation: () -> Unit,
onPrivacyPolicy: () -> Unit,
onFeedback: () -> Unit,
onExportPrivateData: () -> Unit,
onAbout: () -> Unit,
modifier: Modifier = Modifier
onAdvancedSettings: () -> Unit,
onFeedback: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
Modifier
@ -244,13 +232,6 @@ private fun SettingsMainContent(
.then(modifier),
horizontalAlignment = Alignment.CenterHorizontally
) {
PrimaryButton(
onClick = onSeedRecovery,
text = stringResource(R.string.settings_backup_wallet)
)
Spacer(modifier = Modifier.height(dimens.spacingDefault))
PrimaryButton(
onClick = onFeedback,
text = stringResource(R.string.settings_send_us_feedback)
@ -259,22 +240,8 @@ private fun SettingsMainContent(
Spacer(modifier = Modifier.height(dimens.spacingDefault))
PrimaryButton(
onClick = onPrivacyPolicy,
text = stringResource(R.string.settings_privacy_policy)
)
Spacer(modifier = Modifier.height(dimens.spacingDefault))
PrimaryButton(
onClick = onDocumentation,
text = stringResource(R.string.settings_documentation)
)
Spacer(modifier = Modifier.height(dimens.spacingDefault))
PrimaryButton(
onClick = onExportPrivateData,
text = stringResource(R.string.settings_export_private_data)
onClick = onAdvancedSettings,
text = stringResource(R.string.settings_advanced_settings)
)
Spacer(

View File

@ -0,0 +1,9 @@
<resources>
<string name="advanced_settings_back">Back</string>
<string name="advanced_settings_back_content_description">Back</string>
<string name="advanced_settings_backup_wallet">Recovery phrase</string>
<string name="advanced_settings_export_private_data">Export private data</string>
<string name="advanced_settings_choose_server">Choose a server</string>
</resources>

View File

@ -8,11 +8,8 @@
<string name="settings_troubleshooting_enable_keep_screen_on">Keep screen on during sync</string>
<string name="settings_troubleshooting_enable_analytics">Report crashes</string>
<string name="settings_backup_wallet">Recovery phrase</string>
<string name="settings_documentation">Documentation</string>
<string name="settings_privacy_policy">Privacy policy</string>
<string name="settings_send_us_feedback">Send us feedback</string>
<string name="settings_export_private_data">Export private data</string>
<string name="settings_advanced_settings">Advanced</string>
<string name="settings_about">About</string>
</resources>

View File

@ -410,7 +410,9 @@ private fun settingsScreenshots(
tag: String,
composeTestRule: ComposeTestRule
) {
composeTestRule.onNode(hasText(resContext.getString(R.string.settings_backup_wallet), ignoreCase = true)).also {
composeTestRule.onNode(
hasText(resContext.getString(R.string.settings_send_us_feedback), ignoreCase = true)
).also {
it.assertExists()
}