mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2024-11-13 02:14:31 +01:00
feat: updater UI and code improvements (#1597)
This commit is contained in:
parent
aa6e612fba
commit
b7cb6b94f5
11 changed files with 102 additions and 61 deletions
|
@ -21,4 +21,5 @@ val viewModelModule = module {
|
||||||
viewModelOf(::DownloadsViewModel)
|
viewModelOf(::DownloadsViewModel)
|
||||||
viewModelOf(::InstalledAppsViewModel)
|
viewModelOf(::InstalledAppsViewModel)
|
||||||
viewModelOf(::InstalledAppInfoViewModel)
|
viewModelOf(::InstalledAppInfoViewModel)
|
||||||
|
viewModelOf(::UpdatesSettingsViewModel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue