diff --git a/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt b/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt index dbcba6a0..f34b02b2 100644 --- a/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt +++ b/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt @@ -21,4 +21,5 @@ val viewModelModule = module { viewModelOf(::DownloadsViewModel) viewModelOf(::InstalledAppsViewModel) viewModelOf(::InstalledAppInfoViewModel) + viewModelOf(::UpdatesSettingsViewModel) } diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt index 295cc2bd..1c857051 100644 --- a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt +++ b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt @@ -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) } } diff --git a/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt b/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt index 1bc5fdd6..300d0e82 100644 --- a/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt +++ b/app/src/main/java/app/revanced/manager/network/api/ReVancedAPI.kt @@ -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) } } diff --git a/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt b/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt index d7fe2bbf..442e4107 100644 --- a/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt +++ b/app/src/main/java/app/revanced/manager/network/dto/ReVancedRelease.kt @@ -17,7 +17,9 @@ data class ReVancedReleases( data class ReVancedRelease( val metadata: ReVancedReleaseMeta, val assets: List -) +) { + val version get() = metadata.tag +} @Serializable data class ReVancedReleaseMeta( diff --git a/app/src/main/java/app/revanced/manager/ui/component/settings/BooleanItem.kt b/app/src/main/java/app/revanced/manager/ui/component/settings/BooleanItem.kt index 5df102a1..42e9a83e 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/settings/BooleanItem.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/settings/BooleanItem.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.launch @Composable fun BooleanItem( + modifier: Modifier = Modifier, preference: Preference, 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 = { diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt index 88e094e0..0f5111db 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt @@ -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 + ) } } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/ChangelogsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/ChangelogsViewModel.kt index 61466ed2..aa9c878c 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/ChangelogsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/ChangelogsViewModel.kt @@ -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 diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/MainViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/MainViewModel.kt index 0fcfedfd..3534b5d5 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/MainViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/MainViewModel.kt @@ -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 } } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt index d8b26b22..5ea4db74 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdateViewModel.kt @@ -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 diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt new file mode 100644 index 00000000..cd96e091 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7c418ba..5cb02d50 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -281,11 +281,10 @@ Ready to install update Update installed Failed to install update - A minor update for ReVanced Manager is available. Click here to update and get the latest features and fixes! - Update channel - Stable - Update notifications - Dialog on app launch + badges + Check for updates + Manually check for updates + Update checking + Check for new versions of ReVanced Manager when the application starts Changelog Loading changelog Failed to download changelog: %s @@ -306,7 +305,9 @@ Invalid date Disable battery optimization - Failed to check for updates + Failed to check for updates: %s + No update available + Checking for updates… Not now New update available A new version (%s) is available for download.