[#1338] Redesign Update-Available screen

- Closes  #1338
- Changelog update
This commit is contained in:
Honza Rychnovský 2024-05-07 16:57:45 +02:00 committed by GitHub
parent b235e0cc82
commit a97b71d922
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 218 additions and 76 deletions

View File

@ -16,6 +16,7 @@ directly impact users rather than highlighting other key architectural updates.*
### Changed
- We've improved the visibility logic of the little loader that is part of the Balances widget
- The App-Update screen UI has been reworked to align with the latest design guidelines
### Removed
- Concatenation of the messages on a multi-messages transaction has been removed and will be addressed using a new

View File

@ -245,6 +245,8 @@ abstract class PublishToGooglePlay @Inject constructor(
track,
Track().setReleases(
listOf(TrackRelease()
// TODO [#1440]: Provide a way to inject in-app-update information
// TODO [#1440]: https://github.com/Electric-Coin-Company/zashi-android/issues/1440
.setName(versionName)
.setVersionCodes(bundleVersionCodes)
.setStatus(status)

View File

@ -161,7 +161,7 @@ ANDROIDX_CAMERA_VERSION=1.3.2
ANDROIDX_COMPOSE_COMPILER_VERSION=1.5.11
ANDROIDX_COMPOSE_MATERIAL3_VERSION=1.2.1
ANDROIDX_COMPOSE_MATERIAL_ICONS_VERSION=1.6.5
ANDROIDX_COMPOSE_VERSION=1.6.5
ANDROIDX_COMPOSE_VERSION=1.6.6
ANDROIDX_CONSTRAINTLAYOUT_VERSION=1.0.1
ANDROIDX_CORE_VERSION=1.12.0
ANDROIDX_ESPRESSO_VERSION=3.5.1

View File

@ -278,6 +278,7 @@ fun Reference(
fontWeight: FontWeight = FontWeight.SemiBold,
textAlign: TextAlign = TextAlign.Center,
textStyle: TextStyle = ZcashTheme.typography.primary.bodyLarge,
color: Color = ZcashTheme.colors.reference,
imageVector: ImageVector? = null,
imageContentDescription: String? = null
) {
@ -303,7 +304,7 @@ fun Reference(
style =
textStyle.merge(
TextStyle(
color = ZcashTheme.colors.reference,
color = color,
textAlign = textAlign,
textDecoration = TextDecoration.Underline,
fontWeight = fontWeight

View File

@ -29,6 +29,7 @@ data class ExtendedColors(
val textFieldWarning: Color,
val textFieldFrame: Color,
val textDescription: Color,
val textDescriptionDark: Color,
val textPending: Color,
val layoutStroke: Color,
val overlay: Color,

View File

@ -32,6 +32,7 @@ internal object Dark {
val textFieldWarning = Color(0xFFF40202)
val textFieldHint = Color(0xFFB7B7B7)
val textDescription = Color(0xFF777777)
val textDescriptionDark = Color(0xFF4D4D4D)
val textProgress = Color(0xFF8B8A8A)
val aboutTextColor = Color(0xFF4E4E4E)
@ -98,6 +99,7 @@ internal object Light {
val textFieldHint = Color(0xFFB7B7B7)
val textChipIndex = Color(0xFFEE8592)
val textDescription = Color(0xFF777777)
val textDescriptionDark = Color(0xFF4D4D4D)
val textProgress = Color(0xFF8B8A8A)
val screenTitleColor = Color(0xFF040404)
@ -191,6 +193,7 @@ internal val DarkExtendedColorPalette =
textFieldWarning = Dark.textFieldWarning,
textFieldHint = Dark.textFieldHint,
textDescription = Dark.textDescription,
textDescriptionDark = Dark.textDescriptionDark,
textPending = Dark.textProgress,
layoutStroke = Dark.layoutStroke,
overlay = Dark.overlay,
@ -240,6 +243,7 @@ internal val LightExtendedColorPalette =
textFieldWarning = Light.textFieldWarning,
textFieldHint = Light.textFieldHint,
textDescription = Light.textDescription,
textDescriptionDark = Light.textDescriptionDark,
textPending = Light.textProgress,
layoutStroke = Light.layoutStroke,
overlay = Light.overlay,
@ -291,6 +295,7 @@ internal val LocalExtendedColors =
textFieldWarning = Color.Unspecified,
textFieldFrame = Color.Unspecified,
textDescription = Color.Unspecified,
textDescriptionDark = Color.Unspecified,
textPending = Color.Unspecified,
layoutStroke = Color.Unspecified,
overlay = Color.Unspecified,

View File

@ -194,6 +194,7 @@ data class ExtendedTypography(
val transactionItemStyles: TransactionItemTextStyles,
val restoringTopAppBarStyle: TextStyle,
val deleteWalletWarnStyle: TextStyle,
val updateTitleStyle: TextStyle,
)
@Suppress("CompositionLocalAllowlist")
@ -377,6 +378,10 @@ val LocalExtendedTypography =
deleteWalletWarnStyle =
PrimaryTypography.bodyLarge.copy(
fontWeight = FontWeight.Bold
),
updateTitleStyle =
PrimaryTypography.titleLarge.copy(
fontWeight = FontWeight.Bold
)
)
}

View File

@ -7,6 +7,7 @@ import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.fixture.UpdateInfoFixture
import co.electriccoin.zcash.ui.integration.test.common.IntegrationTestingActivity
import co.electriccoin.zcash.ui.screen.update.AppUpdateChecker
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerMock
import co.electriccoin.zcash.ui.screen.update.model.UpdateInfo
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
import co.electriccoin.zcash.ui.screen.update.viewmodel.UpdateViewModel

View File

@ -6,6 +6,7 @@ import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.test.filters.MediumTest
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R
@ -147,6 +148,7 @@ class UpdateViewTest : UiTestPrerequisites() {
composeTestRule.onNodeWithText(getStringResource(R.string.update_link_text)).also {
it.assertExists()
it.performScrollTo()
it.performClick()
}

View File

@ -1,4 +1,4 @@
package co.electriccoin.zcash.ui.integration.test.screen.update.viewmodel
package co.electriccoin.zcash.ui.screen.update
import android.app.Activity
import android.content.Context
@ -6,7 +6,6 @@ import androidx.activity.ComponentActivity
import co.electriccoin.zcash.spackle.getPackageInfoCompat
import co.electriccoin.zcash.spackle.versionCodeCompat
import co.electriccoin.zcash.ui.fixture.UpdateInfoFixture
import co.electriccoin.zcash.ui.screen.update.AppUpdateChecker
import co.electriccoin.zcash.ui.screen.update.model.UpdateInfo
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
import com.google.android.play.core.appupdate.AppUpdateInfo
@ -26,13 +25,13 @@ class AppUpdateCheckerMock private constructor() : AppUpdateChecker {
fun new() = AppUpdateCheckerMock()
// used mostly for tests
// Used mostly for tests
val resultUpdateInfo =
UpdateInfoFixture.new(
appUpdateInfo = null,
state = UpdateState.Prepared,
priority = AppUpdateChecker.Priority.HIGH,
force = true
priority = AppUpdateChecker.Priority.LOW,
force = false
)
}
@ -52,7 +51,7 @@ class AppUpdateCheckerMock private constructor() : AppUpdateChecker {
val appUpdateInfoTask = fakeAppUpdateManager.appUpdateInfo
// to simulate a real-world situation
// To simulate a real-world situation
delay(100.milliseconds)
appUpdateInfoTask.addOnCompleteListener { infoTask ->
@ -83,8 +82,8 @@ class AppUpdateCheckerMock private constructor() : AppUpdateChecker {
appUpdateInfo: AppUpdateInfo
): Flow<Int> =
flow {
// to simulate a real-world situation
delay(100.milliseconds)
// To simulate a real-world situation
delay(2000.milliseconds)
emit(Activity.RESULT_OK)
}
}

View File

@ -7,22 +7,27 @@ 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.wrapContentHeight
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Update
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.Body
@ -30,7 +35,6 @@ import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.Reference
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
import co.electriccoin.zcash.ui.design.component.TertiaryButton
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.UpdateInfoFixture
import co.electriccoin.zcash.ui.screen.update.UpdateTag
@ -73,28 +77,21 @@ fun Update(
updateInfo,
onDownload,
onLater,
modifier =
Modifier
.fillMaxWidth()
.padding(
top = ZcashTheme.dimens.spacingDefault,
bottom = ZcashTheme.dimens.spacingHuge,
start = ZcashTheme.dimens.screenHorizontalSpacingBig,
end = ZcashTheme.dimens.screenHorizontalSpacingBig
)
modifier = Modifier.fillMaxWidth()
)
}
) { paddingValues ->
UpdateContentNormal(
onReference,
UpdateContentContent(
onReference = onReference,
updateInfo = updateInfo,
modifier =
Modifier
.fillMaxWidth()
.padding(
top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding(),
start = ZcashTheme.dimens.spacingDefault,
end = ZcashTheme.dimens.spacingDefault
start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
end = ZcashTheme.dimens.screenHorizontalSpacingRegular
)
)
}
@ -136,6 +133,7 @@ private fun UpdateTopAppBar(updateInfo: UpdateInfo) {
}
@Composable
@Suppress("LongMethod")
private fun UpdateBottomAppBar(
updateInfo: UpdateInfo,
onDownload: (state: UpdateState) -> Unit,
@ -146,73 +144,143 @@ private fun UpdateBottomAppBar(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
PrimaryButton(
onClick = { onDownload(UpdateState.Running) },
text = stringResource(R.string.update_download_button),
HorizontalDivider(
thickness = DividerDefaults.Thickness,
color = ZcashTheme.colors.dividerColor
)
Column(
modifier =
Modifier
.testTag(UpdateTag.BTN_DOWNLOAD)
.fillMaxWidth(),
enabled = updateInfo.state != UpdateState.Running,
outerPaddingValues = PaddingValues(all = ZcashTheme.dimens.spacingNone),
)
.padding(
top = ZcashTheme.dimens.spacingDefault,
bottom = ZcashTheme.dimens.spacingBig,
start = ZcashTheme.dimens.screenHorizontalSpacingBig,
end = ZcashTheme.dimens.screenHorizontalSpacingBig
),
horizontalAlignment = Alignment.CenterHorizontally
) {
PrimaryButton(
onClick = { onDownload(UpdateState.Running) },
text = stringResource(R.string.update_download_button),
modifier =
Modifier
.testTag(UpdateTag.BTN_DOWNLOAD)
.fillMaxWidth(),
enabled = updateInfo.state != UpdateState.Running,
outerPaddingValues = PaddingValues(all = ZcashTheme.dimens.spacingNone),
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
TertiaryButton(
onClick = onLater,
text =
stringResource(
updateInfo.isForce.let { force ->
if (force) {
R.string.update_later_disabled_button
if (updateInfo.isForce) {
Text(
text = stringResource(R.string.update_later_disabled_button),
textAlign = TextAlign.Center,
style = ZcashTheme.typography.primary.bodyLarge,
fontWeight = FontWeight.SemiBold,
modifier =
Modifier
.padding(all = ZcashTheme.dimens.spacingDefault)
.testTag(UpdateTag.BTN_LATER)
)
} else {
Reference(
text = stringResource(R.string.update_later_enabled_button),
onClick = {
if (updateInfo.state != UpdateState.Running) {
onLater()
} else {
R.string.update_later_enabled_button
// Keep current state
}
}
),
modifier = Modifier.testTag(UpdateTag.BTN_LATER),
enabled = !updateInfo.isForce && updateInfo.state != UpdateState.Running,
outerPaddingValues = PaddingValues(top = ZcashTheme.dimens.spacingSmall)
)
},
textAlign = TextAlign.Center,
modifier =
Modifier
.padding(all = ZcashTheme.dimens.spacingDefault)
.testTag(UpdateTag.BTN_LATER)
)
}
}
}
}
@Composable
private fun UpdateContentNormal(
@Suppress("LongMethod")
private fun UpdateContentContent(
onReference: () -> Unit,
modifier: Modifier = Modifier
updateInfo: UpdateInfo,
modifier: Modifier = Modifier,
) {
val appName = stringResource(id = R.string.app_name)
Column(
modifier = modifier,
modifier =
modifier.then(
Modifier
.fillMaxHeight()
.verticalScroll(
rememberScrollState()
)
),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Replace this placeholder graphic once this screen is being redesigned
@Suppress("MagicNumber")
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingBig))
Image(
imageVector = Icons.Filled.Update,
contentDescription = stringResource(id = R.string.update_image_content_description),
modifier = Modifier.fillMaxSize(0.45f)
imageVector =
if (updateInfo.isForce) {
ImageVector.vectorResource(R.drawable.ic_zashi_logo_update_required)
} else {
ImageVector.vectorResource(R.drawable.ic_zashi_logo_update_available)
},
contentDescription = stringResource(id = R.string.update_image_content_description)
)
Body(
text = stringResource(id = R.string.update_description),
modifier =
Modifier
.wrapContentHeight()
.align(Alignment.CenterHorizontally)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingBig))
Text(
text =
if (updateInfo.isForce) {
stringResource(id = R.string.update_title_required)
} else {
stringResource(id = R.string.update_title_available, appName)
},
style = ZcashTheme.extendedTypography.updateTitleStyle,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
Body(
text =
if (updateInfo.isForce) {
stringResource(id = R.string.update_description_required, appName)
} else {
stringResource(id = R.string.update_description_available, appName)
},
textAlign = TextAlign.Center,
color = ZcashTheme.colors.textDescriptionDark
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
Reference(
text = stringResource(id = R.string.update_link_text),
onClick = {
onReference()
if (updateInfo.state != UpdateState.Running) {
onReference()
} else {
// Keep current state
}
},
modifier =
Modifier
.wrapContentHeight()
.align(Alignment.CenterHorizontally)
.padding(all = ZcashTheme.dimens.spacingDefault),
fontWeight = FontWeight.Normal,
textStyle = ZcashTheme.typography.primary.bodyMedium,
textAlign = TextAlign.Center,
color = ZcashTheme.colors.textDescriptionDark,
modifier = Modifier.padding(all = ZcashTheme.dimens.spacingDefault)
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
}
}

View File

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="92dp"
android:height="118dp"
android:viewportWidth="92"
android:viewportHeight="118">
<path
android:pathData="M50.11,86.3L64.11,114.47L27.61,98.12L89.79,65.69L84.77,47.89L6.21,5.29L58.15,71.12L84.77,47.89L27.61,98.12"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
<group>
<clip-path
android:pathData="M70.58,84h14.1v18h-14.1z"/>
<path
android:strokeWidth="1"
android:pathData="M80.41,93.27H83.74L77.63,101.24L71.52,93.27H74.85V90.49H80.41V93.27Z"
android:fillColor="#000000"
android:strokeColor="#000000"/>
<path
android:pathData="M79.87,84.93H75.4"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="square"/>
<path
android:pathData="M79.87,87.71H75.4"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="square"/>
</group>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="92dp"
android:height="118dp"
android:viewportWidth="92"
android:viewportHeight="118">
<path
android:pathData="M50.11,86.02L64.11,114.18L27.61,97.84L89.79,65.4L84.77,47.61L6.21,5L58.15,70.83L84.77,47.61L27.61,97.84"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
<path
android:pathData="M77.86,102.59C72.34,102.59 67.86,98.11 67.86,92.59C67.86,87.06 72.34,82.59 77.86,82.59C83.38,82.59 87.86,87.06 87.86,92.59C87.86,98.11 83.38,102.59 77.86,102.59ZM77.86,87.59C77.59,87.59 77.34,87.69 77.15,87.88C76.96,88.07 76.86,88.32 76.86,88.59V93.59C76.86,93.85 76.96,94.11 77.15,94.29C77.34,94.48 77.59,94.59 77.86,94.59C78.12,94.59 78.38,94.48 78.57,94.29C78.75,94.11 78.86,93.85 78.86,93.59V88.59C78.86,88.32 78.75,88.07 78.57,87.88C78.38,87.69 78.12,87.59 77.86,87.59ZM77.86,97.59C78.12,97.59 78.38,97.48 78.57,97.29C78.75,97.11 78.86,96.85 78.86,96.59C78.86,96.32 78.75,96.07 78.57,95.88C78.38,95.69 78.12,95.59 77.86,95.59C77.59,95.59 77.34,95.69 77.15,95.88C76.96,96.07 76.86,96.32 76.86,96.59C76.86,96.85 76.96,97.11 77.15,97.29C77.34,97.48 77.59,97.59 77.86,97.59Z"
android:fillColor="#000000"/>
</vector>

View File

@ -1,12 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="update_header">Update available</string>
<string name="update_critical_header">Critical update required!</string>
<string name="update_critical_header">Update required</string>
<string name="update_image_content_description"></string>
<string name="update_description">There is a new version of the app available.</string>
<string name="update_title_available"><xliff:g id="app_name" example="Zcash">%1$s</xliff:g> here.</string>
<string name="update_title_required">It\'s not you, it\'s me.</string>
<string name="update_description_required">
There is a required update for <xliff:g id="app_name" example="Zcash">%1$s</xliff:g> that makes major
improvements to performance and/or security.
</string>
<string name="update_description_available">
There is a new version of <xliff:g id="app_name" example="Zcash">%1$s</xliff:g> that makes minor updates to
improve performance and/or security.\n\nPlease take a moment to update to the latest version.
</string>
<string name="update_link_text">Learn more about this update here.</string>
<string name="update_download_button">Download Update</string>
<string name="update_download_button">Update</string>
<string name="update_later_enabled_button">Remind me later</string>
<string name="update_later_disabled_button">This can not be skipped.</string>
<string name="update_later_disabled_button">(required)</string>
<string name="update_unable_to_open_play_store">Unable to launch Google Play store app.</string>
</resources>