[#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:
parent
57a133a12c
commit
e871c4eb45
|
@ -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
|
||||
|
|
|
@ -139,7 +139,7 @@ dependencyResolutionManagement {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage", "MaxLineLength")
|
||||
@Suppress("MaxLineLength")
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
val accompanistPermissionsVersion = extra["ACCOMPANIST_PERMISSIONS_VERSION"].toString()
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -31,6 +31,7 @@ data class ExtendedColors(
|
|||
val disabledButtonTextColor: Color,
|
||||
val buttonShadowColor: Color,
|
||||
val screenTitleColor: Color,
|
||||
val welcomeAnimationColor: Color,
|
||||
) {
|
||||
@Composable
|
||||
fun surfaceGradient() = Brush.verticalGradient(
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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">It‘s 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>
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue