Skip to content

Commit fc05f95

Browse files
authored
feat: Improve update screen design (#2487)
1 parent d5c63ea commit fc05f95

File tree

4 files changed

+118
-139
lines changed

4 files changed

+118
-139
lines changed

app/src/main/java/app/revanced/manager/ui/component/AppScaffold.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,41 @@ fun AppTopBar(
8181
)
8282
}
8383

84+
@OptIn(ExperimentalMaterial3Api::class)
85+
@Composable
86+
fun AppTopBar(
87+
title: @Composable () -> Unit,
88+
onBackClick: (() -> Unit)? = null,
89+
backIcon: @Composable (() -> Unit) = @Composable {
90+
Icon(
91+
imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(
92+
R.string.back
93+
)
94+
)
95+
},
96+
actions: @Composable (RowScope.() -> Unit) = {},
97+
scrollBehavior: TopAppBarScrollBehavior? = null,
98+
applyContainerColor: Boolean = false
99+
) {
100+
val containerColor = if (applyContainerColor) {
101+
MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
102+
} else {
103+
Color.Unspecified
104+
}
105+
106+
TopAppBar(
107+
title = title,
108+
scrollBehavior = scrollBehavior,
109+
navigationIcon = {
110+
if (onBackClick != null) {
111+
IconButton(onClick = onBackClick) {
112+
backIcon()
113+
}
114+
}
115+
},
116+
actions = actions,
117+
colors = TopAppBarDefaults.topAppBarColors(
118+
containerColor = containerColor
119+
)
120+
)
121+
}

