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(::DownloadsViewModel)
viewModelOf(::InstalledAppsViewModel) viewModelOf(::InstalledAppsViewModel)
viewModelOf(::InstalledAppInfoViewModel) viewModelOf(::InstalledAppInfoViewModel)
viewModelOf(::UpdatesSettingsViewModel)
} }

View file

@ -107,7 +107,7 @@ class APIPatchBundle(name: String, id: Int, directory: File, endpoint: String) :
.getLatestRelease(repo) .getLatestRelease(repo)
.getOrThrow() .getOrThrow()
.let { .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 package app.revanced.manager.network.api
import android.os.Build
import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.network.dto.ReVancedRelease import app.revanced.manager.network.dto.ReVancedRelease
import app.revanced.manager.network.service.ReVancedService import app.revanced.manager.network.service.ReVancedService
import app.revanced.manager.network.utils.getOrThrow
import app.revanced.manager.network.utils.transform import app.revanced.manager.network.utils.transform
class ReVancedAPI( class ReVancedAPI(
@ -13,12 +15,20 @@ class ReVancedAPI(
suspend fun getContributors() = service.getContributors(apiUrl()).transform { it.repositories } 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 { 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( data class ReVancedRelease(
val metadata: ReVancedReleaseMeta, val metadata: ReVancedReleaseMeta,
val assets: List<Asset> val assets: List<Asset>
) ) {
val version get() = metadata.tag
}
@Serializable @Serializable
data class ReVancedReleaseMeta( data class ReVancedReleaseMeta(

View file

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

View file

@ -3,22 +3,24 @@ package app.revanced.manager.ui.screen.settings.update
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
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.filled.Update
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.revanced.manager.R 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.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.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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -26,25 +28,9 @@ fun UpdatesSettingsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
onChangelogClick: () -> Unit, onChangelogClick: () -> Unit,
onUpdateClick: () -> Unit, onUpdateClick: () -> Unit,
vm: UpdatesSettingsViewModel = getViewModel(),
) { ) {
val listItems = listOf( val coroutineScope = rememberCoroutineScope()
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
),
)
Scaffold( Scaffold(
topBar = { topBar = {
@ -60,22 +46,29 @@ fun UpdatesSettingsScreen(
.padding(paddingValues) .padding(paddingValues)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
NotificationCard( SettingsListItem(
text = stringResource(R.string.update_notification), modifier = Modifier.clickable {
icon = Icons.Default.Update, coroutineScope.launch {
primaryAction = onUpdateClick 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(
SettingsListItem( modifier = Modifier.clickable(onClick = onChangelogClick),
modifier = Modifier headlineContent = stringResource(R.string.changelog),
.fillMaxWidth() supportingContent = stringResource(
.padding(horizontal = 8.dp) R.string.changelog_description
.clickable { onClick() },
headlineContent = title,
supportingContent = 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") { uiSafe(app, R.string.changelog_download_fail, "Failed to download changelog") {
changelogs = api.getReleases("revanced-manager").getOrNull().orEmpty().map { release -> changelogs = api.getReleases("revanced-manager").getOrNull().orEmpty().map { release ->
Changelog( Changelog(
release.metadata.tag, release.version,
release.findAssetByType(APK_MIMETYPE).downloadCount, release.findAssetByType(APK_MIMETYPE).downloadCount,
release.metadata.publishedAt, release.metadata.publishedAt,
release.metadata.body 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.ui.theme.Theme
import app.revanced.manager.util.tag import app.revanced.manager.util.tag
import app.revanced.manager.util.toast import app.revanced.manager.util.toast
import app.revanced.manager.util.uiSafe
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -55,13 +58,8 @@ class MainViewModel(
private suspend fun checkForManagerUpdates() { private suspend fun checkForManagerUpdates() {
if (!prefs.managerAutoUpdates.get() || !networkInfo.isConnected()) return if (!prefs.managerAutoUpdates.get() || !networkInfo.isConnected()) return
try { uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
reVancedAPI.getLatestRelease("revanced-manager").getOrThrow().let { release -> updatedManagerVersion = reVancedAPI.getAppUpdate()?.version
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)
} }
} }

View file

@ -16,6 +16,7 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.data.platform.NetworkInfo import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.network.api.ReVancedAPI import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType
@ -47,6 +48,7 @@ class UpdateViewModel(
private val http: HttpService by inject() private val http: HttpService by inject()
private val pm: PM by inject() private val pm: PM by inject()
private val networkInfo: NetworkInfo by inject() private val networkInfo: NetworkInfo by inject()
private val fs: Filesystem by inject()
var downloadedSize by mutableStateOf(0L) var downloadedSize by mutableStateOf(0L)
private set private set
@ -65,17 +67,16 @@ class UpdateViewModel(
var changelog: Changelog? by mutableStateOf(null) 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 var release: ReVancedRelease? = null
private val job = viewModelScope.launch { private val job = viewModelScope.launch {
uiSafe(app, R.string.download_manager_failed, "Failed to download ReVanced Manager") { uiSafe(app, R.string.download_manager_failed, "Failed to download ReVanced Manager") {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val response = reVancedAPI val response = reVancedAPI.getAppUpdate() ?: throw Exception("No update available")
.getLatestRelease("revanced-manager")
.getOrThrow()
release = response release = response
changelog = Changelog( changelog = Changelog(
response.metadata.tag, response.version,
response.findAssetByType(APK_MIMETYPE).downloadCount, response.findAssetByType(APK_MIMETYPE).downloadCount,
response.metadata.publishedAt, response.metadata.publishedAt,
response.metadata.body 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="ready_to_install_update">Ready to install update</string>
<string name="update_completed">Update installed</string> <string name="update_completed">Update installed</string>
<string name="install_update_manager_failed">Failed to install update</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="manual_update_check">Check for updates</string>
<string name="update_channel">Update channel</string> <string name="manual_update_check_description">Manually check for updates</string>
<string name="update_channel_description">Stable</string> <string name="update_checking_manager">Update checking</string>
<string name="update_notifications">Update notifications</string> <string name="update_checking_manager_description">Check for new versions of ReVanced Manager when the application starts</string>
<string name="update_notifications_description">Dialog on app launch + badges</string>
<string name="changelog">Changelog</string> <string name="changelog">Changelog</string>
<string name="changelog_loading">Loading changelog</string> <string name="changelog_loading">Loading changelog</string>
<string name="changelog_download_fail">Failed to download changelog: %s</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="invalid_date">Invalid date</string>
<string name="disable_battery_optimization">Disable battery optimization</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="dismiss_temporary">Not now</string>
<string name="update_available_dialog_title">New update available</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> <string name="update_available_dialog_description">A new version (%s) is available for download.</string>