From fc05f95837203364b5cad03922632e872cd44f70 Mon Sep 17 00:00:00 2001 From: Ushie Date: Wed, 23 Apr 2025 20:09:05 +0300 Subject: [PATCH] feat: Improve update screen design (#2487) --- .../manager/ui/component/AppScaffold.kt | 38 ++++ .../ui/component/settings/Changelog.kt | 19 +- .../manager/ui/screen/UpdateScreen.kt | 187 +++++++----------- .../manager/ui/viewmodel/UpdateViewModel.kt | 13 +- 4 files changed, 118 insertions(+), 139 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/ui/component/AppScaffold.kt b/app/src/main/java/app/revanced/manager/ui/component/AppScaffold.kt index d63decf4..7bd8c276 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/AppScaffold.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/AppScaffold.kt @@ -81,3 +81,41 @@ fun AppTopBar( ) } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AppTopBar( + title: @Composable () -> Unit, + onBackClick: (() -> Unit)? = null, + backIcon: @Composable (() -> Unit) = @Composable { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource( + R.string.back + ) + ) + }, + actions: @Composable (RowScope.() -> Unit) = {}, + scrollBehavior: TopAppBarScrollBehavior? = null, + applyContainerColor: Boolean = false +) { + val containerColor = if (applyContainerColor) { + MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp) + } else { + Color.Unspecified + } + + TopAppBar( + title = title, + scrollBehavior = scrollBehavior, + navigationIcon = { + if (onBackClick != null) { + IconButton(onClick = onBackClick) { + backIcon() + } + } + }, + actions = actions, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = containerColor + ) + ) +} diff --git a/app/src/main/java/app/revanced/manager/ui/component/settings/Changelog.kt b/app/src/main/java/app/revanced/manager/ui/component/settings/Changelog.kt index af26e232..6a0f8d58 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/settings/Changelog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/settings/Changelog.kt @@ -7,10 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.CalendarToday -import androidx.compose.material.icons.outlined.Campaign -import androidx.compose.material.icons.outlined.FileDownload -import androidx.compose.material.icons.outlined.Sell +import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -37,28 +34,18 @@ fun Changelog( verticalAlignment = Alignment.CenterVertically ) { Icon( - imageVector = Icons.Outlined.Campaign, + imageVector = Icons.Outlined.NewReleases, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier .size(32.dp) ) Text( - version.removePrefix("v"), + "${version.removePrefix("v")} ($publishDate)", style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight(800)), color = MaterialTheme.colorScheme.primary, ) } - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier - .fillMaxWidth() - ) { - Tag( - Icons.Outlined.CalendarToday, - publishDate - ) - } } Markdown( markdown, diff --git a/app/src/main/java/app/revanced/manager/ui/screen/UpdateScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/UpdateScreen.kt index 21e5d3fc..068a75dd 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/UpdateScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/UpdateScreen.kt @@ -5,19 +5,17 @@ import androidx.compose.animation.core.spring import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Cancel +import androidx.compose.material.icons.outlined.InstallMobile import androidx.compose.material.icons.outlined.Update import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme @@ -28,16 +26,15 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import app.revanced.manager.BuildConfig import app.revanced.manager.R import app.revanced.manager.network.dto.ReVancedAsset import app.revanced.manager.ui.component.AppTopBar +import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton import app.revanced.manager.ui.component.settings.Changelog import app.revanced.manager.ui.viewmodel.UpdateViewModel import app.revanced.manager.ui.viewmodel.UpdateViewModel.State @@ -60,37 +57,81 @@ fun UpdateScreen( Scaffold( topBar = { AppTopBar( - title = stringResource(R.string.update), + title = { + Column { + Text(stringResource(vm.state.title)) + + if (vm.state == State.DOWNLOADING) { + Text( + text = "${vm.downloadedSize.div(1000000)} MB / ${ + vm.totalSize.div(1000000) + } MB (${vm.downloadProgress.times(100).toInt()}%)", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.outline + ) + } + } + }, scrollBehavior = scrollBehavior, onBackClick = onBackClick ) }, + floatingActionButton = { + val buttonConfig = when (vm.state) { + State.CAN_DOWNLOAD -> Triple( + { vm.downloadUpdate() }, + R.string.download, + Icons.Outlined.InstallMobile + ) + + State.DOWNLOADING -> Triple(onBackClick, R.string.cancel, Icons.Outlined.Cancel) + State.CAN_INSTALL -> Triple( + { vm.installUpdate() }, + R.string.install_app, + Icons.Outlined.InstallMobile + ) + + else -> null + } + + buttonConfig?.let { (onClick, textRes, icon) -> + HapticExtendedFloatingActionButton( + onClick = onClick::invoke, + icon = { Icon(icon, null) }, + text = { Text(stringResource(textRes)) } + ) + } + + }, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), ) { paddingValues -> - AnimatedVisibility(visible = vm.showInternetCheckDialog) { - MeteredDownloadConfirmationDialog( - onDismiss = { vm.showInternetCheckDialog = false }, - onDownloadAnyways = { vm.downloadUpdate(true) } - ) - } Column( modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(vertical = 16.dp, horizontal = 24.dp) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(32.dp) + .padding(paddingValues), ) { - Header( - vm.state, - vm.releaseInfo, - DownloadData(vm.downloadProgress, vm.downloadedSize, vm.totalSize) - ) - vm.releaseInfo?.let { changelog -> - HorizontalDivider() - Changelog(changelog) - } ?: Spacer(modifier = Modifier.weight(1f)) - Buttons(vm.state, vm::downloadUpdate, vm::installUpdate, onBackClick) + if (vm.state == State.DOWNLOADING) + LinearProgressIndicator( + progress = { vm.downloadProgress }, + modifier = Modifier.fillMaxWidth(), + ) + + AnimatedVisibility(visible = vm.showInternetCheckDialog) { + MeteredDownloadConfirmationDialog( + onDismiss = { vm.showInternetCheckDialog = false }, + onDownloadAnyways = { vm.downloadUpdate(true) } + ) + } + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(32.dp) + ) { + vm.releaseInfo?.let { changelog -> + Changelog(changelog) + } + } } } } @@ -123,58 +164,6 @@ private fun MeteredDownloadConfirmationDialog( ) } -@Composable -private fun Header(state: State, releaseInfo: ReVancedAsset?, downloadData: DownloadData) { - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - Text( - text = stringResource(state.title), - style = MaterialTheme.typography.headlineMedium - ) - if (state == State.CAN_DOWNLOAD) { - Column { - Text( - text = stringResource( - id = R.string.current_version, - BuildConfig.VERSION_NAME - ), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - releaseInfo?.version?.let { - Text( - text = stringResource( - R.string.new_version, - it.replace("v", "") - ), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - } - } else if (state == State.DOWNLOADING) { - LinearProgressIndicator( - progress = { downloadData.downloadProgress }, - modifier = Modifier.fillMaxWidth(), - ) - Text( - text = - "${downloadData.downloadedSize.div(1000000)} MB / ${ - downloadData.totalSize.div( - 1000000 - ) - } MB (${ - downloadData.downloadProgress.times( - 100 - ).toInt() - }%)", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.outline, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - } - } -} - @Composable private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) { val scrollState = rememberScrollState() @@ -205,40 +194,4 @@ private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) { publishDate = releaseInfo.createdAt.relativeTime(LocalContext.current) ) } -} - -@Composable -private fun Buttons( - state: State, - onDownloadClick: () -> Unit, - onInstallClick: () -> Unit, - onBackClick: () -> Unit -) { - Row(modifier = Modifier.fillMaxWidth()) { - if (state.showCancel) { - TextButton( - onClick = onBackClick, - ) { - Text(text = stringResource(R.string.cancel)) - } - } - Spacer(modifier = Modifier.weight(1f)) - if (state == State.CAN_DOWNLOAD) { - Button(onClick = onDownloadClick) { - Text(text = stringResource(R.string.update)) - } - } else if (state == State.CAN_INSTALL) { - Button( - onClick = onInstallClick - ) { - Text(text = stringResource(R.string.install_app)) - } - } - } -} - -data class DownloadData( - val downloadProgress: Float, - val downloadedSize: Long, - val totalSize: Long -) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt index 644563de..db31d654 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt @@ -9,6 +9,7 @@ import android.content.pm.PackageInstaller import androidx.annotation.StringRes import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.core.content.ContextCompat @@ -42,9 +43,9 @@ class UpdateViewModel( private val networkInfo: NetworkInfo by inject() private val fs: Filesystem by inject() - var downloadedSize by mutableStateOf(0L) + var downloadedSize by mutableLongStateOf(0L) private set - var totalSize by mutableStateOf(0L) + var totalSize by mutableLongStateOf(0L) private set val downloadProgress by derivedStateOf { if (downloadedSize == 0L || totalSize == 0L) return@derivedStateOf 0f @@ -89,7 +90,7 @@ class UpdateViewModel( totalSize = contentLength } } - state = State.CAN_INSTALL + installUpdate() } } } @@ -140,10 +141,10 @@ class UpdateViewModel( location.delete() } - enum class State(@StringRes val title: Int, val showCancel: Boolean = false) { + enum class State(@StringRes val title: Int) { CAN_DOWNLOAD(R.string.update_available), - DOWNLOADING(R.string.downloading_manager_update, true), - CAN_INSTALL(R.string.ready_to_install_update, true), + DOWNLOADING(R.string.downloading_manager_update), + CAN_INSTALL(R.string.ready_to_install_update), INSTALLING(R.string.installing_manager_update), FAILED(R.string.install_update_manager_failed), SUCCESS(R.string.update_completed)