app/src/main/java/app/revanced/manager/ui/component/settings/Changelog.kt

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
77
import androidx.compose.foundation.layout.padding
88
import androidx.compose.foundation.layout.size
99
import androidx.compose.material.icons.Icons
10-
import androidx.compose.material.icons.outlined.CalendarToday
11-
import androidx.compose.material.icons.outlined.Campaign
12-
import androidx.compose.material.icons.outlined.FileDownload
13-
import androidx.compose.material.icons.outlined.Sell
10+
import androidx.compose.material.icons.outlined.NewReleases
1411
import androidx.compose.material3.Icon
1512
import androidx.compose.material3.MaterialTheme
1613
import androidx.compose.material3.Text
@@ -37,28 +34,18 @@ fun Changelog(
3734
verticalAlignment = Alignment.CenterVertically
3835
) {
3936
Icon(
40-
imageVector = Icons.Outlined.Campaign,
37+
imageVector = Icons.Outlined.NewReleases,
4138
contentDescription = null,
4239
tint = MaterialTheme.colorScheme.primary,
4340
modifier = Modifier
4441
.size(32.dp)
4542
)
4643
Text(
47-
version.removePrefix("v"),
44+
"${version.removePrefix("v")} ($publishDate)",
4845
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight(800)),
4946
color = MaterialTheme.colorScheme.primary,
5047
)
5148
}
52-
Row(
53-
horizontalArrangement = Arrangement.spacedBy(16.dp),
54-
modifier = Modifier
55-
.fillMaxWidth()
56-
) {
57-
Tag(
58-
Icons.Outlined.CalendarToday,
59-
publishDate
60-
)
61-
}
6249
}
6350
Markdown(
6451
markdown,

app/src/main/java/app/revanced/manager/ui/screen/UpdateScreen.kt

Lines changed: 70 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,17 @@ import androidx.compose.animation.core.spring
55
import androidx.compose.foundation.layout.Arrangement
66
import androidx.compose.foundation.layout.Column
77
import androidx.compose.foundation.layout.ColumnScope
8-
import androidx.compose.foundation.layout.Row
9-
import androidx.compose.foundation.layout.Spacer
108
import androidx.compose.foundation.layout.fillMaxSize
119
import androidx.compose.foundation.layout.fillMaxWidth
1210
import androidx.compose.foundation.layout.padding
1311
import androidx.compose.foundation.rememberScrollState
1412
import androidx.compose.foundation.verticalScroll
1513
import androidx.compose.material.icons.Icons
14+
import androidx.compose.material.icons.outlined.Cancel
15+
import androidx.compose.material.icons.outlined.InstallMobile
1616
import androidx.compose.material.icons.outlined.Update
1717
import androidx.compose.material3.AlertDialog
18-
import androidx.compose.material3.Button
1918
import androidx.compose.material3.ExperimentalMaterial3Api
20-
import androidx.compose.material3.HorizontalDivider
2119
import androidx.compose.material3.Icon
2220
import androidx.compose.material3.LinearProgressIndicator
2321
import androidx.compose.material3.MaterialTheme
@@ -28,16 +26,15 @@ import androidx.compose.material3.TopAppBarDefaults
2826
import androidx.compose.material3.rememberTopAppBarState
2927
import androidx.compose.runtime.Composable
3028
import androidx.compose.runtime.Stable
31-
import androidx.compose.ui.Alignment
3229
import androidx.compose.ui.Modifier
3330
import androidx.compose.ui.input.nestedscroll.nestedScroll
3431
import androidx.compose.ui.platform.LocalContext
3532
import androidx.compose.ui.res.stringResource
3633
import androidx.compose.ui.unit.dp
37-
import app.revanced.manager.BuildConfig
3834
import app.revanced.manager.R
3935
import app.revanced.manager.network.dto.ReVancedAsset
4036
import app.revanced.manager.ui.component.AppTopBar
37+
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
4138
import app.revanced.manager.ui.component.settings.Changelog
4239
import app.revanced.manager.ui.viewmodel.UpdateViewModel
4340
import app.revanced.manager.ui.viewmodel.UpdateViewModel.State
@@ -60,37 +57,81 @@ fun UpdateScreen(
6057
Scaffold(
6158
topBar = {
6259
AppTopBar(
63-
title = stringResource(R.string.update),
60+
title = {
61+
Column {
62+
Text(stringResource(vm.state.title))
63+
64+
if (vm.state == State.DOWNLOADING) {
65+
Text(
66+
text = "${vm.downloadedSize.div(1000000)} MB / ${
67+
vm.totalSize.div(1000000)
68+
} MB (${vm.downloadProgress.times(100).toInt()}%)",
69+
style = MaterialTheme.typography.bodySmall,
70+
color = MaterialTheme.colorScheme.outline
71+
)
72+
}
73+
}
74+
},
6475
scrollBehavior = scrollBehavior,
6576
onBackClick = onBackClick
6677
)
6778
},
79+
floatingActionButton = {
80+
val buttonConfig = when (vm.state) {
81+
State.CAN_DOWNLOAD -> Triple(
82+
{ vm.downloadUpdate() },
83+
R.string.download,
84+
Icons.Outlined.InstallMobile
85+
)
86+
87+
State.DOWNLOADING -> Triple(onBackClick, R.string.cancel, Icons.Outlined.Cancel)
88+
State.CAN_INSTALL -> Triple(
89+
{ vm.installUpdate() },
90+
R.string.install_app,
91+
Icons.Outlined.InstallMobile
92+
)
93+
94+
else -> null
95+
}
96+
97+
buttonConfig?.let { (onClick, textRes, icon) ->
98+
HapticExtendedFloatingActionButton(
99+
onClick = onClick::invoke,
100+
icon = { Icon(icon, null) },
101+
text = { Text(stringResource(textRes)) }
102+
)
103+
}
104+
105+
},
68106
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
69107
) { paddingValues ->
70-
AnimatedVisibility(visible = vm.showInternetCheckDialog) {
71-
MeteredDownloadConfirmationDialog(
72-
onDismiss = { vm.showInternetCheckDialog = false },
73-
onDownloadAnyways = { vm.downloadUpdate(true) }
74-
)
75-
}
76108
Column(
77109
modifier = Modifier
78-
.fillMaxSize()
79-
.padding(paddingValues)
80-
.padding(vertical = 16.dp, horizontal = 24.dp)
81-
.verticalScroll(rememberScrollState()),
82-
verticalArrangement = Arrangement.spacedBy(32.dp)
110+
.padding(paddingValues),
83111
) {
84-
Header(
85-
vm.state,
86-
vm.releaseInfo,
87-
DownloadData(vm.downloadProgress, vm.downloadedSize, vm.totalSize)
88-
)
89-
vm.releaseInfo?.let { changelog ->
90-
HorizontalDivider()
91-
Changelog(changelog)
92-
} ?: Spacer(modifier = Modifier.weight(1f))
93-
Buttons(vm.state, vm::downloadUpdate, vm::installUpdate, onBackClick)
112+
if (vm.state == State.DOWNLOADING)
113+
LinearProgressIndicator(
114+
progress = { vm.downloadProgress },
115+
modifier = Modifier.fillMaxWidth(),
116+
)
117+
118+
AnimatedVisibility(visible = vm.showInternetCheckDialog) {
119+
MeteredDownloadConfirmationDialog(
120+
onDismiss = { vm.showInternetCheckDialog = false },
121+
onDownloadAnyways = { vm.downloadUpdate(true) }
122+
)
123+
}
124+
Column(
125+
modifier = Modifier
126+
.fillMaxSize()
127+
.padding(16.dp)
128+
.verticalScroll(rememberScrollState()),
129+
verticalArrangement = Arrangement.spacedBy(32.dp)
130+
) {
131+
vm.releaseInfo?.let { changelog ->
132+
Changelog(changelog)
133+
}
134+
}
94135
}
95136
}
96137
}
@@ -123,58 +164,6 @@ private fun MeteredDownloadConfirmationDialog(
123164
)
124165
}
125166

