[#941] Opening screen + Load animation

* [#941] Loading screen UI

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

* Fix unallowed doubled scroll

* Remove duplicate icon

* [#997] Splash alignment with welcome animation

* [#999] Compile SDK version 34

* [#1000] Colour system bars

* [#941] Load + Opening screens

- Contains UI of both these screens
- Their logic
- And the Load screen animation
- Designed util ScreenHeight composable
- Redesigned SecondaryButton to match the design and fixed its positioning on different screens

---------

Co-authored-by: Venkat-corebts <143575548+Venkat-corebts@users.noreply.github.com>
This commit is contained in:
Honza Rychnovský 2023-10-12 19:04:23 +02:00 committed by GitHub
parent 57a133a12c
commit e871c4eb45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 411 additions and 125 deletions

View File

@ -98,7 +98,7 @@ BIP_39_INCLUDED_BUILD_PATH=
# Versions
ANDROID_MIN_SDK_VERSION=27
ANDROID_TARGET_SDK_VERSION=33
ANDROID_COMPILE_SDK_VERSION=33
ANDROID_COMPILE_SDK_VERSION=34
ANDROID_NDK_VERSION=23.0.7599858
@ -115,8 +115,8 @@ JGIT_VERSION=6.4.0.202211300538-r
KTLINT_VERSION=0.49.0
PLAY_PUBLISHER_PLUGIN_VERSION=3.8.4
ACCOMPANIST_PERMISSIONS_VERSION=0.30.1
ANDROIDX_ACTIVITY_VERSION=1.7.1
ACCOMPANIST_PERMISSIONS_VERSION=0.32.0
ANDROIDX_ACTIVITY_VERSION=1.8.0
ANDROIDX_ANNOTATION_VERSION=1.6.0
ANDROIDX_APPCOMPAT_VERSION=1.6.1
ANDROIDX_CAMERA_VERSION=1.3.0-alpha06

View File

@ -139,7 +139,7 @@ dependencyResolutionManagement {
}
}
@Suppress("UnstableApiUsage", "MaxLineLength")
@Suppress("MaxLineLength")
versionCatalogs {
create("libs") {
val accompanistPermissionsVersion = extra["ACCOMPANIST_PERMISSIONS_VERSION"].toString()

View File

@ -54,8 +54,8 @@ private fun ButtonComposablePreview() {
}
}
@Suppress("LongParameterList")
@Composable
@Suppress("LongParameterList")
fun PrimaryButton(
onClick: () -> Unit,
text: String,
@ -105,6 +105,7 @@ fun PrimaryButton(
}
@Composable
@Suppress("LongParameterList")
fun SecondaryButton(
onClick: () -> Unit,
text: String,
@ -113,24 +114,42 @@ fun SecondaryButton(
horizontal = ZcashTheme.dimens.spacingDefault,
vertical = ZcashTheme.dimens.spacingSmall
),
enabled: Boolean = true
enabled: Boolean = true,
buttonColor: Color = MaterialTheme.colorScheme.secondary,
textColor: Color = MaterialTheme.colorScheme.onSecondary,
) {
Button(
shape = RectangleShape,
onClick = onClick,
modifier = modifier.then(
Modifier
.fillMaxWidth()
.padding(outerPaddingValues)
),
enabled = enabled,
colors = buttonColors(containerColor = MaterialTheme.colorScheme.secondary)
modifier = modifier
.padding(outerPaddingValues)
.shadow(
ZcashTheme.colors.buttonShadowColor,
borderRadius = 0.dp,
offsetX = ZcashTheme.dimens.shadowOffsetX,
offsetY = ZcashTheme.dimens.shadowOffsetY,
spread = ZcashTheme.dimens.shadowSpread,
blurRadius = 0.dp,
stroke = textColor != MaterialTheme.colorScheme.primary,
)
.translationClick(
translationX = ZcashTheme.dimens.shadowOffsetX + 6.dp, // + 6dp to exactly cover the bottom shadow
translationY = ZcashTheme.dimens.shadowOffsetX + 6.dp
)
.defaultMinSize(ZcashTheme.dimens.defaultButtonWidth, ZcashTheme.dimens.defaultButtonHeight)
.border(1.dp, Color.Black),
colors = buttonColors(
buttonColor,
disabledContainerColor = ZcashTheme.colors.disabledButtonColor,
disabledContentColor = ZcashTheme.colors.disabledButtonTextColor
),
onClick = onClick,
) {
Text(
style = MaterialTheme.typography.labelLarge,
style = ZcashTheme.extendedTypography.buttonText,
textAlign = TextAlign.Center,
text = text.uppercase(),
color = MaterialTheme.colorScheme.onSecondary
color = textColor
)
}
}

View File

@ -51,6 +51,27 @@ fun Body(
)
}
@Composable
@Suppress("LongParameterList")
fun TitleLarge(
text: String,
modifier: Modifier = Modifier,
maxLines: Int = Int.MAX_VALUE,
overflow: TextOverflow = TextOverflow.Clip,
textAlign: TextAlign = TextAlign.Start,
color: Color = MaterialTheme.colorScheme.onBackground,
) {
Text(
text = text,
color = color,
maxLines = maxLines,
overflow = overflow,
textAlign = textAlign,
modifier = modifier,
style = MaterialTheme.typography.titleLarge,
)
}
@Composable
fun Small(
text: String,

View File

@ -25,6 +25,8 @@ data class Dimens(
val shadowSpread: Dp,
val defaultButtonWidth: Dp,
val defaultButtonHeight: Dp,
val zcashLogoHeight: Dp,
val zcashLogoWidth: Dp,
)
private val defaultDimens = Dimens(
@ -41,6 +43,8 @@ private val defaultDimens = Dimens(
shadowSpread = 10.dp,
defaultButtonWidth = 230.dp,
defaultButtonHeight = 50.dp,
zcashLogoHeight = 100.dp,
zcashLogoWidth = 60.dp,
)
private val normalDimens = defaultDimens

View File

@ -31,6 +31,7 @@ data class ExtendedColors(
val disabledButtonTextColor: Color,
val buttonShadowColor: Color,
val screenTitleColor: Color,
val welcomeAnimationColor: Color,
) {
@Composable
fun surfaceGradient() = Brush.verticalGradient(

View File

@ -60,8 +60,9 @@ internal object Dark {
val buttonShadowColor = Color(0xFFFFFFFF)
// to be added later for dark theme
val screenTitleColor = Color.Unspecified
val screenTitleColor = Color(0xFF040404)
val welcomeAnimationColor = Color(0xFF231F20)
}
internal object Light {
@ -120,6 +121,8 @@ internal object Light {
val buttonShadowColor = Color(0xFF000000)
val screenTitleColor = Color(0xFF040404)
val welcomeAnimationColor = Color(0xFF231F20)
}
internal val DarkColorPalette = darkColorScheme(
@ -167,7 +170,8 @@ internal val DarkExtendedColorPalette = ExtendedColors(
disabledButtonColor = Dark.disabledButtonColor,
reference = Dark.reference,
buttonShadowColor = Dark.buttonShadowColor,
screenTitleColor = Dark.screenTitleColor
screenTitleColor = Dark.screenTitleColor,
welcomeAnimationColor = Dark.welcomeAnimationColor
)
internal val LightExtendedColorPalette = ExtendedColors(
@ -194,6 +198,7 @@ internal val LightExtendedColorPalette = ExtendedColors(
reference = Light.reference,
buttonShadowColor = Light.buttonShadowColor,
screenTitleColor = Light.screenTitleColor,
welcomeAnimationColor = Light.welcomeAnimationColor
)
@Suppress("CompositionLocalAllowlist")
@ -222,5 +227,6 @@ internal val LocalExtendedColors = staticCompositionLocalOf {
reference = Color.Unspecified,
buttonShadowColor = Color.Unspecified,
screenTitleColor = Color.Unspecified,
welcomeAnimationColor = Color.Unspecified,
)
}

View File

@ -0,0 +1,62 @@
package co.electriccoin.zcash.ui.design.util
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import co.electriccoin.zcash.spackle.Twig
import kotlin.math.roundToInt
/**
* This operation performs calculation of the screen height together with remembering its result for a further calls.
*
* @param cacheKey The cache defining key. Use a different one for recalculation.
*
* @return Wrapper object of the calculated heights in density pixels.
*/
@Composable
fun screenHeight(
cacheKey: Any = true
): ScreenHeight {
val density = LocalDensity.current
val configuration = LocalConfiguration.current
val statusBars = WindowInsets.statusBars
val navigationBars = WindowInsets.navigationBars
val cachedResult = remember(cacheKey) {
val contentHeightPx = with(density) { configuration.screenHeightDp.dp.roundToPx() }
Twig.debug { "Screen content height in pixels: $contentHeightPx" }
val statusBarHeight = statusBars.getTop(density).dp
Twig.debug { "Status bar height: $statusBarHeight" }
val navigationBarHeight = navigationBars.getBottom(density).dp
Twig.debug { "Navigation bar height: $navigationBarHeight" }
val contentHeight = (contentHeightPx / density.density.roundToInt()).dp
Twig.debug { "Screen content height in dps: $contentHeight" }
ScreenHeight(
contentHeight = contentHeight,
systemStatusBarHeight = statusBarHeight,
systemNavigationBarHeight = navigationBarHeight,
)
}
Twig.debug { "Screen total height: $cachedResult" }
return cachedResult
}
data class ScreenHeight(
val contentHeight: Dp,
val systemStatusBarHeight: Dp,
val systemNavigationBarHeight: Dp
) {
fun overallScreenHeight() = contentHeight + systemBarsHeight()
fun systemBarsHeight() = systemStatusBarHeight + systemNavigationBarHeight
}

View File

@ -27,11 +27,9 @@ class ShortOnboardingTestSetup(
fun DefaultContent() {
ZcashTheme {
ShortOnboarding(
isDebugMenuEnabled = false,
onCreateWallet = { onCreateWalletCallbackCount.incrementAndGet() },
onImportWallet = { onImportWalletCallbackCount.incrementAndGet() },
// We aren't testing this because it is for debug builds only.
onFixtureWallet = {}
onCreateWallet = { onCreateWalletCallbackCount.incrementAndGet() },
showWelcomeAnim = false, // It's fine to test the screen UI after the welcome animation
)
}
}

View File

@ -29,14 +29,17 @@ class ShortOnboardingViewTest : UiTestPrerequisites() {
fun layout() {
newTestSetup()
composeTestRule.onNodeWithText(getStringResource(R.string.onboarding_short_import_existing_wallet)).also {
composeTestRule.onNodeWithText(
text = getStringResource(R.string.onboarding_short_create_new_wallet),
ignoreCase = true
).also {
it.assertExists()
it.assertIsEnabled()
it.assertHasClickAction()
}
composeTestRule.onNodeWithText(
text = getStringResource(R.string.onboarding_short_create_new_wallet),
text = getStringResource(R.string.onboarding_short_import_existing_wallet),
ignoreCase = true
).also {
it.assertExists()
@ -66,7 +69,8 @@ class ShortOnboardingViewTest : UiTestPrerequisites() {
val testSetup = newTestSetup()
val newWalletButton = composeTestRule.onNodeWithText(
getStringResource(R.string.onboarding_short_import_existing_wallet)
text = getStringResource(R.string.onboarding_short_import_existing_wallet),
ignoreCase = true
)
newWalletButton.performClick()

View File

@ -74,7 +74,10 @@ class ScanViewBasicTest : UiTestPrerequisites() {
// Permission denied ui items (not visible):
composeTestRule.onNodeWithText(getStringResource(R.string.scan_settings_button)).also {
composeTestRule.onNodeWithText(
text = getStringResource(R.string.scan_settings_button),
ignoreCase = true
).also {
it.assertDoesNotExist()
}
}

View File

@ -4,6 +4,7 @@ import android.os.Bundle
import android.os.SystemClock
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.layout.fillMaxHeight
@ -92,6 +93,11 @@ class MainActivity : ComponentActivity() {
}
private fun setupUiContent() {
// Turn off the decor fitting system windows, which allows us to handle insets,
// including IME animations, and go edge-to-edge.
// This also sets up the initial system bar style based on the platform theme
enableEdgeToEdge()
setContent {
Override(configurationOverrideFlow) {
ZcashTheme {

View File

@ -113,7 +113,10 @@ fun LongNewWalletBackup(
onComplete = onComplete,
onBackToSeedPhrase = {
backupState.goToStage(BackupStage.ReviewSeed)
}
},
modifier = Modifier.padding(
bottom = ZcashTheme.dimens.spacingHuge
).fillMaxWidth()
)
}
) { paddingValues ->
@ -425,10 +428,11 @@ private fun BackupBottomNav(
onBack: () -> Unit,
selectedTestChoices: TestChoices,
onComplete: () -> Unit,
onBackToSeedPhrase: () -> Unit
onBackToSeedPhrase: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = Modifier.fillMaxWidth(),
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
when (backupStage) {

View File

@ -74,6 +74,9 @@ fun ShortNewWalletBackup(
bottomBar = {
ShortNewWalletBottomNav(
onComplete = onComplete,
modifier = Modifier.padding(
bottom = ZcashTheme.dimens.spacingHuge
).fillMaxWidth(),
)
}
) { paddingValues ->
@ -157,10 +160,11 @@ private fun CopySeedMenu(onCopyToClipboard: () -> Unit) {
@Suppress("LongParameterList")
@Composable
private fun ShortNewWalletBottomNav(
onComplete: () -> Unit
onComplete: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = Modifier.fillMaxWidth(),
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
PrimaryButton(onClick = onComplete, text = stringResource(R.string.new_wallet_short_button_finished))

View File

@ -295,19 +295,19 @@ sealed class SynchronizerError {
abstract fun getCauseMessage(): String?
class Critical(val error: Throwable?) : SynchronizerError() {
override fun getCauseMessage(): String? = error?.localizedMessage
override fun getCauseMessage(): String? = error?.message
}
class Processor(val error: Throwable?) : SynchronizerError() {
override fun getCauseMessage(): String? = error?.localizedMessage
override fun getCauseMessage(): String? = error?.message
}
class Submission(val error: Throwable?) : SynchronizerError() {
override fun getCauseMessage(): String? = error?.localizedMessage
override fun getCauseMessage(): String? = error?.message
}
class Setup(val error: Throwable?) : SynchronizerError() {
override fun getCauseMessage(): String? = error?.localizedMessage
override fun getCauseMessage(): String? = error?.message
}
class Chain(val x: BlockHeight, val y: BlockHeight) : SynchronizerError() {

View File

@ -56,6 +56,7 @@ internal fun WrapOnboarding(
if (!onboardingViewModel.isImporting.collectAsStateWithLifecycle().value) {
val onCreateWallet = {
walletViewModel.persistOnboardingState(OnboardingState.NEEDS_WARN)
onboardingViewModel.setShowWelcomeAnimation(false)
}
val onImportWallet = {
// In the case of the app currently being messed with by the robo test runner on
@ -72,6 +73,8 @@ internal fun WrapOnboarding(
} else {
onboardingViewModel.setIsImporting(true)
}
onboardingViewModel.setShowWelcomeAnimation(false)
}
val onFixtureWallet = {
@ -83,12 +86,16 @@ internal fun WrapOnboarding(
)
}
val showWelcomeAnimation = onboardingViewModel.showWelcomeAnimation.collectAsStateWithLifecycle().value
// TODO [#1003]: Clear unused alternative Onboarding screens
// TODO [#1003]: https://github.com/zcash/secant-android-wallet/issues/1003
if (ConfigurationEntries.IS_SHORT_ONBOARDING_UX.getValue(RemoteConfig.current)) {
ShortOnboarding(
isDebugMenuEnabled = isDebugMenuEnabled,
showWelcomeAnim = showWelcomeAnimation,
onImportWallet = onImportWallet,
onCreateWallet = onCreateWallet,
onFixtureWallet = onFixtureWallet
)
} else {
LongOnboarding(

View File

@ -2,42 +2,46 @@
package co.electriccoin.zcash.ui.screen.onboarding.view
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.LaunchedEffect
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.graphics.painter.ColorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.zIndex
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
import co.electriccoin.zcash.ui.design.component.Body
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.Header
import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.TertiaryButton
import co.electriccoin.zcash.ui.design.component.SecondaryButton
import co.electriccoin.zcash.ui.design.component.TitleLarge
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.util.ScreenHeight
import co.electriccoin.zcash.ui.design.util.screenHeight
import kotlinx.coroutines.delay
@Preview("ShortOnboarding")
@Composable
@ -45,76 +49,60 @@ private fun ShortOnboardingComposablePreview() {
ZcashTheme(darkTheme = false) {
GradientSurface {
ShortOnboarding(
isDebugMenuEnabled = false,
onImportWallet = {},
onCreateWallet = {},
onFixtureWallet = {}
showWelcomeAnim = false,
)
}
}
}
// TODO [#998]: Check and enhance screen dark mode
// TODO [#998]: https://github.com/zcash/secant-android-wallet/issues/998
// TODO [#1001]: Screens in landscape mode
// TODO [#1001]: https://github.com/zcash/secant-android-wallet/issues/1001
/**
* @param showWelcomeAnim Whether the welcome screen growing chart animation should be done or not.
* @param onImportWallet Callback when the user decides to import an existing wallet.
* @param onCreateWallet Callback when the user decides to create a new wallet.
*/
@Composable
fun ShortOnboarding(
isDebugMenuEnabled: Boolean,
showWelcomeAnim: Boolean,
onImportWallet: () -> Unit,
onCreateWallet: () -> Unit,
onFixtureWallet: () -> Unit
) {
Scaffold(
topBar = {
OnboardingTopAppBar(isDebugMenuEnabled, onFixtureWallet)
}
) { paddingValues ->
OnboardingMainContent(
onImportWallet = onImportWallet,
onCreateWallet = onCreateWallet,
modifier = Modifier.padding(
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingDefault,
start = ZcashTheme.dimens.spacingDefault,
end = ZcashTheme.dimens.spacingDefault
)
)
}
}
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun OnboardingTopAppBar(
isDebugMenuEnabled: Boolean,
onFixtureWallet: () -> Unit
) {
TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) },
actions = {
if (isDebugMenuEnabled) {
DebugMenu(onFixtureWallet)
}
}
)
}
@Composable
private fun DebugMenu(onFixtureWallet: () -> Unit) {
Column {
var expanded by rememberSaveable { mutableStateOf(false) }
IconButton(onClick = { expanded = true }) {
Icon(Icons.Default.MoreVert, contentDescription = null)
Scaffold { paddingValues ->
val screenHeight = screenHeight()
val (welcomeAnimVisibility, setWelcomeAnimVisibility) = rememberSaveable {
mutableStateOf(showWelcomeAnim)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
Column(
modifier = Modifier.verticalScroll(rememberScrollState())
) {
DropdownMenuItem(
text = { Text("Import wallet with fixture seed phrase") },
onClick = onFixtureWallet
)
Box(modifier = Modifier.fillMaxSize()) {
AnimatedImage(
screenHeight = screenHeight,
welcomeAnimVisibility = welcomeAnimVisibility,
setWelcomeAnimVisibility = setWelcomeAnimVisibility,
modifier = Modifier.zIndex(1f)
)
OnboardingMainContent(
onImportWallet = onImportWallet,
onCreateWallet = onCreateWallet,
modifier = Modifier
.padding(
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingHuge,
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingHuge,
start = ZcashTheme.dimens.spacingHuge,
end = ZcashTheme.dimens.spacingHuge
)
.height(screenHeight.contentHeight - paddingValues.calculateBottomPadding())
)
}
}
}
}
@ -126,19 +114,27 @@ private fun OnboardingMainContent(
modifier: Modifier = Modifier
) {
Column(
Modifier
.fillMaxSize()
.verticalScroll(
rememberScrollState()
)
.then(modifier),
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Header(text = stringResource(R.string.onboarding_short_header))
Image(
painterResource(id = R.drawable.zashi_logo_without_text),
stringResource(R.string.zcash_logo_onboarding_content_description),
Modifier
.height(ZcashTheme.dimens.zcashLogoHeight)
.width(ZcashTheme.dimens.zcashLogoWidth)
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingXlarge))
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
Body(text = stringResource(R.string.onboarding_short_information))
Image(
painterResource(id = R.drawable.zashi_text_logo),
""
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
TitleLarge(text = stringResource(R.string.onboarding_short_header), textAlign = TextAlign.Center)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
@ -154,9 +150,12 @@ private fun OnboardingMainContent(
outerPaddingValues = PaddingValues(
horizontal = ZcashTheme.dimens.spacingNone,
vertical = ZcashTheme.dimens.spacingSmall
)
),
)
TertiaryButton(
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
SecondaryButton(
onImportWallet,
stringResource(R.string.onboarding_short_import_existing_wallet),
outerPaddingValues = PaddingValues(
@ -166,3 +165,60 @@ private fun OnboardingMainContent(
)
}
}
@Composable
fun AnimatedImage(
screenHeight: ScreenHeight,
welcomeAnimVisibility: Boolean,
setWelcomeAnimVisibility: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
// TODO [#1002]: Welcome screen animation masking
// TODO [#1002]: https://github.com/zcash/secant-android-wallet/issues/1002
AnimatedVisibility(
visible = welcomeAnimVisibility,
exit = slideOutVertically(
targetOffsetY = { -it },
animationSpec = tween(AnimationConstants.ANIMATION_DURATION)
),
modifier = modifier
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.fillMaxHeight()) {
Image(
painter = ColorPainter(ZcashTheme.colors.welcomeAnimationColor),
contentScale = ContentScale.FillBounds,
modifier = Modifier
.fillMaxHeight()
.height(screenHeight.overallScreenHeight()),
contentDescription = null
)
Image(
painter = painterResource(id = R.drawable.chart_line),
contentScale = ContentScale.FillBounds,
contentDescription = null
)
}
Image(
painter = painterResource(id = R.drawable.logo_with_hi),
contentDescription = stringResource(R.string.zcash_logo_with_hi_text_content_description),
modifier = Modifier
.align(Alignment.TopCenter)
.fillMaxWidth()
.padding(top = screenHeight.systemStatusBarHeight + ZcashTheme.dimens.spacingHuge)
)
}
}
LaunchedEffect(Unit) {
delay(AnimationConstants.INITIAL_DELAY)
setWelcomeAnimVisibility(false)
}
}
object AnimationConstants {
const val ANIMATION_DURATION = 1250
const val INITIAL_DELAY: Long = 800
}

View File

@ -40,6 +40,11 @@ class OnboardingViewModel(
savedStateHandle[KEY_IS_IMPORTING] = isImporting
}
val showWelcomeAnimation = savedStateHandle.getStateFlow(KEY_SHOW_WELCOME_ANIMATION, true)
fun setShowWelcomeAnimation(setShowWelcomeAnimation: Boolean) {
savedStateHandle[KEY_SHOW_WELCOME_ANIMATION] = setShowWelcomeAnimation
}
init {
// viewModelScope is constructed with Dispatchers.Main.immediate, so this will
// update the save state as soon as a change occurs.
@ -51,5 +56,6 @@ class OnboardingViewModel(
companion object {
private const val KEY_STAGE = "stage" // $NON-NLS
private const val KEY_IS_IMPORTING = "is_importing" // $NON-NLS
private const val KEY_SHOW_WELCOME_ANIMATION = "show_welcome_animation" // $NON-NLS
}
}

View File

@ -138,7 +138,9 @@ private fun ReceiveContents(
PrimaryButton(
onClick = onAddressDetails,
text = stringResource(id = R.string.receive_see_address_details),
outerPaddingValues = PaddingValues(all = ZcashTheme.dimens.spacingNone)
outerPaddingValues = PaddingValues(
bottom = ZcashTheme.dimens.spacingHuge
)
)
}
}

View File

@ -175,7 +175,10 @@ fun RestoreWallet(
userWordList = userWordList,
parseResult = parseResult,
setTextState = { textState = it },
focusRequester = focusRequester
focusRequester = focusRequester,
modifier = Modifier.padding(
bottom = ZcashTheme.dimens.spacingHuge
).fillMaxWidth()
)
}
RestoreStage.Birthday -> {
@ -352,7 +355,9 @@ private fun RestoreSeedBottomBar(
// Hide the field once the user has completed the seed phrase; if they need the field back then
// the user can hit the clear button
if (!isSeedValid) {
Column(modifier) {
Column(
modifier = modifier
) {
Warn(
parseResult = parseResult,
modifier = Modifier

View File

@ -61,6 +61,9 @@ private fun SecurityWarningPreview() {
}
}
// TODO [#998]: Check and enhance screen dark mode
// TODO [#998]: https://github.com/zcash/secant-android-wallet/issues/998
@Composable
@Suppress("LongParameterList")
fun SecurityWarning(

View File

@ -76,8 +76,10 @@ fun Update(
modifier = Modifier
.fillMaxWidth()
.padding(
vertical = ZcashTheme.dimens.spacingDefault,
horizontal = ZcashTheme.dimens.spacingDefault
top = ZcashTheme.dimens.spacingDefault,
bottom = ZcashTheme.dimens.spacingHuge,
start = ZcashTheme.dimens.spacingDefault,
end = ZcashTheme.dimens.spacingDefault
)
)
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="83dp"
android:height="222dp"
android:viewportWidth="83"
android:viewportHeight="222">
<group>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M41.32 151l16.06 2.06v67.68L41.32 222v-31.22l-24.93-0.44v30.39l-16.4 1.26v-69.1L16.4 151v29.69l24.93-0.21V151Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M65.6 152.86L82 151v71l-16.4-1.26v-67.88Z"/>
</group>
<group>
<path
android:strokeColor="#FFFFFFFF"
android:strokeWidth="2"
android:strokeMiterLimit="10"
android:pathData="M45 78.57l12.9 26L24.24 89.5 81.6 59.54l-4.63-16.43L4.49 3.8l47.93 60.76 24.55-21.44L24.24 89.5"/>
</group>
</vector>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="169dp"
android:height="47dp"
android:viewportWidth="169"
android:viewportHeight="47">
<path
android:pathData="M51.32,2.33L60.18,0.99L75.78,46.08L65.64,47M65.64,46.92L61.16,35.39L45.94,34.49L41.46,46.11L34.3,46.93L51.31,2.33M48.63,28.25L58.39,28.26L53.1,14.84L48.62,28.24L48.63,28.25Z"
android:fillColor="#000000"/>
<path
android:pathData="M93.16,46.93C86.35,46.93 80.09,44.94 76.5,40.15L85.13,32.87C87.69,36.14 90.03,38.28 93.99,39.85L101.86,39.78C102.58,38.38 102.89,37.05 102.89,35.77C102.89,31.65 100.7,30.38 92.55,28.79C83.25,27.03 77.54,23.7 77.54,16.17C77.54,13.44 78.94,10.17 81.12,7.8L95.47,0.99C101.48,0.99 108.24,3.07 111.82,7.86L103.18,15.14C100.63,11.86 98.08,9.8 94.13,8.22H86.71C86.04,9.38 85.68,10.65 85.68,11.93C85.68,15.81 88.41,17.02 97.22,18.6C107.19,20.24 111.08,23.88 111.08,31.1C111.08,33.83 109.87,37.04 107.86,40.02L93.15,46.92L93.16,46.93Z"
android:fillColor="#000000"/>
<path
android:pathData="M142.35,0.99L152.88,2.33V46.11L142.35,46.92V26.74L126.02,26.45V46.1L115.28,46.92V2.22L126.02,0.99V20.19L142.35,20.06V0.99Z"
android:fillColor="#000000"/>
<path
android:pathData="M158.26,2.21L169,1V46.93L158.26,46.11V2.21Z"
android:fillColor="#000000"/>
<path
android:pathData="M13.43,36H34.02L30.4,45.87H0V43.15L21.49,10.98H1.79L4.47,2.05H34.91"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,12 @@
<!-- No icon placeholder to satisfy necessary splash screen icon and
be able to create custom welcome screen animation -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1px"
android:height="1px"
android:viewportWidth="1"
android:viewportHeight="1">
<!-- A very simple path data needs to be provided -->
<path
android:pathData="L10,0 L0,10 z"
android:fillColor="@color/splash_screen_background" />
</vector>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_screen_background">#FF000000</color>
<color name="splash_screen_background">#231F20</color>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_screen_background">#FFFFFFFF</color>
<color name="splash_screen_background">#231F20</color>
</resources>

View File

@ -2,7 +2,7 @@
<resources>
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/splash_screen_background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/zashi_logo</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/no_icon_splash_logo</item>
<item name="postSplashScreenTheme">@style/Theme.AppCompat.DayNight.NoActionBar</item>
</style>
</resources>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="390dp"
android:height="114dp"
android:viewportWidth="390"
android:viewportHeight="114">
<group>
<clip-path
android:pathData="M-0.02,-0.78h390v113.81h-390z"/>
<path
android:pathData="M65.2,47.76L0.01,113.01L-0,0.03L389.98,0L342.37,15.11L277.15,15.11L228.25,64.06L146.72,47.75L65.2,47.76Z"
android:fillColor="#231F20"/>
</group>
</vector>

View File

@ -19,8 +19,11 @@
<string name="onboarding_4_create_new_wallet">Create New Wallet</string>
<string name="onboarding_4_import_existing_wallet">Import an Existing Wallet</string>
<string name="onboarding_short_header">Its time to set up your no-frills wallet, powered by Zcash</string>
<string name="onboarding_short_header">A no-frills wallet for sending and receiving Zcash (ZEC)</string>
<string name="onboarding_short_information">We need to create a new wallet or restore an existing one. Select your path:</string>
<string name="onboarding_short_create_new_wallet">Create New Wallet</string>
<string name="onboarding_short_import_existing_wallet">Import an Existing Wallet</string>
<string name="onboarding_short_import_existing_wallet">Restore Existing Wallet</string>
<string name="zcash_logo_onboarding_content_description">Zcash logo in onboarding screen</string>
<string name="zcash_logo_with_hi_text_content_description">Zcash logo with text Hi</string>
</resources>

View File

@ -163,9 +163,10 @@ class ScreenshotTest : UiTestPrerequisites() {
if (ConfigurationEntries.IS_SHORT_ONBOARDING_UX.getValue(emptyConfiguration)) {
composeTestRule.onNodeWithText(
resContext.getString(
text = resContext.getString(
R.string.onboarding_short_import_existing_wallet
)
),
ignoreCase = true
).also {
it.assertExists()
it.performClick()