mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-04-30 05:54:26 +02:00
feat: Improve update screen design (#2487)
This commit is contained in:
parent
d5c63ead26
commit
fc05f95837
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -7,10 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.CalendarToday
|
import androidx.compose.material.icons.outlined.NewReleases
|
||||||
import androidx.compose.material.icons.outlined.Campaign
|
|
||||||
import androidx.compose.material.icons.outlined.FileDownload
|
|
||||||
import androidx.compose.material.icons.outlined.Sell
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -37,28 +34,18 @@ fun Changelog(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Campaign,
|
imageVector = Icons.Outlined.NewReleases,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(32.dp)
|
.size(32.dp)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
version.removePrefix("v"),
|
"${version.removePrefix("v")} ($publishDate)",
|
||||||
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight(800)),
|
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight(800)),
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Tag(
|
|
||||||
Icons.Outlined.CalendarToday,
|
|
||||||
publishDate
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Markdown(
|
Markdown(
|
||||||
markdown,
|
markdown,
|
||||||
|
@ -5,19 +5,17 @@ import androidx.compose.animation.core.spring
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Cancel
|
||||||
|
import androidx.compose.material.icons.outlined.InstallMobile
|
||||||
import androidx.compose.material.icons.outlined.Update
|
import androidx.compose.material.icons.outlined.Update
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -28,16 +26,15 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.revanced.manager.BuildConfig
|
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.network.dto.ReVancedAsset
|
import app.revanced.manager.network.dto.ReVancedAsset
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
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.component.settings.Changelog
|
||||||
import app.revanced.manager.ui.viewmodel.UpdateViewModel
|
import app.revanced.manager.ui.viewmodel.UpdateViewModel
|
||||||
import app.revanced.manager.ui.viewmodel.UpdateViewModel.State
|
import app.revanced.manager.ui.viewmodel.UpdateViewModel.State
|
||||||
@ -60,37 +57,81 @@ fun UpdateScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppTopBar(
|
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,
|
scrollBehavior = scrollBehavior,
|
||||||
onBackClick = onBackClick
|
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),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
AnimatedVisibility(visible = vm.showInternetCheckDialog) {
|
|
||||||
MeteredDownloadConfirmationDialog(
|
|
||||||
onDismiss = { vm.showInternetCheckDialog = false },
|
|
||||||
onDownloadAnyways = { vm.downloadUpdate(true) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.padding(paddingValues),
|
||||||
.padding(paddingValues)
|
|
||||||
.padding(vertical = 16.dp, horizontal = 24.dp)
|
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(32.dp)
|
|
||||||
) {
|
) {
|
||||||
Header(
|
if (vm.state == State.DOWNLOADING)
|
||||||
vm.state,
|
LinearProgressIndicator(
|
||||||
vm.releaseInfo,
|
progress = { vm.downloadProgress },
|
||||||
DownloadData(vm.downloadProgress, vm.downloadedSize, vm.totalSize)
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
vm.releaseInfo?.let { changelog ->
|
|
||||||
HorizontalDivider()
|
AnimatedVisibility(visible = vm.showInternetCheckDialog) {
|
||||||
Changelog(changelog)
|
MeteredDownloadConfirmationDialog(
|
||||||
} ?: Spacer(modifier = Modifier.weight(1f))
|
onDismiss = { vm.showInternetCheckDialog = false },
|
||||||
Buttons(vm.state, vm::downloadUpdate, vm::installUpdate, onBackClick)
|
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
|
@Composable
|
||||||
private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) {
|
private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) {
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
@ -205,40 +194,4 @@ private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) {
|
|||||||
publishDate = releaseInfo.createdAt.relativeTime(LocalContext.current)
|
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
|
|
||||||
)
|
|
@ -9,6 +9,7 @@ import android.content.pm.PackageInstaller
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@ -42,9 +43,9 @@ class UpdateViewModel(
|
|||||||
private val networkInfo: NetworkInfo by inject()
|
private val networkInfo: NetworkInfo by inject()
|
||||||
private val fs: Filesystem by inject()
|
private val fs: Filesystem by inject()
|
||||||
|
|
||||||
var downloadedSize by mutableStateOf(0L)
|
var downloadedSize by mutableLongStateOf(0L)
|
||||||
private set
|
private set
|
||||||
var totalSize by mutableStateOf(0L)
|
var totalSize by mutableLongStateOf(0L)
|
||||||
private set
|
private set
|
||||||
val downloadProgress by derivedStateOf {
|
val downloadProgress by derivedStateOf {
|
||||||
if (downloadedSize == 0L || totalSize == 0L) return@derivedStateOf 0f
|
if (downloadedSize == 0L || totalSize == 0L) return@derivedStateOf 0f
|
||||||
@ -89,7 +90,7 @@ class UpdateViewModel(
|
|||||||
totalSize = contentLength
|
totalSize = contentLength
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state = State.CAN_INSTALL
|
installUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,10 +141,10 @@ class UpdateViewModel(
|
|||||||
location.delete()
|
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),
|
CAN_DOWNLOAD(R.string.update_available),
|
||||||
DOWNLOADING(R.string.downloading_manager_update, true),
|
DOWNLOADING(R.string.downloading_manager_update),
|
||||||
CAN_INSTALL(R.string.ready_to_install_update, true),
|
CAN_INSTALL(R.string.ready_to_install_update),
|
||||||
INSTALLING(R.string.installing_manager_update),
|
INSTALLING(R.string.installing_manager_update),
|
||||||
FAILED(R.string.install_update_manager_failed),
|
FAILED(R.string.install_update_manager_failed),
|
||||||
SUCCESS(R.string.update_completed)
|
SUCCESS(R.string.update_completed)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user