mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
feat: check for updates on startup (#1462)
This commit is contained in:
parent
1a83315424
commit
1dc41badd9
@ -1,27 +1,25 @@
|
||||
package app.revanced.manager
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Update
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import app.revanced.manager.ui.component.AutoUpdatesDialog
|
||||
import app.revanced.manager.ui.destination.Destination
|
||||
import app.revanced.manager.ui.screen.InstalledAppInfoScreen
|
||||
import app.revanced.manager.ui.destination.SettingsDestination
|
||||
import app.revanced.manager.ui.screen.AppSelectorScreen
|
||||
import app.revanced.manager.ui.screen.DashboardScreen
|
||||
import app.revanced.manager.ui.screen.InstalledAppInfoScreen
|
||||
import app.revanced.manager.ui.screen.InstallerScreen
|
||||
import app.revanced.manager.ui.screen.SelectedAppInfoScreen
|
||||
import app.revanced.manager.ui.screen.SettingsScreen
|
||||
@ -30,17 +28,15 @@ import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
||||
import app.revanced.manager.ui.theme.Theme
|
||||
import app.revanced.manager.ui.viewmodel.MainViewModel
|
||||
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
|
||||
import app.revanced.manager.util.tag
|
||||
import app.revanced.manager.util.toast
|
||||
import dev.olshevski.navigation.reimagined.AnimatedNavHost
|
||||
import dev.olshevski.navigation.reimagined.NavBackHandler
|
||||
import dev.olshevski.navigation.reimagined.navigate
|
||||
import dev.olshevski.navigation.reimagined.pop
|
||||
import dev.olshevski.navigation.reimagined.popUpTo
|
||||
import dev.olshevski.navigation.reimagined.rememberNavController
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koin.androidx.compose.getViewModel as getComposeViewModel
|
||||
import org.koin.androidx.viewmodel.ext.android.getViewModel as getAndroidViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@ExperimentalAnimationApi
|
||||
@ -51,6 +47,8 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
val vm: MainViewModel = getAndroidViewModel()
|
||||
|
||||
vm.importLegacySettings(this)
|
||||
|
||||
setContent {
|
||||
val theme by vm.prefs.theme.getAsState()
|
||||
val dynamicColor by vm.prefs.dynamicColor.getAsState()
|
||||
@ -66,46 +64,30 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
val firstLaunch by vm.prefs.firstLaunch.getAsState()
|
||||
|
||||
if (firstLaunch) {
|
||||
var legacyActivityState by rememberSaveable { mutableStateOf(LegacyActivity.NOT_LAUNCHED) }
|
||||
if (legacyActivityState == LegacyActivity.NOT_LAUNCHED) {
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) { result: ActivityResult ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
if (result.data != null) {
|
||||
val jsonData = result.data!!.getStringExtra("data")!!
|
||||
vm.applyLegacySettings(jsonData)
|
||||
if (firstLaunch) AutoUpdatesDialog(vm::applyAutoUpdatePrefs)
|
||||
|
||||
vm.updatedManagerVersion?.let {
|
||||
AlertDialog(
|
||||
onDismissRequest = vm::dismissUpdateDialog,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
vm.dismissUpdateDialog()
|
||||
navController.navigate(Destination.Settings(SettingsDestination.UpdateProgress))
|
||||
}
|
||||
} else {
|
||||
legacyActivityState = LegacyActivity.FAILED
|
||||
toast(getString(R.string.legacy_import_failed))
|
||||
) {
|
||||
Text(stringResource(R.string.update))
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent().apply {
|
||||
setClassName(
|
||||
"app.revanced.manager.flutter",
|
||||
"app.revanced.manager.flutter.ExportSettingsActivity"
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
launcher.launch(intent)
|
||||
} catch (e: Exception) {
|
||||
if (e !is ActivityNotFoundException) {
|
||||
toast(getString(R.string.legacy_import_failed))
|
||||
Log.e(tag, "Failed to launch legacy import activity: $e")
|
||||
}
|
||||
legacyActivityState = LegacyActivity.FAILED
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = vm::dismissUpdateDialog) {
|
||||
Text(stringResource(R.string.dismiss_temporary))
|
||||
}
|
||||
}
|
||||
|
||||
legacyActivityState = LegacyActivity.LAUNCHED
|
||||
} else if (legacyActivityState == LegacyActivity.FAILED) {
|
||||
AutoUpdatesDialog(vm::applyAutoUpdatePrefs)
|
||||
}
|
||||
},
|
||||
icon = { Icon(Icons.Outlined.Update, null) },
|
||||
title = { Text(stringResource(R.string.update_available)) },
|
||||
text = { Text(stringResource(R.string.update_available_description, it)) }
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedNavHost(
|
||||
@ -113,7 +95,7 @@ class MainActivity : ComponentActivity() {
|
||||
) { destination ->
|
||||
when (destination) {
|
||||
is Destination.Dashboard -> DashboardScreen(
|
||||
onSettingsClick = { navController.navigate(Destination.Settings) },
|
||||
onSettingsClick = { navController.navigate(Destination.Settings()) },
|
||||
onAppSelectorClick = { navController.navigate(Destination.AppSelector) },
|
||||
onAppClick = { installedApp ->
|
||||
navController.navigate(
|
||||
@ -138,7 +120,8 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
|
||||
is Destination.Settings -> SettingsScreen(
|
||||
onBackClick = { navController.pop() }
|
||||
onBackClick = { navController.pop() },
|
||||
startDestination = destination.startDestination
|
||||
)
|
||||
|
||||
is Destination.AppSelector -> AppSelectorScreen(
|
||||
@ -199,10 +182,4 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class LegacyActivity {
|
||||
NOT_LAUNCHED,
|
||||
LAUNCHED,
|
||||
FAILED
|
||||
}
|
||||
}
|
||||
|
@ -11,16 +11,16 @@ import kotlinx.parcelize.RawValue
|
||||
sealed interface Destination : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
object Dashboard : Destination
|
||||
data object Dashboard : Destination
|
||||
|
||||
@Parcelize
|
||||
data class InstalledApplicationInfo(val installedApp: InstalledApp) : Destination
|
||||
|
||||
@Parcelize
|
||||
object AppSelector : Destination
|
||||
data object AppSelector : Destination
|
||||
|
||||
@Parcelize
|
||||
object Settings : Destination
|
||||
data class Settings(val startDestination: SettingsDestination = SettingsDestination.Settings) : Destination
|
||||
|
||||
@Parcelize
|
||||
data class VersionSelector(val packageName: String, val patchesSelection: PatchesSelection? = null) : Destination
|
||||
|
@ -43,10 +43,16 @@ import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
onBackClick: () -> Unit,
|
||||
startDestination: SettingsDestination,
|
||||
viewModel: SettingsViewModel = getViewModel()
|
||||
) {
|
||||
val navController =
|
||||
rememberNavController<SettingsDestination>(startDestination = SettingsDestination.Settings)
|
||||
val navController = rememberNavController(startDestination)
|
||||
|
||||
val backClick: () -> Unit = {
|
||||
if (navController.backstack.entries.size == 1)
|
||||
onBackClick()
|
||||
else navController.pop()
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
@ -92,48 +98,48 @@ fun SettingsScreen(
|
||||
when (destination) {
|
||||
|
||||
is SettingsDestination.General -> GeneralSettingsScreen(
|
||||
onBackClick = { navController.pop() },
|
||||
onBackClick = backClick,
|
||||
viewModel = viewModel
|
||||
)
|
||||
|
||||
is SettingsDestination.Advanced -> AdvancedSettingsScreen(
|
||||
onBackClick = { navController.pop() }
|
||||
onBackClick = backClick
|
||||
)
|
||||
|
||||
is SettingsDestination.Updates -> UpdatesSettingsScreen(
|
||||
onBackClick = { navController.pop() },
|
||||
onBackClick = backClick,
|
||||
onChangelogClick = { navController.navigate(SettingsDestination.Changelogs) },
|
||||
onUpdateClick = { navController.navigate(SettingsDestination.UpdateProgress) }
|
||||
)
|
||||
|
||||
is SettingsDestination.Downloads -> DownloadsSettingsScreen(
|
||||
onBackClick = { navController.pop() }
|
||||
onBackClick = backClick
|
||||
)
|
||||
|
||||
is SettingsDestination.ImportExport -> ImportExportSettingsScreen(
|
||||
onBackClick = { navController.pop() }
|
||||
onBackClick = backClick
|
||||
)
|
||||
|
||||
is SettingsDestination.About -> AboutSettingsScreen(
|
||||
onBackClick = { navController.pop() },
|
||||
onBackClick = backClick,
|
||||
onContributorsClick = { navController.navigate(SettingsDestination.Contributors) },
|
||||
onLicensesClick = { navController.navigate(SettingsDestination.Licenses) }
|
||||
)
|
||||
|
||||
is SettingsDestination.UpdateProgress -> UpdateProgressScreen(
|
||||
onBackClick = { navController.pop() },
|
||||
onBackClick = backClick,
|
||||
)
|
||||
|
||||
is SettingsDestination.Changelogs -> ChangelogsScreen(
|
||||
onBackClick = { navController.pop() },
|
||||
onBackClick = backClick,
|
||||
)
|
||||
|
||||
is SettingsDestination.Contributors -> ContributorScreen(
|
||||
onBackClick = { navController.pop() },
|
||||
onBackClick = backClick,
|
||||
)
|
||||
|
||||
is SettingsDestination.Licenses -> LicensesScreen(
|
||||
onBackClick = { navController.pop() },
|
||||
onBackClick = backClick,
|
||||
)
|
||||
|
||||
is SettingsDestination.Settings -> {
|
||||
@ -141,7 +147,7 @@ fun SettingsScreen(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
title = stringResource(R.string.settings),
|
||||
onBackClick = onBackClick,
|
||||
onBackClick = backClick,
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
|
@ -1,15 +1,32 @@
|
||||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.data.platform.NetworkInfo
|
||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.asRemoteOrNull
|
||||
import app.revanced.manager.domain.manager.KeystoreManager
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||
import app.revanced.manager.domain.repository.PatchSelectionRepository
|
||||
import app.revanced.manager.domain.repository.SerializedSelection
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
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 kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -19,12 +36,42 @@ class MainViewModel(
|
||||
private val patchBundleRepository: PatchBundleRepository,
|
||||
private val patchSelectionRepository: PatchSelectionRepository,
|
||||
private val keystoreManager: KeystoreManager,
|
||||
private val reVancedAPI: ReVancedAPI,
|
||||
private val app: Application,
|
||||
private val networkInfo: NetworkInfo,
|
||||
val prefs: PreferencesManager
|
||||
) : ViewModel() {
|
||||
var updatedManagerVersion: String? by mutableStateOf(null)
|
||||
private set
|
||||
|
||||
init {
|
||||
viewModelScope.launch { checkForManagerUpdates() }
|
||||
}
|
||||
|
||||
fun dismissUpdateDialog() {
|
||||
updatedManagerVersion = null
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fun applyAutoUpdatePrefs(manager: Boolean, patches: Boolean) = viewModelScope.launch {
|
||||
prefs.firstLaunch.update(false)
|
||||
|
||||
prefs.managerAutoUpdates.update(manager)
|
||||
|
||||
if (manager) checkForManagerUpdates()
|
||||
|
||||
if (patches) {
|
||||
with(patchBundleRepository) {
|
||||
sources
|
||||
@ -38,7 +85,39 @@ class MainViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun applyLegacySettings(data: String) = viewModelScope.launch {
|
||||
fun importLegacySettings(componentActivity: ComponentActivity) {
|
||||
if (!prefs.firstLaunch.getBlocking()) return
|
||||
|
||||
try {
|
||||
val launcher = componentActivity.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result: ActivityResult ->
|
||||
if (result.resultCode == ComponentActivity.RESULT_OK) {
|
||||
result.data?.getStringExtra("data")?.let {
|
||||
applyLegacySettings(it)
|
||||
} ?: app.toast(app.getString(R.string.legacy_import_failed))
|
||||
} else {
|
||||
app.toast(app.getString(R.string.legacy_import_failed))
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent().apply {
|
||||
setClassName(
|
||||
"app.revanced.manager.flutter",
|
||||
"app.revanced.manager.flutter.ExportSettingsActivity"
|
||||
)
|
||||
}
|
||||
|
||||
launcher.launch(intent)
|
||||
} catch (e: Exception) {
|
||||
if (e !is ActivityNotFoundException) {
|
||||
app.toast(app.getString(R.string.legacy_import_failed))
|
||||
Log.e(tag, "Failed to launch legacy import activity: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyLegacySettings(data: String) = viewModelScope.launch {
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val settings = json.decodeFromString<LegacySettings>(data)
|
||||
|
||||
@ -48,7 +127,7 @@ class MainViewModel(
|
||||
1 to Theme.LIGHT,
|
||||
2 to Theme.DARK
|
||||
)
|
||||
prefs.theme.update(themeMap[theme]!!)
|
||||
prefs.theme.update(themeMap[theme] ?: Theme.SYSTEM)
|
||||
}
|
||||
settings.useDynamicTheme?.let { dynamicColor ->
|
||||
prefs.dynamicColor.update(dynamicColor)
|
||||
@ -84,7 +163,6 @@ class MainViewModel(
|
||||
settings.patches?.let { selection ->
|
||||
patchSelectionRepository.import(0, selection)
|
||||
}
|
||||
prefs.firstLaunch.update(false)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -297,4 +297,9 @@
|
||||
<string name="days_ago">%sd ago</string>
|
||||
<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="dismiss_temporary">Not now</string>
|
||||
<string name="update_available">New update available</string>
|
||||
<string name="update_available_description">A new version (%s) is available for download.</string>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user