126-
@Composable
127-
private fun Header(state: State, releaseInfo: ReVancedAsset?, downloadData: DownloadData) {
128-
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
129-
Text(
130-
text = stringResource(state.title),
131-
style = MaterialTheme.typography.headlineMedium
132-
)
133-
if (state == State.CAN_DOWNLOAD) {
134-
Column {
135-
Text(
136-
text = stringResource(
137-
id = R.string.current_version,
138-
BuildConfig.VERSION_NAME
139-
),
140-
style = MaterialTheme.typography.bodyMedium,
141-
color = MaterialTheme.colorScheme.onSurfaceVariant
142-
)
143-
releaseInfo?.version?.let {
144-
Text(
145-
text = stringResource(
146-
R.string.new_version,
147-
it.replace("v", "")
148-
),
149-
style = MaterialTheme.typography.bodyMedium,
150-
color = MaterialTheme.colorScheme.onSurfaceVariant
151-
)
152-
}
153-
}
154-
} else if (state == State.DOWNLOADING) {
155-
LinearProgressIndicator(
156-
progress = { downloadData.downloadProgress },
157-
modifier = Modifier.fillMaxWidth(),
158-
)
159-
Text(
160-
text =
161-
"${downloadData.downloadedSize.div(1000000)} MB / ${
162-
downloadData.totalSize.div(
163-
1000000
164-
)
165-
} MB (${
166-
downloadData.downloadProgress.times(
167-
100
168-
).toInt()
169-
}%)",
170-
style = MaterialTheme.typography.bodyMedium,
171-
color = MaterialTheme.colorScheme.outline,
172-
modifier = Modifier.align(Alignment.CenterHorizontally)
173-
)
174-
}
175-
}
176-
}
177-
178167
@Composable
179168
private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) {
180169
val scrollState = rememberScrollState()
@@ -205,40 +194,4 @@ private fun ColumnScope.Changelog(releaseInfo: ReVancedAsset) {
205194
publishDate = releaseInfo.createdAt.relativeTime(LocalContext.current)
206195
)
207196
}
208-
}
209-
210-
@Composable
211-
private fun Buttons(
212-
state: State,
213-
onDownloadClick: () -> Unit,
214-
onInstallClick: () -> Unit,
215-
onBackClick: () -> Unit
216-
) {
217-
Row(modifier = Modifier.fillMaxWidth()) {
218-
if (state.showCancel) {
219-
TextButton(
220-
onClick = onBackClick,
221-
) {
222-
Text(text = stringResource(R.string.cancel))
223-
}
224-
}
225-
Spacer(modifier = Modifier.weight(1f))
226-
if (state == State.CAN_DOWNLOAD) {
227-
Button(onClick = onDownloadClick) {
228-
Text(text = stringResource(R.string.update))
229-
}
230-
} else if (state == State.CAN_INSTALL) {
231-
Button(
232-
onClick = onInstallClick
233-
) {
234-
Text(text = stringResource(R.string.install_app))
235-
}
236-
}
237-
}
238-
}
239-
240-
data class DownloadData(
241-
val downloadProgress: Float,
242-
val downloadedSize: Long,
243-
val totalSize: Long
244-
)
197+
}

app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.content.pm.PackageInstaller
99
import androidx.annotation.StringRes
1010
import androidx.compose.runtime.derivedStateOf
1111
import androidx.compose.runtime.getValue
12+
import androidx.compose.runtime.mutableLongStateOf
1213
import androidx.compose.runtime.mutableStateOf
1314
import androidx.compose.runtime.setValue
1415
import androidx.core.content.ContextCompat
@@ -42,9 +43,9 @@ class UpdateViewModel(
4243
private val networkInfo: NetworkInfo by inject()
4344
private val fs: Filesystem by inject()
4445

45-
var downloadedSize by mutableStateOf(0L)
46+
var downloadedSize by mutableLongStateOf(0L)
4647
private set
47-
var totalSize by mutableStateOf(0L)
48+
var totalSize by mutableLongStateOf(0L)
4849
private set
4950
val downloadProgress by derivedStateOf {
5051
if (downloadedSize == 0L || totalSize == 0L) return@derivedStateOf 0f
@@ -89,7 +90,7 @@ class UpdateViewModel(
8990
totalSize = contentLength
9091
}
9192
}
92-
state = State.CAN_INSTALL
93+
installUpdate()
9394
}
9495
}
9596
}
@@ -140,10 +141,10 @@ class UpdateViewModel(
140141
location.delete()
141142
}
142143

143-
enum class State(@StringRes val title: Int, val showCancel: Boolean = false) {
144+
enum class State(@StringRes val title: Int) {
144145
CAN_DOWNLOAD(R.string.update_available),
145-
DOWNLOADING(R.string.downloading_manager_update, true),
146-
CAN_INSTALL(R.string.ready_to_install_update, true),
146+
DOWNLOADING(R.string.downloading_manager_update),
147+
CAN_INSTALL(R.string.ready_to_install_update),
147148
INSTALLING(R.string.installing_manager_update),
148149
FAILED(R.string.install_update_manager_failed),
149150
SUCCESS(R.string.update_completed)

0 commit comments

Comments
 (0)