feat: updater UI and code improvements (#1597)

This commit is contained in:
Ax333l 2024-01-05 23:05:02 +01:00 committed by GitHub
parent aa6e612fba
commit b7cb6b94f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 102 additions and 61 deletions

View file

@ -21,4 +21,5 @@ val viewModelModule = module {
viewModelOf(::DownloadsViewModel)
viewModelOf(::InstalledAppsViewModel)
viewModelOf(::InstalledAppInfoViewModel)
viewModelOf(::UpdatesSettingsViewModel)
}

View file

@ -107,7 +107,7 @@ class APIPatchBundle(name: String, id: Int, directory: File, endpoint: String) :
.getLatestRelease(repo)
.getOrThrow()
.let {
BundleAsset(it.metadata.tag, it.findAssetByType(mime).downloadUrl)
BundleAsset(it.version, it.findAssetByType(mime).downloadUrl)
}
}

View file

@ -1,8 +1,10 @@
package app.revanced.manager.network.api
import android.os.Build
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.network.dto.ReVancedRelease
import app.revanced.manager.network.service.ReVancedService
import app.revanced.manager.network.utils.getOrThrow
import app.revanced.manager.network.utils.transform
class ReVancedAPI(
@ -13,12 +15,20 @@ class ReVancedAPI(
suspend fun getContributors() = service.getContributors(apiUrl()).transform { it.repositories }
suspend fun getLatestRelease(name: String) = service.getLatestRelease(apiUrl(), name).transform { it.release }
suspend fun getLatestRelease(name: String) =
service.getLatestRelease(apiUrl(), name).transform { it.release }
suspend fun getReleases(name: String) = service.getReleases(apiUrl(), name).transform { it.releases }
suspend fun getReleases(name: String) =
service.getReleases(apiUrl(), name).transform { it.releases }
suspend fun getAppUpdate() =
getLatestRelease("revanced-manager")
.getOrThrow()
.takeIf { it.version != Build.VERSION.RELEASE }
companion object Extensions {
fun ReVancedRelease.findAssetByType(mime: String) = assets.singleOrNull { it.contentType == mime } ?: throw MissingAssetException(mime)
fun ReVancedRelease.findAssetByType(mime: String) =
assets.singleOrNull { it.contentType == mime } ?: throw MissingAssetException(mime)
}
}

View file

@ -17,7 +17,9 @@ data class ReVancedReleases(
data class ReVancedRelease(
val metadata: ReVancedReleaseMeta,
val assets: List<Asset>
)
) {
val version get() = metadata.tag
}
@Serializable
data class ReVancedReleaseMeta(

View file

@ -14,6 +14,7 @@ import kotlinx.coroutines.launch
@Composable
fun BooleanItem(
modifier: Modifier = Modifier,
preference: Preference<Boolean>,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
@StringRes headline: Int,
@ -22,6 +23,7 @@ fun BooleanItem(
val value by preference.getAsState()
BooleanItem(
modifier = modifier,
value = value,
onValueChange = { coroutineScope.launch { preference.update(it) } },
headline = headline,
@ -31,12 +33,15 @@ fun BooleanItem(
@Composable
fun BooleanItem(
modifier: Modifier = Modifier,
value: Boolean,
onValueChange: (Boolean) -> Unit,
@StringRes headline: Int,
@StringRes description: Int
) = SettingsListItem(
modifier = Modifier.clickable { onValueChange(!value) },
modifier = Modifier
.clickable { onValueChange(!value) }
.then(modifier),
headlineContent = stringResource(headline),
supportingContent = stringResource(description),
trailingContent = {

View file

@ -3,22 +3,24 @@ package app.revanced.manager.ui.screen.settings.update
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
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.filled.Update
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.revanced.manager.R
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.component.settings.BooleanItem
import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.viewmodel.UpdatesSettingsViewModel
import kotlinx.coroutines.launch
import org.koin.androidx.compose.getViewModel
import org.koin.compose.rememberKoinInject
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -26,25 +28,9 @@ fun UpdatesSettingsScreen(
onBackClick: () -> Unit,
onChangelogClick: () -> Unit,
onUpdateClick: () -> Unit,
vm: UpdatesSettingsViewModel = getViewModel(),
) {
val listItems = listOf(
Triple(
stringResource(R.string.update_channel),
stringResource(R.string.update_channel_description),
third = { /*TODO*/ }
),
Triple(
stringResource(R.string.update_notifications),
stringResource(R.string.update_notifications_description),
third = { /*TODO*/ }
),
Triple(
stringResource(R.string.changelog),
stringResource(R.string.changelog_description),
third = onChangelogClick
),
)
val coroutineScope = rememberCoroutineScope()
Scaffold(
topBar = {
@ -60,22 +46,29 @@ fun UpdatesSettingsScreen(
.padding(paddingValues)
.verticalScroll(rememberScrollState())
) {
NotificationCard(
text = stringResource(R.string.update_notification),
icon = Icons.Default.Update,
primaryAction = onUpdateClick
SettingsListItem(
modifier = Modifier.clickable {
coroutineScope.launch {
if (vm.checkForUpdates()) onUpdateClick()
}
},
headlineContent = stringResource(R.string.manual_update_check),
supportingContent = stringResource(R.string.manual_update_check_description)
)
listItems.forEach { (title, description, onClick) ->
SettingsListItem(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.clickable { onClick() },
headlineContent = title,
supportingContent = description
SettingsListItem(
modifier = Modifier.clickable(onClick = onChangelogClick),
headlineContent = stringResource(R.string.changelog),
supportingContent = stringResource(
R.string.changelog_description
)
}
)
BooleanItem(
preference = vm.managerAutoUpdates,
headline = R.string.update_checking_manager,
description = R.string.update_checking_manager_description
)
}
}
}

View file

@ -25,7 +25,7 @@ class ChangelogsViewModel(
uiSafe(app, R.string.changelog_download_fail, "Failed to download changelog") {
changelogs = api.getReleases("revanced-manager").getOrNull().orEmpty().map { release ->
Changelog(
release.metadata.tag,
release.version,
release.findAssetByType(APK_MIMETYPE).downloadCount,
release.metadata.publishedAt,
release.metadata.body

View file

@ -27,8 +27,11 @@ import app.revanced.manager.network.utils.getOrThrow
import app.revanced.manager.ui.theme.Theme
import app.revanced.manager.util.tag
import app.revanced.manager.util.toast
import app.revanced.manager.util.uiSafe
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@ -55,13 +58,8 @@ class MainViewModel(
private suspend fun checkForManagerUpdates() {
if (!prefs.managerAutoUpdates.get() || !networkInfo.isConnected()) return
try {
reVancedAPI.getLatestRelease("revanced-manager").getOrThrow().let { release ->
updatedManagerVersion = release.metadata.tag.takeIf { it != Build.VERSION.RELEASE }
}
} catch (e: Exception) {
app.toast(app.getString(R.string.failed_to_check_updates))
Log.e(tag, "Failed to check for updates", e)
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
updatedManagerVersion = reVancedAPI.getAppUpdate()?.version
}
}

View file

@ -16,6 +16,7 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType
@ -47,6 +48,7 @@ class UpdateViewModel(
private val http: HttpService by inject()
private val pm: PM by inject()
private val networkInfo: NetworkInfo by inject()
private val fs: Filesystem by inject()
var downloadedSize by mutableStateOf(0L)
private set
@ -65,17 +67,16 @@ class UpdateViewModel(
var changelog: Changelog? by mutableStateOf(null)
private val location = File.createTempFile("updater", ".apk", app.cacheDir)
private val location = fs.tempDir.resolve("updater.apk")
private var release: ReVancedRelease? = null
private val job = viewModelScope.launch {
uiSafe(app, R.string.download_manager_failed, "Failed to download ReVanced Manager") {
withContext(Dispatchers.IO) {
val response = reVancedAPI
.getLatestRelease("revanced-manager")
.getOrThrow()
val response = reVancedAPI.getAppUpdate() ?: throw Exception("No update available")
release = response
changelog = Changelog(
response.metadata.tag,
response.version,
response.findAssetByType(APK_MIMETYPE).downloadCount,
response.metadata.publishedAt,
response.metadata.body

View file

@ -0,0 +1,30 @@
package app.revanced.manager.ui.viewmodel
import android.app.Application
import androidx.lifecycle.ViewModel
import app.revanced.manager.R
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.util.toast
import app.revanced.manager.util.uiSafe
class UpdatesSettingsViewModel(
prefs: PreferencesManager,
private val app: Application,
private val reVancedAPI: ReVancedAPI,
) : ViewModel() {
val managerAutoUpdates = prefs.managerAutoUpdates
suspend fun checkForUpdates(): Boolean {
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
app.toast(app.getString(R.string.update_check))
if (reVancedAPI.getAppUpdate() == null)
app.toast(app.getString(R.string.no_update_available))
else
return true
}
return false
}
}

View file

@ -281,11 +281,10 @@
<string name="ready_to_install_update">Ready to install update</string>
<string name="update_completed">Update installed</string>
<string name="install_update_manager_failed">Failed to install update</string>
<string name="update_notification">A minor update for ReVanced Manager is available. Click here to update and get the latest features and fixes!</string>
<string name="update_channel">Update channel</string>
<string name="update_channel_description">Stable</string>
<string name="update_notifications">Update notifications</string>
<string name="update_notifications_description">Dialog on app launch + badges</string>
<string name="manual_update_check">Check for updates</string>
<string name="manual_update_check_description">Manually check for updates</string>
<string name="update_checking_manager">Update checking</string>
<string name="update_checking_manager_description">Check for new versions of ReVanced Manager when the application starts</string>
<string name="changelog">Changelog</string>
<string name="changelog_loading">Loading changelog</string>
<string name="changelog_download_fail">Failed to download changelog: %s</string>
@ -306,7 +305,9 @@
<string name="invalid_date">Invalid date</string>
<string name="disable_battery_optimization">Disable battery optimization</string>
<string name="failed_to_check_updates">Failed to check for updates</string>
<string name="failed_to_check_updates">Failed to check for updates: %s</string>
<string name="no_update_available">No update available</string>
<string name="update_check">Checking for updates…</string>
<string name="dismiss_temporary">Not now</string>
<string name="update_available_dialog_title">New update available</string>
<string name="update_available_dialog_description">A new version (%s) is available for download.</string>