mirror of
https://github.com/ReVanced/revanced-manager
synced 2026-04-25 17:15:36 +02:00
feat: Improve Updates settings screen
UI: - Use prereleases toggle now triggers an immediate refetch when changed - Card now shows manager version and release date below Backend: - Move getting app updates into ManagerUpdateRepository with caching - Store fetched data inside ManagerUpdateRepository regardless of update status
This commit is contained in:
@@ -1,22 +1,43 @@
|
||||
package app.revanced.manager.domain.repository
|
||||
|
||||
import app.revanced.manager.BuildConfig
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.network.dto.ReVancedAsset
|
||||
import app.revanced.manager.network.utils.getOrThrow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
|
||||
class ManagerUpdateRepository(
|
||||
private val reVancedAPI: ReVancedAPI
|
||||
) {
|
||||
private val _availableVersion = MutableStateFlow<String?>(null)
|
||||
val availableVersion = _availableVersion.asStateFlow()
|
||||
private val asset: ReVancedAsset? = null
|
||||
private val _releasedAt = MutableStateFlow<LocalDateTime?>(null)
|
||||
private val _version = MutableStateFlow<String?>(null)
|
||||
private val _hasUpdate = MutableStateFlow(false)
|
||||
|
||||
suspend fun refreshAvailableVersion(): String? {
|
||||
val version = reVancedAPI.getAppUpdate()?.version
|
||||
_availableVersion.value = version
|
||||
return version
|
||||
val releasedAt = _releasedAt.asStateFlow()
|
||||
val hasUpdate = _hasUpdate.asStateFlow()
|
||||
val version = _version.asStateFlow()
|
||||
|
||||
suspend fun refresh(): ReVancedAsset {
|
||||
val update = reVancedAPI.getLatestAppInfo().getOrThrow()
|
||||
|
||||
_releasedAt.value = update.createdAt
|
||||
_version.value = update.version
|
||||
_hasUpdate.value = update.version.removePrefix("v") != BuildConfig.VERSION_NAME
|
||||
|
||||
return update
|
||||
}
|
||||
|
||||
fun clearAvailableVersion() {
|
||||
_availableVersion.value = null
|
||||
suspend fun getUpdateOrNull(refetch: Boolean = false): ReVancedAsset? {
|
||||
val asset = if (refetch || asset == null) refresh() else null
|
||||
return asset.takeIf { _hasUpdate.value }
|
||||
}
|
||||
|
||||
fun clearState() {
|
||||
_releasedAt.value = null
|
||||
_version.value = null
|
||||
_hasUpdate.value = false
|
||||
}
|
||||
}
|
||||
@@ -43,9 +43,6 @@ class ReVancedAPI(
|
||||
|
||||
suspend fun getAnnouncements() = request<List<ReVancedAnnouncement>>("announcements")
|
||||
|
||||
suspend fun getAppUpdate() =
|
||||
getLatestAppInfo().getOrThrow().takeIf { it.version.removePrefix("v") != BuildConfig.VERSION_NAME }
|
||||
|
||||
suspend fun getLatestAppInfo() =
|
||||
request<ReVancedAsset>("manager${prefs.useManagerPrereleases.prereleaseString()}")
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -141,7 +140,8 @@ fun DashboardScreen(
|
||||
!suggestedVersionSafeguard
|
||||
}
|
||||
}
|
||||
val availableUpdate by vm.availableManagerUpdate.collectAsStateWithLifecycle()
|
||||
val hasUpdate by vm.hasUpdate.collectAsStateWithLifecycle()
|
||||
val updateVersion by vm.updateVersion.collectAsStateWithLifecycle()
|
||||
val androidContext = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
val logoPainter = rememberDrawablePainter(drawable = remember(resources) {
|
||||
@@ -207,12 +207,12 @@ fun DashboardScreen(
|
||||
}
|
||||
|
||||
var showUpdateDialog by rememberSaveable { mutableStateOf(true) }
|
||||
if (managerAutoUpdates && showUpdateDialog && showManagerUpdateDialogOnLaunch && availableUpdate != null) {
|
||||
if (managerAutoUpdates && showUpdateDialog && showManagerUpdateDialogOnLaunch && hasUpdate) {
|
||||
AvailableUpdateDialog(
|
||||
onDismiss = { showUpdateDialog = false },
|
||||
setShowManagerUpdateDialogOnLaunch = vm::setShowManagerUpdateDialogOnLaunch,
|
||||
onConfirm = onUpdateClick,
|
||||
newVersion = availableUpdate!!
|
||||
newVersion = updateVersion!!
|
||||
)
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ fun DashboardScreen(
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (availableUpdate != null) {
|
||||
if (updateVersion != null) {
|
||||
TooltipIconButton(
|
||||
onClick = onUpdateClick,
|
||||
tooltip = stringResource(R.string.update),
|
||||
|
||||
@@ -3,6 +3,7 @@ package app.revanced.manager.ui.screen.settings.update
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -18,7 +19,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Update
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.WorkOutline
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
@@ -55,7 +55,7 @@ import app.revanced.manager.ui.component.TooltipIconButton
|
||||
import app.revanced.manager.ui.component.settings.BooleanItem
|
||||
import app.revanced.manager.ui.component.settings.SafeguardBooleanItem
|
||||
import app.revanced.manager.ui.viewmodel.UpdatesSettingsViewModel
|
||||
import app.revanced.manager.util.toast
|
||||
import app.revanced.manager.util.relativeTime
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -69,10 +69,11 @@ fun UpdatesSettingsScreen(
|
||||
vm: UpdatesSettingsViewModel = koinViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var checkingForUpdate by remember { mutableStateOf(false) }
|
||||
val availableUpdate by vm.availableManagerUpdate.collectAsStateWithLifecycle()
|
||||
val managerVersion by vm.managerVersion.collectAsStateWithLifecycle()
|
||||
val hasUpdate by vm.hasUpdate.collectAsStateWithLifecycle()
|
||||
val updateReleasedAt by vm.updateReleasedAt.collectAsStateWithLifecycle()
|
||||
val scrollState = androidx.compose.foundation.rememberScrollState()
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(
|
||||
canScroll = {
|
||||
@@ -112,15 +113,14 @@ fun UpdatesSettingsScreen(
|
||||
enabled = !checkingForUpdate,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
if (availableUpdate != null) {
|
||||
if (hasUpdate) {
|
||||
onUpdateClick()
|
||||
return@launch
|
||||
}
|
||||
|
||||
checkingForUpdate = true
|
||||
try {
|
||||
val version = vm.checkForUpdates()
|
||||
if (!version.isNullOrEmpty()) onUpdateClick()
|
||||
if (vm.checkUpdates()) onUpdateClick()
|
||||
} finally {
|
||||
checkingForUpdate = false
|
||||
}
|
||||
@@ -144,7 +144,7 @@ fun UpdatesSettingsScreen(
|
||||
text = stringResource(
|
||||
when {
|
||||
checkingForUpdate -> R.string.update_check
|
||||
availableUpdate != null -> R.string.view_update
|
||||
hasUpdate -> R.string.view_update
|
||||
else -> R.string.manual_update_check
|
||||
}
|
||||
)
|
||||
@@ -164,7 +164,13 @@ fun UpdatesSettingsScreen(
|
||||
) {
|
||||
ListSection(
|
||||
title = stringResource(R.string.manager),
|
||||
leadingContent = { Icon(Icons.Outlined.WorkOutline, contentDescription = null, modifier = Modifier.size(18.dp)) }
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Outlined.WorkOutline,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
@@ -190,11 +196,27 @@ fun UpdatesSettingsScreen(
|
||||
.padding(start = 4.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
managerVersion?.let { version ->
|
||||
updateReleasedAt?.let {
|
||||
val releasedAt = it.relativeTime(LocalContext.current)
|
||||
|
||||
Text(
|
||||
text = "$version\u2002\u2022\u2002$releasedAt",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Button(
|
||||
@@ -242,6 +264,7 @@ fun UpdatesSettingsScreen(
|
||||
coroutineScope.launch {
|
||||
vm.useManagerPrereleases.update(value)
|
||||
vm.clearAvailableManagerUpdate()
|
||||
vm.checkUpdates(false)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -40,7 +40,8 @@ class DashboardViewModel(
|
||||
val bundleDownloadError = patchBundleRepository.apiOutageError
|
||||
private val contentResolver: ContentResolver = app.contentResolver
|
||||
|
||||
val availableManagerUpdate = managerUpdateRepository.availableVersion
|
||||
val hasUpdate = managerUpdateRepository.hasUpdate
|
||||
val updateVersion = managerUpdateRepository.version
|
||||
|
||||
val sourcesNotDownloaded = patchBundleRepository.bundleInfoFlow.map { it.isEmpty() }
|
||||
val sourceUpdatesAvailable = combine(
|
||||
@@ -76,7 +77,7 @@ class DashboardViewModel(
|
||||
if (!prefs.managerAutoUpdates.get()) return
|
||||
|
||||
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
|
||||
managerUpdateRepository.refreshAvailableVersion()
|
||||
managerUpdateRepository.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import app.revanced.manager.data.platform.Filesystem
|
||||
import app.revanced.manager.data.platform.NetworkInfo
|
||||
import app.revanced.manager.domain.repository.ChangelogSource
|
||||
import app.revanced.manager.domain.repository.ChangelogsRepository
|
||||
import app.revanced.manager.domain.repository.ManagerUpdateRepository
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.network.dto.ReVancedAsset
|
||||
import app.revanced.manager.network.dto.ReVancedAssetHistory
|
||||
@@ -47,10 +48,10 @@ class UpdateViewModel(
|
||||
private val downloadOnScreenEntry: Boolean
|
||||
) : ViewModel(), KoinComponent {
|
||||
private val app: Application by inject()
|
||||
private val reVancedAPI: ReVancedAPI by inject()
|
||||
private val http: HttpService by inject()
|
||||
private val networkInfo: NetworkInfo by inject()
|
||||
private val fs: Filesystem by inject()
|
||||
private val managerUpdateRepository: ManagerUpdateRepository = get()
|
||||
private val ackpineInstaller: PackageInstaller = get()
|
||||
|
||||
// TODO: save state to handle process death.
|
||||
@@ -85,7 +86,7 @@ class UpdateViewModel(
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
uiSafe(app, R.string.download_manager_failed, "Failed to download ReVanced Manager") {
|
||||
releaseInfo = reVancedAPI.getAppUpdate()
|
||||
releaseInfo = managerUpdateRepository.getUpdateOrNull()
|
||||
?: throw Exception("No update available")
|
||||
|
||||
if (downloadOnScreenEntry) {
|
||||
|
||||
@@ -18,22 +18,25 @@ class UpdatesSettingsViewModel(
|
||||
val managerAutoUpdates = prefs.managerAutoUpdates
|
||||
val showManagerUpdateDialogOnLaunch = prefs.showManagerUpdateDialogOnLaunch
|
||||
val useManagerPrereleases = prefs.useManagerPrereleases
|
||||
val availableManagerUpdate = managerUpdateRepository.availableVersion
|
||||
val managerVersion = managerUpdateRepository.version
|
||||
val updateReleasedAt = managerUpdateRepository.releasedAt
|
||||
val hasUpdate = managerUpdateRepository.hasUpdate
|
||||
|
||||
suspend fun checkForUpdates(): String? {
|
||||
var availableVersion: String? = null
|
||||
suspend fun checkUpdates(showToast: Boolean = true): Boolean {
|
||||
var hasUpdate = false
|
||||
|
||||
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
|
||||
availableVersion = managerUpdateRepository.refreshAvailableVersion()
|
||||
|
||||
if (availableVersion == null)
|
||||
if (managerUpdateRepository.getUpdateOrNull(true) != null) {
|
||||
hasUpdate = true
|
||||
} else if (showToast) {
|
||||
app.toast(app.getString(R.string.no_update_available))
|
||||
}
|
||||
}
|
||||
|
||||
return availableVersion
|
||||
return hasUpdate
|
||||
}
|
||||
|
||||
fun clearAvailableManagerUpdate() {
|
||||
managerUpdateRepository.clearAvailableVersion()
|
||||
managerUpdateRepository.clearState()
|
||||
}
|
||||
}
|
||||
@@ -491,6 +491,7 @@ It’s only compatible with these versions: %2$s</string>
|
||||
<string name="prerelease_title">Use pre-releases?</string>
|
||||
<string name="prereleases_warning">Pre-release versions may be unstable and contain bugs. You may experience crashes, data loss, or other unexpected issues.</string>
|
||||
<string name="changelog">View changelog</string>
|
||||
<string name="updated_ago">Updated %s</string>
|
||||
<string name="changelog_loading">Loading changelog…</string>
|
||||
<string name="changelog_download_fail">Couldn’t download changelog: %s</string>
|
||||
<string name="battery_optimization_notification">Battery optimizations must be turned off in order for ReVanced Manager to work correctly in the background</string>
|
||||
|
||||
Reference in New Issue
Block a user