[#947] About screen UI/logic/tests

* [#947] About screen UI (partially implemented)

* [#947] About screen UI

* [#947] Fixed failing test case

* [#947] Added Zashi text logo

---------

Co-authored-by: Honza Rychnovský <rychnovsky.honza@gmail.com>

* [#947] UI: About Screen

* [#1005] Remove DebugMenu from Home screen

- The new design does not require it on the Home screen
- Partially moved to the Settings and About screens

* [#392] Update About screen texts

Closes #392 as not needed now

* [#1006] Enhance VersionInfo with other fields

* [#947] About screen UI + logic + tests

* Adopt VersionInfo on different screens

* VersionInfo test

Update .gitignore

---------

Co-authored-by: Venkat-corebts <143575548+Venkat-corebts@users.noreply.github.com>
This commit is contained in:
Honza Rychnovský 2023-10-17 15:56:04 +02:00 committed by GitHub
parent e871c4eb45
commit 16bf1afa90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 413 additions and 281 deletions

1
.gitignore vendored
View File

@ -23,3 +23,4 @@ local.properties
/.idea/androidTestResultsUserPreferences.xml /.idea/androidTestResultsUserPreferences.xml
google-services.json google-services.json
/.idea/kotlinc.xml /.idea/kotlinc.xml
/.idea/other.xml

View File

@ -6,7 +6,6 @@ import co.electriccoin.zcash.configuration.test.fixture.BooleanDefaultEntryFixtu
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
@ -34,7 +33,6 @@ class MergingConfigurationProviderTest {
} }
@Test @Test
@OptIn(ExperimentalCoroutinesApi::class)
fun getFlow_ordering() = runTest { fun getFlow_ordering() = runTest {
val configurationProvider = MergingConfigurationProvider( val configurationProvider = MergingConfigurationProvider(
persistentListOf( persistentListOf(
@ -53,7 +51,6 @@ class MergingConfigurationProviderTest {
} }
@Test @Test
@OptIn(ExperimentalCoroutinesApi::class)
fun getFlow_empty() = runTest { fun getFlow_empty() = runTest {
val configurationProvider = MergingConfigurationProvider( val configurationProvider = MergingConfigurationProvider(
emptyList<ConfigurationProvider>().toPersistentList() emptyList<ConfigurationProvider>().toPersistentList()
@ -65,7 +62,6 @@ class MergingConfigurationProviderTest {
} }
@Test @Test
@OptIn(ExperimentalCoroutinesApi::class)
fun getUpdatedAt_newest() = runTest { fun getUpdatedAt_newest() = runTest {
val older = "2023-01-15T08:38:45.415Z".toInstant() val older = "2023-01-15T08:38:45.415Z".toInstant()
val newer = "2023-01-17T08:38:45.415Z".toInstant() val newer = "2023-01-17T08:38:45.415Z".toInstant()

View File

@ -27,6 +27,7 @@ data class Dimens(
val defaultButtonHeight: Dp, val defaultButtonHeight: Dp,
val zcashLogoHeight: Dp, val zcashLogoHeight: Dp,
val zcashLogoWidth: Dp, val zcashLogoWidth: Dp,
val zcashTextLogoHeight: Dp
) )
private val defaultDimens = Dimens( private val defaultDimens = Dimens(
@ -45,6 +46,7 @@ private val defaultDimens = Dimens(
defaultButtonHeight = 50.dp, defaultButtonHeight = 50.dp,
zcashLogoHeight = 100.dp, zcashLogoHeight = 100.dp,
zcashLogoWidth = 60.dp, zcashLogoWidth = 60.dp,
zcashTextLogoHeight = 30.dp
) )
private val normalDimens = defaultDimens private val normalDimens = defaultDimens

View File

@ -31,6 +31,7 @@ data class ExtendedColors(
val disabledButtonTextColor: Color, val disabledButtonTextColor: Color,
val buttonShadowColor: Color, val buttonShadowColor: Color,
val screenTitleColor: Color, val screenTitleColor: Color,
val aboutTextColor: Color,
val welcomeAnimationColor: Color, val welcomeAnimationColor: Color,
) { ) {
@Composable @Composable

View File

@ -60,6 +60,9 @@ internal object Dark {
val buttonShadowColor = Color(0xFFFFFFFF) val buttonShadowColor = Color(0xFFFFFFFF)
// to be added later
val aboutTextColor = Color.Unspecified
val screenTitleColor = Color(0xFF040404) val screenTitleColor = Color(0xFF040404)
val welcomeAnimationColor = Color(0xFF231F20) val welcomeAnimationColor = Color(0xFF231F20)
@ -122,6 +125,8 @@ internal object Light {
val screenTitleColor = Color(0xFF040404) val screenTitleColor = Color(0xFF040404)
val aboutTextColor = Color(0xFF4E4E4E)
val welcomeAnimationColor = Color(0xFF231F20) val welcomeAnimationColor = Color(0xFF231F20)
} }
@ -171,6 +176,7 @@ internal val DarkExtendedColorPalette = ExtendedColors(
reference = Dark.reference, reference = Dark.reference,
buttonShadowColor = Dark.buttonShadowColor, buttonShadowColor = Dark.buttonShadowColor,
screenTitleColor = Dark.screenTitleColor, screenTitleColor = Dark.screenTitleColor,
aboutTextColor = Dark.aboutTextColor,
welcomeAnimationColor = Dark.welcomeAnimationColor welcomeAnimationColor = Dark.welcomeAnimationColor
) )
@ -198,6 +204,7 @@ internal val LightExtendedColorPalette = ExtendedColors(
reference = Light.reference, reference = Light.reference,
buttonShadowColor = Light.buttonShadowColor, buttonShadowColor = Light.buttonShadowColor,
screenTitleColor = Light.screenTitleColor, screenTitleColor = Light.screenTitleColor,
aboutTextColor = Light.aboutTextColor,
welcomeAnimationColor = Light.welcomeAnimationColor welcomeAnimationColor = Light.welcomeAnimationColor
) )
@ -227,6 +234,7 @@ internal val LocalExtendedColors = staticCompositionLocalOf {
reference = Color.Unspecified, reference = Color.Unspecified,
buttonShadowColor = Color.Unspecified, buttonShadowColor = Color.Unspecified,
screenTitleColor = Color.Unspecified, screenTitleColor = Color.Unspecified,
aboutTextColor = Color.Unspecified,
welcomeAnimationColor = Color.Unspecified, welcomeAnimationColor = Color.Unspecified,
) )
} }

View File

@ -112,6 +112,7 @@ data class ExtendedTypography(
val chipIndex: TextStyle, val chipIndex: TextStyle,
val listItem: TextStyle, val listItem: TextStyle,
val zecBalance: TextStyle, val zecBalance: TextStyle,
val aboutText: TextStyle,
val buttonText: TextStyle, val buttonText: TextStyle,
val checkboxText: TextStyle, val checkboxText: TextStyle,
val securityWarningText: TextStyle val securityWarningText: TextStyle
@ -141,6 +142,10 @@ val LocalExtendedTypography = staticCompositionLocalOf {
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 30.sp fontSize = 30.sp
), ),
aboutText = PrimaryTypography.bodyLarge.copy(
fontSize = 14.sp,
lineHeight = 20.sp
),
buttonText = PrimaryTypography.bodySmall.copy( buttonText = PrimaryTypography.bodySmall.copy(
fontSize = 14.sp fontSize = 14.sp
), ),

View File

@ -0,0 +1,25 @@
package co.electriccoin.zcash.ui.common.model
import androidx.test.filters.SmallTest
import co.electriccoin.zcash.ui.test.getAppContext
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class VersionInfoTest {
@Test
@SmallTest
fun sanity_check_version_info_in_testing() {
val versionInfo = VersionInfo.new(getAppContext())
// We expect some VersionInfo object parameters to be empty during the testing
// isDebuggable is not tested as it's not static during UI testing in CI or locally
assertEquals("null", versionInfo.versionName)
assertEquals(0, versionInfo.versionCode)
assertNotEquals(versionInfo.gitSha, "")
assertTrue(versionInfo.gitCommitCount >= 1)
}
}

View File

@ -1,84 +0,0 @@
package co.electriccoin.zcash.ui.screen.about
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.filters.MediumTest
import co.electriccoin.zcash.build.gitSha
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.ConfigInfoFixture
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
import co.electriccoin.zcash.ui.screen.about.model.VersionInfo
import co.electriccoin.zcash.ui.screen.about.view.About
import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
class AboutViewTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
@MediumTest
fun setup() {
newTestSetup()
composeTestRule.onNodeWithText(VersionInfoFixture.VERSION_NAME, substring = true).also {
it.assertExists()
}
composeTestRule.onNodeWithText(gitSha, substring = true).also {
it.assertExists()
}
}
@Test
@MediumTest
fun back() {
val testSetup = newTestSetup()
assertEquals(0, testSetup.getOnBackCount())
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.about_back_content_description)).also {
it.performClick()
}
assertEquals(1, testSetup.getOnBackCount())
}
private fun newTestSetup() = TestSetup(
composeTestRule,
VersionInfoFixture.new(),
ConfigInfoFixture.new()
)
private class TestSetup(
private val composeTestRule: ComposeContentTestRule,
versionInfo: VersionInfo,
configInfo: ConfigInfo
) {
private val onBackCount = AtomicInteger(0)
fun getOnBackCount(): Int {
composeTestRule.waitForIdle()
return onBackCount.get()
}
init {
composeTestRule.setContent {
ZcashTheme {
About(versionInfo = versionInfo, configInfo = configInfo) {
onBackCount.incrementAndGet()
}
}
}
}
}
}

View File

@ -0,0 +1,97 @@
package co.electriccoin.zcash.ui.screen.about.view
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.filters.MediumTest
import androidx.test.filters.SmallTest
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.fixture.ConfigInfoFixture
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
import co.electriccoin.zcash.ui.screen.about.AboutTag
import co.electriccoin.zcash.ui.test.getStringResource
import org.junit.Rule
import kotlin.test.Test
import kotlin.test.assertEquals
class AboutViewTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
@MediumTest
fun default_ui_state_test() {
val testSetup = newTestSetup()
assertEquals(0, testSetup.getOnBackCount())
composeTestRule.onNodeWithText(getStringResource(R.string.about_back).uppercase()).also {
it.assertExists()
}
composeTestRule.onNodeWithContentDescription(
label = getStringResource(R.string.about_app_logo_content_description)
).also {
it.assertExists()
}
composeTestRule.onNodeWithText(getStringResource(R.string.about_description)).also {
it.assertExists()
}
}
@Test
@MediumTest
fun version_setup_test() {
newTestSetup()
composeTestRule.onNodeWithText(VersionInfoFixture.VERSION_NAME, substring = true).also {
it.assertExists()
}
composeTestRule.onNodeWithText(VersionInfoFixture.VERSION_CODE.toString(), substring = true).also {
it.assertExists()
}
}
@Test
@MediumTest
fun back_test() {
val testSetup = newTestSetup()
assertEquals(0, testSetup.getOnBackCount())
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.about_back_content_description)).also {
it.performClick()
}
assertEquals(1, testSetup.getOnBackCount())
}
@Test
@SmallTest
fun debug_menu_visible_test() {
newTestSetup(isDebuggable = true)
composeTestRule.onNodeWithTag(AboutTag.DEBUG_MENU_TAG).also {
it.assertExists()
}
}
@Test
@SmallTest
fun debug_menu_not_visible_test() {
newTestSetup(isDebuggable = false)
composeTestRule.onNodeWithTag(AboutTag.DEBUG_MENU_TAG).also {
it.assertDoesNotExist()
}
}
private fun newTestSetup(isDebuggable: Boolean = false) = AboutViewTestSetup(
composeTestRule,
VersionInfoFixture.new(isDebuggable = isDebuggable),
ConfigInfoFixture.new()
)
}

View File

@ -0,0 +1,33 @@
package co.electriccoin.zcash.ui.screen.about.view
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
import java.util.concurrent.atomic.AtomicInteger
class AboutViewTestSetup(
private val composeTestRule: ComposeContentTestRule,
versionInfo: VersionInfo,
configInfo: ConfigInfo
) {
private val onBackCount = AtomicInteger(0)
fun getOnBackCount(): Int {
composeTestRule.waitForIdle()
return onBackCount.get()
}
init {
composeTestRule.setContent {
ZcashTheme {
About(
onBack = { onBackCount.incrementAndGet() },
versionInfo = versionInfo,
configInfo = configInfo
)
}
}
}
}

View File

@ -71,7 +71,6 @@ class HomeTestSetup(
isKeepScreenOnDuringSync = false, isKeepScreenOnDuringSync = false,
isFiatConversionEnabled = isShowFiatConversion, isFiatConversionEnabled = isShowFiatConversion,
isCircularProgressBarEnabled = isCircularProgressBar, isCircularProgressBarEnabled = isCircularProgressBar,
isDebugMenuEnabled = false,
goSettings = { goSettings = {
onSettingsCount.incrementAndGet() onSettingsCount.incrementAndGet()
}, },
@ -93,7 +92,6 @@ class HomeTestSetup(
goHistory = { goHistory = {
onHistoryCount.incrementAndGet() onHistoryCount.incrementAndGet()
}, },
resetSdk = {},
drawerState = drawerValues.drawerState, drawerState = drawerValues.drawerState,
scope = drawerValues.scope scope = drawerValues.scope
) )

View File

@ -3,10 +3,8 @@ package co.electriccoin.zcash.ui.screen.securitywarning.view
import androidx.compose.material3.SnackbarHostState 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.spackle.getPackageInfoCompat
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.screen.about.model.VersionInfo import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
import co.electriccoin.zcash.ui.test.getAppContext
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -50,9 +48,7 @@ class SecurityWarningViewTestSetup(private val composeTestRule: ComposeContentTe
onConfirm = { onConfirm = {
onConfirmCount.incrementAndGet() onConfirmCount.incrementAndGet()
}, },
versionInfo = VersionInfo.new( versionInfo = VersionInfoFixture.new()
getAppContext().packageManager.getPackageInfoCompat(getAppContext().packageName, 0L)
)
) )
} }

View File

@ -74,6 +74,9 @@ internal fun MainActivity.Navigation() {
WrapSettings( WrapSettings(
goBack = { goBack = {
navController.popBackStackJustOnce(SETTINGS) navController.popBackStackJustOnce(SETTINGS)
},
goAbout = {
navController.navigateJustOnce(ABOUT)
} }
) )
} }

View File

@ -0,0 +1,37 @@
package co.electriccoin.zcash.ui.common.model
import android.content.Context
import android.content.pm.ApplicationInfo
import co.electriccoin.zcash.build.gitCommitCount
import co.electriccoin.zcash.build.gitSha
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
import co.electriccoin.zcash.spackle.getPackageInfoCompat
import co.electriccoin.zcash.spackle.versionCodeCompat
data class VersionInfo(
val versionName: String,
val versionCode: Long,
val isDebuggable: Boolean,
val gitSha: String,
val gitCommitCount: Long
) {
companion object {
fun new(context: Context): VersionInfo {
val packageInfo = context.packageManager.getPackageInfoCompat(context.packageName, 0L)
val applicationInfo = context.applicationInfo
return VersionInfo(
versionName = packageInfo.versionName ?: "null", // Should only be null during tests
versionCode = packageInfo.versionCodeCompat, // Should only be 0 during tests
isDebuggable = (
(0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) &&
!FirebaseTestLabUtil.isFirebaseTestLab(context.applicationContext) &&
!EmulatorWtfUtil.isEmulatorWtf(context.applicationContext)
),
gitSha = gitSha,
gitCommitCount = gitCommitCount.toLong()
)
}
}
}

View File

@ -1,15 +1,27 @@
package co.electriccoin.zcash.ui.fixture package co.electriccoin.zcash.ui.fixture
import co.electriccoin.zcash.ui.screen.about.model.VersionInfo import co.electriccoin.zcash.ui.common.model.VersionInfo
// Magic Number doesn't matter here for hard-coded fixture values // Magic Number doesn't matter here for hard-coded fixture values
@Suppress("MagicNumber") @Suppress("MagicNumber")
object VersionInfoFixture { object VersionInfoFixture {
const val VERSION_NAME = "1.0.3" const val VERSION_NAME = "1.0.0"
const val VERSION_CODE = 3L const val VERSION_CODE = 1L
const val IS_DEBUGGABLE = false
const val GIT_SHA = "635dac0eb9ddc2bc6da5177f0dd495d8b76af4dc"
const val GIT_COMMIT_COUNT = 1L
fun new( fun new(
versionName: String = VERSION_NAME, versionName: String = VERSION_NAME,
versionCode: Long = VERSION_CODE versionCode: Long = VERSION_CODE,
) = VersionInfo(versionName, versionCode) isDebuggable: Boolean = IS_DEBUGGABLE,
gitSha: String = GIT_SHA,
gitCommitCount: Long = GIT_COMMIT_COUNT
) = VersionInfo(
versionName,
versionCode,
isDebuggable,
gitSha,
gitCommitCount
)
} }

View File

@ -0,0 +1,8 @@
package co.electriccoin.zcash.ui.screen.about
/**
* These are only used for automated testing.
*/
object AboutTag {
const val DEBUG_MENU_TAG = "debug_menu"
}

View File

@ -6,9 +6,8 @@ import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import co.electriccoin.zcash.configuration.AndroidConfigurationFactory import co.electriccoin.zcash.configuration.AndroidConfigurationFactory
import co.electriccoin.zcash.spackle.getPackageInfoCompat
import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.screen.about.model.VersionInfo import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.screen.about.view.About import co.electriccoin.zcash.ui.screen.about.view.About
import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
@ -24,13 +23,17 @@ internal fun WrapAbout(
activity: ComponentActivity, activity: ComponentActivity,
goBack: () -> Unit goBack: () -> Unit
) { ) {
val packageInfo = activity.packageManager.getPackageInfoCompat(activity.packageName, 0L) val configInfo = ConfigInfo.new(AndroidConfigurationFactory.getInstance(activity.applicationContext))
val configurationProvider = AndroidConfigurationFactory.getInstance(activity.applicationContext) val versionInfo = VersionInfo.new(activity.applicationContext)
About(VersionInfo.new(packageInfo), ConfigInfo.new(configurationProvider), goBack)
// Allows an implicit way to force configuration refresh by simply visiting the About screen // Allows an implicit way to force configuration refresh by simply visiting the About screen
LaunchedEffect(key1 = true) { LaunchedEffect(key1 = true) {
AndroidConfigurationFactory.getInstance(activity.applicationContext).hintToRefresh() AndroidConfigurationFactory.getInstance(activity.applicationContext).hintToRefresh()
} }
About(
onBack = goBack,
versionInfo = versionInfo,
configInfo = configInfo
)
} }

View File

@ -1,13 +0,0 @@
package co.electriccoin.zcash.ui.screen.about.model
import android.content.pm.PackageInfo
import co.electriccoin.zcash.spackle.versionCodeCompat
data class VersionInfo(val versionName: String, val versionCode: Long) {
companion object {
fun new(packageInfo: PackageInfo) = VersionInfo(
packageInfo.versionName ?: "null", // Should only be null during tests
packageInfo.versionCodeCompat
)
}
}

View File

@ -1,34 +1,47 @@
package co.electriccoin.zcash.ui.screen.about.view package co.electriccoin.zcash.ui.screen.about.view
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
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.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
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.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import co.electriccoin.zcash.build.gitSha
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.Body import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.design.component.GradientSurface import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.Header
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.ConfigInfoFixture import co.electriccoin.zcash.ui.fixture.ConfigInfoFixture
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
import co.electriccoin.zcash.ui.screen.about.model.VersionInfo import co.electriccoin.zcash.ui.screen.about.AboutTag
import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
@Preview("About") @Preview("About")
@ -37,9 +50,9 @@ private fun AboutPreview() {
ZcashTheme(darkTheme = false) { ZcashTheme(darkTheme = false) {
GradientSurface { GradientSurface {
About( About(
onBack = {},
versionInfo = VersionInfoFixture.new(), versionInfo = VersionInfoFixture.new(),
configInfo = ConfigInfoFixture.new(), configInfo = ConfigInfoFixture.new()
goBack = {}
) )
} }
} }
@ -47,16 +60,19 @@ private fun AboutPreview() {
@Composable @Composable
fun About( fun About(
onBack: () -> Unit,
versionInfo: VersionInfo, versionInfo: VersionInfo,
configInfo: ConfigInfo, configInfo: ConfigInfo
goBack: () -> Unit
) { ) {
Scaffold(topBar = { Scaffold(topBar = {
AboutTopAppBar(onBack = goBack) AboutTopAppBar(
onBack = onBack,
versionInfo = versionInfo,
configInfo = configInfo
)
}) { paddingValues -> }) { paddingValues ->
AboutMainContent( AboutMainContent(
versionInfo, versionInfo = versionInfo,
configInfo,
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.verticalScroll( .verticalScroll(
@ -65,8 +81,8 @@ fun About(
.padding( .padding(
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault, top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingDefault, bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingDefault,
start = ZcashTheme.dimens.spacingDefault, start = ZcashTheme.dimens.spacingHuge,
end = ZcashTheme.dimens.spacingDefault end = ZcashTheme.dimens.spacingHuge
) )
) )
} }
@ -74,52 +90,122 @@ fun About(
@Composable @Composable
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
private fun AboutTopAppBar(onBack: () -> Unit) { private fun AboutTopAppBar(
TopAppBar( onBack: () -> Unit,
title = { Text(text = stringResource(id = R.string.about_title)) }, versionInfo: VersionInfo,
configInfo: ConfigInfo
) {
CenterAlignedTopAppBar(
title = {
Text(
text = stringResource(id = R.string.about_title).uppercase(),
style = ZcashTheme.typography.primary.titleSmall,
color = ZcashTheme.colors.screenTitleColor
)
},
navigationIcon = { navigationIcon = {
IconButton( Row(
onClick = onBack verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Icon( IconButton(
imageVector = Icons.Filled.ArrowBack, onClick = onBack
contentDescription = stringResource(R.string.about_back_content_description) ) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource(R.string.about_back_content_description)
)
}
Text(
text = stringResource(id = R.string.about_back).uppercase(),
style = ZcashTheme.typography.primary.bodyMedium
) )
} }
},
actions = {
if (versionInfo.isDebuggable) {
DebugMenu(versionInfo, configInfo)
}
} }
) )
} }
@Composable
private fun DebugMenu(
versionInfo: VersionInfo,
configInfo: ConfigInfo
) {
Column(
modifier = Modifier.testTag(AboutTag.DEBUG_MENU_TAG)
) {
var expanded by rememberSaveable { mutableStateOf(false) }
IconButton(onClick = { expanded = true }) {
Icon(Icons.Default.MoreVert, contentDescription = null)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = {
Column {
Text(stringResource(R.string.about_debug_menu_build, versionInfo.gitSha))
Text(configInfo.toSupportString())
}
},
onClick = {
expanded = false
}
)
}
}
}
@Composable @Composable
fun AboutMainContent( fun AboutMainContent(
versionInfo: VersionInfo, versionInfo: VersionInfo,
configInfo: ConfigInfo,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column(modifier) { Column(modifier) {
Icon(painterResource(id = R.drawable.zashi_logo), contentDescription = null) val logoContentDescription = stringResource(R.string.about_app_logo_content_description)
Text(stringResource(id = R.string.app_name)) Row(
verticalAlignment = Alignment.CenterVertically,
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge)) modifier = Modifier.semantics(mergeDescendants = true) {
contentDescription = logoContentDescription
Header(stringResource(id = R.string.about_version_header)) }
Body(stringResource(R.string.about_version_format, versionInfo.versionName, versionInfo.versionCode)) ) {
Image(
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge)) painter = painterResource(id = R.drawable.zashi_logo_without_text),
contentDescription = null,
Header(stringResource(id = R.string.about_build_header)) Modifier
Body(gitSha) .height(ZcashTheme.dimens.zcashLogoHeight)
.width(ZcashTheme.dimens.zcashLogoWidth)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge)) )
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingDefault))
configInfo.configurationUpdatedAt?.let { updatedAt -> Image(
Header(stringResource(id = R.string.about_build_configuration)) painter = painterResource(id = R.drawable.zashi_text_logo),
Body(updatedAt.toString()) contentDescription = null,
modifier = Modifier.height(ZcashTheme.dimens.zcashTextLogoHeight)
)
} }
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge)) Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
Header(stringResource(id = R.string.about_legal_header)) Text(
Body(stringResource(id = R.string.about_legal_info)) text = stringResource(
R.string.about_version_format,
versionInfo.versionName,
versionInfo.versionCode
),
style = ZcashTheme.typography.primary.titleSmall
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
Text(
text = stringResource(id = R.string.about_description),
color = ZcashTheme.colors.aboutTextColor,
style = ZcashTheme.extendedTypography.aboutText
)
} }
} }

View File

@ -10,11 +10,7 @@ import androidx.compose.material3.DrawerValue
import androidx.compose.material3.rememberDrawerState import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
import co.electriccoin.zcash.ui.BuildConfig
import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.common.closeDrawerMenu import co.electriccoin.zcash.ui.common.closeDrawerMenu
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
@ -86,23 +82,14 @@ internal fun WrapHome(
if (null == walletSnapshot) { if (null == walletSnapshot) {
// Display loading indicator // Display loading indicator
} else { } else {
val context = LocalContext.current
// We might eventually want to check the debuggable property of the manifest instead
// of relying on BuildConfig.
val isDebugMenuEnabled = BuildConfig.DEBUG &&
!FirebaseTestLabUtil.isFirebaseTestLab(context) &&
!EmulatorWtfUtil.isEmulatorWtf(context)
val drawerValues = drawerBackHandler() val drawerValues = drawerBackHandler()
Home( Home(
walletSnapshot, walletSnapshot = walletSnapshot,
isUpdateAvailable = updateAvailable, isUpdateAvailable = updateAvailable,
isKeepScreenOnDuringSync = isKeepScreenOnWhileSyncing, isKeepScreenOnDuringSync = isKeepScreenOnWhileSyncing,
isFiatConversionEnabled = isFiatConversionEnabled, isFiatConversionEnabled = isFiatConversionEnabled,
isCircularProgressBarEnabled = isCircularProgressBarEnabled, isCircularProgressBarEnabled = isCircularProgressBarEnabled,
isDebugMenuEnabled = isDebugMenuEnabled,
goSeedPhrase = goSeedPhrase, goSeedPhrase = goSeedPhrase,
goSettings = goSettings, goSettings = goSettings,
goSupport = goSupport, goSupport = goSupport,
@ -110,9 +97,6 @@ internal fun WrapHome(
goReceive = goReceive, goReceive = goReceive,
goSend = goSend, goSend = goSend,
goHistory = goHistory, goHistory = goHistory,
resetSdk = {
walletViewModel.resetSdk()
},
drawerState = drawerValues.drawerState, drawerState = drawerValues.drawerState,
scope = drawerValues.scope scope = drawerValues.scope
) )

View File

@ -19,14 +19,11 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ContactSupport import androidx.compose.material.icons.filled.ContactSupport
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Password import androidx.compose.material.icons.filled.Password
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerState
import androidx.compose.material3.DrawerValue import androidx.compose.material3.DrawerValue
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -40,9 +37,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberDrawerState import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -56,7 +51,6 @@ import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.PercentDecimal
import co.electriccoin.zcash.crash.android.GlobalCrashReporter
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.DisableScreenTimeout import co.electriccoin.zcash.ui.common.DisableScreenTimeout
import co.electriccoin.zcash.ui.common.closeDrawerMenu import co.electriccoin.zcash.ui.common.closeDrawerMenu
@ -83,7 +77,6 @@ private fun ComposablePreview() {
walletSnapshot = WalletSnapshotFixture.new(), walletSnapshot = WalletSnapshotFixture.new(),
isUpdateAvailable = false, isUpdateAvailable = false,
isKeepScreenOnDuringSync = false, isKeepScreenOnDuringSync = false,
isDebugMenuEnabled = false,
isFiatConversionEnabled = false, isFiatConversionEnabled = false,
isCircularProgressBarEnabled = false, isCircularProgressBarEnabled = false,
goSeedPhrase = {}, goSeedPhrase = {},
@ -93,7 +86,6 @@ private fun ComposablePreview() {
goReceive = {}, goReceive = {},
goSend = {}, goSend = {},
goHistory = {}, goHistory = {},
resetSdk = {},
drawerState = rememberDrawerState(DrawerValue.Closed), drawerState = rememberDrawerState(DrawerValue.Closed),
scope = rememberCoroutineScope() scope = rememberCoroutineScope()
) )
@ -109,7 +101,6 @@ fun Home(
isKeepScreenOnDuringSync: Boolean?, isKeepScreenOnDuringSync: Boolean?,
isFiatConversionEnabled: Boolean, isFiatConversionEnabled: Boolean,
isCircularProgressBarEnabled: Boolean, isCircularProgressBarEnabled: Boolean,
isDebugMenuEnabled: Boolean,
goSeedPhrase: () -> Unit, goSeedPhrase: () -> Unit,
goSettings: () -> Unit, goSettings: () -> Unit,
goSupport: () -> Unit, goSupport: () -> Unit,
@ -117,7 +108,6 @@ fun Home(
goReceive: () -> Unit, goReceive: () -> Unit,
goSend: () -> Unit, goSend: () -> Unit,
goHistory: () -> Unit, goHistory: () -> Unit,
resetSdk: () -> Unit,
drawerState: DrawerState, drawerState: DrawerState,
scope: CoroutineScope scope: CoroutineScope
) { ) {
@ -135,9 +125,7 @@ fun Home(
content = { content = {
Scaffold(topBar = { Scaffold(topBar = {
HomeTopAppBar( HomeTopAppBar(
isDebugMenuEnabled = isDebugMenuEnabled, openDrawer = { drawerState.openDrawerMenu(scope) }
openDrawer = { drawerState.openDrawerMenu(scope) },
resetSdk = resetSdk
) )
}) { paddingValues -> }) { paddingValues ->
HomeMainContent( HomeMainContent(
@ -164,9 +152,7 @@ fun Home(
@Composable @Composable
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
private fun HomeTopAppBar( private fun HomeTopAppBar(
isDebugMenuEnabled: Boolean, openDrawer: () -> Unit
openDrawer: () -> Unit,
resetSdk: () -> Unit,
) { ) {
TopAppBar( TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) }, title = { Text(text = stringResource(id = R.string.app_name)) },
@ -180,59 +166,10 @@ private fun HomeTopAppBar(
contentDescription = stringResource(R.string.home_menu_content_description) contentDescription = stringResource(R.string.home_menu_content_description)
) )
} }
},
actions = {
if (isDebugMenuEnabled) {
DebugMenu(resetSdk)
}
} }
) )
} }
@Composable
private fun DebugMenu(
resetSdk: () -> Unit
) {
Column {
var expanded by rememberSaveable { mutableStateOf(false) }
IconButton(onClick = { expanded = true }) {
Icon(Icons.Default.MoreVert, contentDescription = null)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text("Throw Uncaught Exception") },
onClick = {
// Supposed to be generic, for manual debugging only
@Suppress("TooGenericExceptionThrown")
throw RuntimeException("Manually crashed from debug menu")
}
)
DropdownMenuItem(
text = { Text("Report Caught Exception") },
onClick = {
// Eventually this shouldn't rely on the Android implementation, but rather an expect/actual
// should be used at the crash API level.
GlobalCrashReporter.reportCaughtException(
RuntimeException("Manually caught exception from debug menu")
)
expanded = false
}
)
DropdownMenuItem(
text = { Text("Reset SDK") },
onClick = {
resetSdk()
expanded = false
}
)
}
}
}
@Composable @Composable
private fun HomeDrawer( private fun HomeDrawer(
onCloseDrawer: () -> Unit, onCloseDrawer: () -> Unit,

View File

@ -6,7 +6,6 @@ import android.content.Context
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cash.z.ecc.android.sdk.WalletInitMode import cash.z.ecc.android.sdk.WalletInitMode
import cash.z.ecc.android.sdk.fixture.WalletFixture import cash.z.ecc.android.sdk.fixture.WalletFixture
@ -17,10 +16,9 @@ import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork import cash.z.ecc.android.sdk.model.defaultForNetwork
import cash.z.ecc.sdk.type.fromResources import cash.z.ecc.sdk.type.fromResources
import co.electriccoin.lightwallet.client.model.LightWalletEndpoint import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
import co.electriccoin.zcash.ui.BuildConfig
import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries 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.screen.home.model.OnboardingState import co.electriccoin.zcash.ui.screen.home.model.OnboardingState
@ -43,13 +41,7 @@ internal fun WrapOnboarding(
val walletViewModel by activity.viewModels<WalletViewModel>() val walletViewModel by activity.viewModels<WalletViewModel>()
val onboardingViewModel by activity.viewModels<OnboardingViewModel>() val onboardingViewModel by activity.viewModels<OnboardingViewModel>()
val applicationContext = LocalContext.current.applicationContext val versionInfo = VersionInfo.new(activity.applicationContext)
// We might eventually want to check the debuggable property of the manifest instead
// of relying on BuildConfig.
val isDebugMenuEnabled = BuildConfig.DEBUG &&
!FirebaseTestLabUtil.isFirebaseTestLab(applicationContext) &&
!EmulatorWtfUtil.isEmulatorWtf(applicationContext)
// TODO [#383]: https://github.com/zcash/secant-android-wallet/issues/383 // TODO [#383]: https://github.com/zcash/secant-android-wallet/issues/383
// TODO [#383]: Refactoring of UI state retention into rememberSaveable fields // TODO [#383]: Refactoring of UI state retention into rememberSaveable fields
@ -63,12 +55,12 @@ internal fun WrapOnboarding(
// Firebase Test Lab or Google Play pre-launch report, we want to skip creating // Firebase Test Lab or Google Play pre-launch report, we want to skip creating
// a new or restoring an existing wallet screens by persisting an existing wallet // a new or restoring an existing wallet screens by persisting an existing wallet
// with a mock seed. // with a mock seed.
if (FirebaseTestLabUtil.isFirebaseTestLab(applicationContext)) { if (FirebaseTestLabUtil.isFirebaseTestLab(activity.applicationContext)) {
persistExistingWalletWithSeedPhrase( persistExistingWalletWithSeedPhrase(
applicationContext, activity.applicationContext,
walletViewModel, walletViewModel,
SeedPhrase.new(WalletFixture.Alice.seedPhrase), SeedPhrase.new(WalletFixture.Alice.seedPhrase),
birthday = WalletFixture.Alice.getBirthday(ZcashNetwork.fromResources(applicationContext)) birthday = WalletFixture.Alice.getBirthday(ZcashNetwork.fromResources(activity.applicationContext))
) )
} else { } else {
onboardingViewModel.setIsImporting(true) onboardingViewModel.setIsImporting(true)
@ -79,10 +71,10 @@ internal fun WrapOnboarding(
val onFixtureWallet = { val onFixtureWallet = {
persistExistingWalletWithSeedPhrase( persistExistingWalletWithSeedPhrase(
applicationContext, activity.applicationContext,
walletViewModel, walletViewModel,
SeedPhrase.new(WalletFixture.Alice.seedPhrase), SeedPhrase.new(WalletFixture.Alice.seedPhrase),
birthday = WalletFixture.Alice.getBirthday(ZcashNetwork.fromResources(applicationContext)) birthday = WalletFixture.Alice.getBirthday(ZcashNetwork.fromResources(activity.applicationContext))
) )
} }
@ -100,7 +92,7 @@ internal fun WrapOnboarding(
} else { } else {
LongOnboarding( LongOnboarding(
onboardingState = onboardingViewModel.onboardingState, onboardingState = onboardingViewModel.onboardingState,
isDebugMenuEnabled = isDebugMenuEnabled, isDebugMenuEnabled = versionInfo.isDebuggable,
onImportWallet = onImportWallet, onImportWallet = onImportWallet,
onCreateWallet = onCreateWallet, onCreateWallet = onCreateWallet,
onFixtureWallet = onFixtureWallet onFixtureWallet = onFixtureWallet

View File

@ -8,10 +8,9 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import co.electriccoin.zcash.configuration.AndroidConfigurationFactory import co.electriccoin.zcash.configuration.AndroidConfigurationFactory
import co.electriccoin.zcash.spackle.getPackageInfoCompat
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.screen.about.model.VersionInfo import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.screen.securitywarning.util.WebBrowserUtil import co.electriccoin.zcash.ui.screen.securitywarning.util.WebBrowserUtil
import co.electriccoin.zcash.ui.screen.securitywarning.view.SecurityWarning import co.electriccoin.zcash.ui.screen.securitywarning.view.SecurityWarning
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -35,14 +34,12 @@ internal fun WrapSecurityWarning(
onBack: () -> Unit, onBack: () -> Unit,
onConfirm: () -> Unit onConfirm: () -> Unit
) { ) {
val packageInfo = activity.packageManager.getPackageInfoCompat(activity.packageName, 0L)
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
SecurityWarning( SecurityWarning(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
versionInfo = VersionInfo.new(packageInfo), versionInfo = VersionInfo.new(activity.applicationContext),
onBack = onBack, onBack = onBack,
onAcknowledged = { onAcknowledged = {
// Needed for UI testing only // Needed for UI testing only

View File

@ -36,13 +36,13 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
import co.electriccoin.zcash.ui.design.component.CheckBox import co.electriccoin.zcash.ui.design.component.CheckBox
import co.electriccoin.zcash.ui.design.component.GradientSurface import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.PrimaryButton import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
import co.electriccoin.zcash.ui.screen.about.model.VersionInfo
@Preview("Security Warning") @Preview("Security Warning")
@Composable @Composable

View File

@ -6,8 +6,8 @@ import androidx.activity.ComponentActivity
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.electriccoin.zcash.ui.BuildConfig
import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries 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.screen.home.viewmodel.WalletViewModel import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
@ -17,11 +17,13 @@ import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
@Composable @Composable
internal fun MainActivity.WrapSettings( internal fun MainActivity.WrapSettings(
goBack: () -> Unit goBack: () -> Unit,
goAbout: () -> Unit,
) { ) {
WrapSettings( WrapSettings(
activity = this, activity = this,
goBack = goBack, goBack = goBack,
goAbout = goAbout
) )
} }
@ -29,10 +31,13 @@ internal fun MainActivity.WrapSettings(
private fun WrapSettings( private fun WrapSettings(
activity: ComponentActivity, activity: ComponentActivity,
goBack: () -> Unit, goBack: () -> Unit,
goAbout: () -> Unit,
) { ) {
val walletViewModel by activity.viewModels<WalletViewModel>() val walletViewModel by activity.viewModels<WalletViewModel>()
val settingsViewModel by activity.viewModels<SettingsViewModel>() val settingsViewModel by activity.viewModels<SettingsViewModel>()
val versionInfo = VersionInfo.new(activity.applicationContext)
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
val isBackgroundSyncEnabled = settingsViewModel.isBackgroundSync.collectAsStateWithLifecycle().value val isBackgroundSyncEnabled = settingsViewModel.isBackgroundSync.collectAsStateWithLifecycle().value
val isKeepScreenOnWhileSyncing = settingsViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value val isKeepScreenOnWhileSyncing = settingsViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value
@ -48,7 +53,7 @@ private fun WrapSettings(
} else { } else {
Settings( Settings(
TroubleshootingParameters( TroubleshootingParameters(
isEnabled = BuildConfig.DEBUG, isEnabled = versionInfo.isDebuggable,
isBackgroundSyncEnabled = isBackgroundSyncEnabled, isBackgroundSyncEnabled = isBackgroundSyncEnabled,
isKeepScreenOnDuringSyncEnabled = isKeepScreenOnWhileSyncing, isKeepScreenOnDuringSyncEnabled = isKeepScreenOnWhileSyncing,
isAnalyticsEnabled = isAnalyticsEnabled, isAnalyticsEnabled = isAnalyticsEnabled,
@ -59,7 +64,7 @@ private fun WrapSettings(
onDocumentation = {}, onDocumentation = {},
onPrivacyPolicy = {}, onPrivacyPolicy = {},
onFeedback = {}, onFeedback = {},
onAbout = {}, onAbout = goAbout,
onRescanWallet = { onRescanWallet = {
walletViewModel.rescanBlockchain() walletViewModel.rescanBlockchain()
}, },

View File

@ -1,13 +1,14 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="about_title">About</string> <string name="about_title">About</string>
<string name="about_back">Back</string>
<string name="about_back_content_description">Back</string> <string name="about_back_content_description">Back</string>
<string name="about_icon_content_description">Zcash icon</string> <string name="about_app_logo_content_description">Zcash logo</string>
<string name="about_version_header">Version</string> <string name="about_version_format" formatted="true">Version <xliff:g example="1.0" id="version_name">%1$s
<string name="about_version_format" formatted="true"><xliff:g id="version_name" example="1.0">%1$s</xliff:g> (<xliff:g id="version_code" example="1.0">%2$d</xliff:g>)</string> </xliff:g> (<xliff:g example="1.0" id="version_code">%2$d</xliff:g>)</string>
<string name="about_build_header">Build</string> <string name="about_debug_menu_build">Build: <xliff:g example="635dac0eb9ddc2bc6da5177f0dd495d8b76af4dc"
<string name="about_build_configuration">Configuration</string> id="git_commit_hash">%1$s</xliff:g></string>
<string name="about_legal_header">Legal</string> <string name="about_description">Send and receive ZEC on Zashi! Zashi is a minimal-design, self-custody, ZEC-only
<!-- TODO [#392] Update with real legal info. --> shielded wallet that keeps your transaction history and wallet balance private. Built by Zcashers, for
<!-- TODO [#392] https://github.com/zcash/secant-android-wallet/issues/392 --> Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in
<string name="about_legal_info">GitHub Issue #392</string> user-feedback mechanism to enable more features, more quickly.</string>
</resources> </resources>

View File

@ -680,7 +680,9 @@ private fun supportScreenshots(resContext: Context, tag: String, composeTestRule
} }
private fun aboutScreenshots(resContext: Context, tag: String, composeTestRule: ComposeTestRule) { private fun aboutScreenshots(resContext: Context, tag: String, composeTestRule: ComposeTestRule) {
composeTestRule.onNode(hasText(resContext.getString(R.string.about_title))).also { composeTestRule.onNode(
hasText(resContext.getString(R.string.about_title).uppercase())
).also {
it.assertExists() it.assertExists()
} }