diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 211d1ca5..03b6166d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -47,7 +47,6 @@ android { isPseudoLocalesEnabled = true } - buildConfigField("long", "BUILD_ID", "0L") } } diff --git a/app/src/main/java/app/revanced/manager/MainActivity.kt b/app/src/main/java/app/revanced/manager/MainActivity.kt index 60399f10..563e0e0d 100644 --- a/app/src/main/java/app/revanced/manager/MainActivity.kt +++ b/app/src/main/java/app/revanced/manager/MainActivity.kt @@ -48,7 +48,7 @@ import app.revanced.manager.ui.screen.settings.ContributorScreen import app.revanced.manager.ui.screen.settings.DeveloperOptionsScreen import app.revanced.manager.ui.screen.settings.DownloadsSettingsScreen import app.revanced.manager.ui.screen.settings.GeneralSettingsScreen -import app.revanced.manager.ui.screen.settings.ImportExportSettingsScreen +import app.revanced.manager.ui.screen.settings.BackupRestoreSettingsScreen import app.revanced.manager.ui.screen.settings.LicensesScreen import app.revanced.manager.ui.screen.settings.update.ChangelogsScreen import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen @@ -290,7 +290,7 @@ private fun ReVancedManager(vm: MainViewModel) { } composable { - ImportExportSettingsScreen(onBackClick = navController::popBackStack) + BackupRestoreSettingsScreen(onBackClick = navController::popBackStack) } composable { diff --git a/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt index dbf2f100..1c3b3a2d 100644 --- a/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt +++ b/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt @@ -22,10 +22,9 @@ class PreferencesManager( val managerAutoUpdates = booleanPreference("manager_auto_updates", false) val showManagerUpdateDialogOnLaunch = booleanPreference("show_manager_update_dialog_on_launch", true) - val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", false) - val disableSelectionWarning = booleanPreference("disable_selection_warning", false) - val disableUniversalPatchWarning = booleanPreference("disable_universal_patch_warning", false) - val suggestedVersionSafeguard = booleanPreference("suggested_version_safeguard", true) + val allowIncompatibleMixing = booleanPreference("allow_incompatible_mixing", false) + val allowChangingPatchSelection = booleanPreference("allow_changing_patch_selection", false) + val allowUniversalPatch = booleanPreference("allow_universal_patch", false) val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet()) } diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt index 79bb5cea..65800db1 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt @@ -76,7 +76,7 @@ class PatchBundleRepository( suspend fun isVersionAllowed(packageName: String, version: String) = withContext(Dispatchers.Default) { - if (!prefs.suggestedVersionSafeguard.get()) return@withContext true + if (!prefs.allowIncompatibleMixing.get()) return@withContext true val suggestedVersion = suggestedVersions.first()[packageName] ?: return@withContext true suggestedVersion == version diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt index 35f2546f..d3d52b9e 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt @@ -66,7 +66,7 @@ fun SelectedAppInfoScreen( val version = vm.selectedApp.version val bundles by vm.bundleInfoFlow.collectAsStateWithLifecycle(emptyList()) - val allowIncompatiblePatches by vm.prefs.disablePatchVersionCompatCheck.getAsState() + val allowIncompatiblePatches by vm.prefs.allowIncompatibleMixing.getAsState() val patches = remember(bundles, allowIncompatiblePatches) { vm.getPatches(bundles, allowIncompatiblePatches) } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt index 46a25b10..badc8110 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt @@ -22,18 +22,13 @@ private val settingsSections = listOf( Icons.Outlined.Settings ) to Settings.General, Triple( - R.string.updates, - R.string.updates_description, - Icons.Outlined.Update - ) to Settings.Updates, - Triple( - R.string.downloads, - R.string.downloads_description, + R.string.extensions, + R.string.extensions_description, Icons.Outlined.Download ) to Settings.Downloads, Triple( - R.string.import_export, - R.string.import_export_description, + R.string.backup_restore, + R.string.backup_restore_description, Icons.Outlined.SwapVert ) to Settings.ImportExport, Triple( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/AboutSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/AboutSettingsScreen.kt index 3b5e0cbc..e518ff0d 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/AboutSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/AboutSettingsScreen.kt @@ -51,8 +51,9 @@ import org.koin.androidx.compose.koinViewModel @Composable fun AboutSettingsScreen( onBackClick: () -> Unit, + onChangelogClick: () -> Unit, navigate: (Settings.Destination) -> Unit, - viewModel: AboutViewModel = koinViewModel() + vm: AboutViewModel = koinViewModel() ) { val context = LocalContext.current // painterResource() is broken on release builds for some reason. @@ -60,11 +61,11 @@ fun AboutSettingsScreen( AppCompatResources.getDrawable(context, R.drawable.ic_logo_ring) }) - val (preferredSocials, socials) = remember(viewModel.socials) { - viewModel.socials.partition(ReVancedSocial::preferred) + val (preferredSocials, socials) = remember(vm.socials) { + vm.socials.partition(ReVancedSocial::preferred) } - val preferredSocialButtons = remember(preferredSocials, viewModel.donate, viewModel.contact) { + val preferredSocialButtons = remember(preferredSocials, vm.donate, vm.contact) { preferredSocials.map { Triple( getSocialIcon(it.name), @@ -74,7 +75,7 @@ fun AboutSettingsScreen( } ) } + listOfNotNull( - viewModel.donate?.let { + vm.donate?.let { Triple( Icons.Outlined.FavoriteBorder, context.getString(R.string.donate), @@ -83,7 +84,7 @@ fun AboutSettingsScreen( } ) }, - viewModel.contact?.let { + vm.contact?.let { Triple( Icons.Outlined.MailOutline, context.getString(R.string.contact), @@ -108,11 +109,18 @@ fun AboutSettingsScreen( } val listItems = listOfNotNull( - Triple(stringResource(R.string.submit_feedback), + Triple( + stringResource(R.string.changelog), + stringResource(R.string.changelog_description), + third = { onChangelogClick } + ), + Triple( + stringResource(R.string.submit_feedback), stringResource(R.string.submit_feedback_description), third = { context.openUrl("https://github.com/ReVanced/revanced-manager/issues/new/choose") - }), + }, + ), Triple( stringResource(R.string.contributors), stringResource(R.string.contributors_description), diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt index ac3dfc7d..fdf8c68f 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Api +import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Restore import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api @@ -89,73 +90,81 @@ fun AdvancedSettingsScreen( .fillMaxSize() .padding(paddingValues) ) { - GroupHeader(stringResource(R.string.manager)) + val apiSource by vm.prefs.api.getAsState() + var showApiSourceDialog by rememberSaveable { mutableStateOf(false) } - val apiUrl by vm.prefs.api.getAsState() - var showApiUrlDialog by rememberSaveable { mutableStateOf(false) } - - if (showApiUrlDialog) { - APIUrlDialog( - currentUrl = apiUrl, + if (showApiSourceDialog) { + APISourceDialog( + currentUrl = apiSource, defaultUrl = vm.prefs.api.default, onSubmit = { - showApiUrlDialog = false - it?.let(vm::setApiUrl) + showApiSourceDialog = false + it?.let(vm::setApiSource) } ) } - SettingsListItem( - headlineContent = stringResource(R.string.api_url), - supportingContent = stringResource(R.string.api_url_description), - modifier = Modifier.clickable { - showApiUrlDialog = true - } - ) - GroupHeader(stringResource(R.string.patcher)) - BooleanItem( - preference = vm.prefs.useProcessRuntime, - coroutineScope = vm.viewModelScope, - headline = R.string.process_runtime, - description = R.string.process_runtime_description, - ) + GroupHeader(stringResource(R.string.revanced_patcher)) IntegerItem( preference = vm.prefs.patcherProcessMemoryLimit, coroutineScope = vm.viewModelScope, headline = R.string.process_runtime_memory_limit, - description = R.string.process_runtime_memory_limit_description, + description = vm.prefs.patcherProcessMemoryLimit.getAsState().value, ) - GroupHeader(stringResource(R.string.safeguards)) - SafeguardBooleanItem( - preference = vm.prefs.disablePatchVersionCompatCheck, - coroutineScope = vm.viewModelScope, - headline = R.string.patch_compat_check, - description = R.string.patch_compat_check_description, - confirmationText = R.string.patch_compat_check_confirmation + GroupHeader(stringResource(R.string.manager)) + SettingsListItem( + headlineContent = stringResource(R.string.api_source), + supportingContent = apiSource, + modifier = Modifier.clickable { + showApiSourceDialog = true + }, + trailingContent = { + IconButton(onClick = { showApiSourceDialog = true }) { + Icon( + Icons.Outlined.Edit, + contentDescription = stringResource(R.string.edit) + ) + } + } ) SafeguardBooleanItem( - preference = vm.prefs.disableUniversalPatchWarning, + preference = vm.prefs.allowIncompatibleMixing, + coroutineScope = vm.viewModelScope, + headline = R.string.allow_compatibility_mixing, + description = R.string.allow_compatibility_mixing_description, + confirmationText = R.string.allow_compatibility_mixing_confirmation + ) + SafeguardBooleanItem( + preference = vm.prefs.allowUniversalPatch, coroutineScope = vm.viewModelScope, headline = R.string.universal_patches_safeguard, description = R.string.universal_patches_safeguard_description, confirmationText = R.string.universal_patches_safeguard_confirmation ) SafeguardBooleanItem( - preference = vm.prefs.suggestedVersionSafeguard, - coroutineScope = vm.viewModelScope, - headline = R.string.suggested_version_safeguard, - description = R.string.suggested_version_safeguard_description, - confirmationText = R.string.suggested_version_safeguard_confirmation - ) - SafeguardBooleanItem( - preference = vm.prefs.disableSelectionWarning, + preference = vm.prefs.allowChangingPatchSelection, coroutineScope = vm.viewModelScope, headline = R.string.patch_selection_safeguard, description = R.string.patch_selection_safeguard_description, confirmationText = R.string.patch_selection_safeguard_confirmation ) + GroupHeader(stringResource(R.string.update)) + BooleanItem( + preference = vm.showManagerUpdateDialogOnLaunch, + headline = R.string.show_manager_update_dialog_on_launch, + description = R.string.check_for_update_auto_description + ) + + GroupHeader(stringResource(R.string.experimental_features)) + BooleanItem( + preference = vm.prefs.useProcessRuntime, + coroutineScope = vm.viewModelScope, + headline = R.string.process_runtime, + description = R.string.process_runtime_description, + ) + GroupHeader(stringResource(R.string.debugging)) val exportDebugLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { @@ -167,11 +176,10 @@ fun AdvancedSettingsScreen( ) val clipboard = remember { context.getSystemService()!! } val deviceContent = """ - Version: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) - Build type: ${BuildConfig.BUILD_TYPE} + Version: ${BuildConfig.VERSION_NAME}, ${BuildConfig.BUILD_TYPE} (${BuildConfig.VERSION_CODE}) Model: ${Build.MODEL} Android version: ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT}) - Supported Archs: ${Build.SUPPORTED_ABIS.joinToString(", ")} + Preferred Architectures: ${Build.SUPPORTED_ABIS.joinToString(", ")} Memory limit: $memoryLimit """.trimIndent() SettingsListItem( @@ -194,18 +202,18 @@ fun AdvancedSettingsScreen( } @Composable -private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (String?) -> Unit) { - var url by rememberSaveable(currentUrl) { mutableStateOf(currentUrl) } +private fun APISourceDialog(currentUrl: String, defaultUrl: String, onSubmit: (String?) -> Unit) { + var source by rememberSaveable(currentUrl) { mutableStateOf(currentUrl) } AlertDialog( onDismissRequest = { onSubmit(null) }, confirmButton = { TextButton( onClick = { - onSubmit(url) + onSubmit(source) } ) { - Text(stringResource(R.string.api_url_dialog_save)) + Text(stringResource(R.string.api_source_dialog_save)) } }, dismissButton = { @@ -218,7 +226,7 @@ private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (Stri }, title = { Text( - text = stringResource(R.string.api_url_dialog_title), + text = stringResource(R.string.api_source_dialog_title), style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center), color = MaterialTheme.colorScheme.onSurface, ) @@ -228,23 +236,23 @@ private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (Stri verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( - text = stringResource(R.string.api_url_dialog_description), + text = stringResource(R.string.api_source_dialog_description), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) Text( - text = stringResource(R.string.api_url_dialog_warning), + text = stringResource(R.string.api_source_dialog_warning), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error ) OutlinedTextField( modifier = Modifier.fillMaxWidth(), - value = url, - onValueChange = { url = it }, - label = { Text(stringResource(R.string.api_url)) }, + value = source, + onValueChange = { source = it }, + label = { Text(stringResource(R.string.api_source)) }, trailingIcon = { - IconButton(onClick = { url = defaultUrl }) { - Icon(Icons.Outlined.Restore, stringResource(R.string.api_url_dialog_reset)) + IconButton(onClick = { source = defaultUrl }) { + Icon(Icons.Outlined.Restore, stringResource(R.string.api_source_dialog_reset)) } } ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/ImportExportSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/BackupRestoreSettingsScreen.kt similarity index 79% rename from app/src/main/java/app/revanced/manager/ui/screen/settings/ImportExportSettingsScreen.kt rename to app/src/main/java/app/revanced/manager/ui/screen/settings/BackupRestoreSettingsScreen.kt index 7f8083f3..cf8e8b4f 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/ImportExportSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/BackupRestoreSettingsScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -54,7 +55,7 @@ import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ImportExportSettingsScreen( +fun BackupRestoreSettingsScreen( onBackClick: () -> Unit, vm: ImportExportViewModel = koinViewModel() ) { @@ -100,7 +101,7 @@ fun ImportExportSettingsScreen( vm.viewModelScope.launch { uiSafe(context, R.string.failed_to_import_keystore, "Failed to import keystore") { val result = vm.tryKeystoreImport(cn, pass) - if (!result) context.toast(context.getString(R.string.import_keystore_wrong_credentials)) + if (!result) context.toast(context.getString(R.string.restore_keystore_wrong_credentials)) } } } @@ -112,7 +113,7 @@ fun ImportExportSettingsScreen( Scaffold( topBar = { AppTopBar( - title = stringResource(R.string.import_export), + title = stringResource(R.string.backup_restore), scrollBehavior = scrollBehavior, onBackClick = onBackClick ) @@ -147,63 +148,89 @@ fun ImportExportSettingsScreen( } } - GroupHeader(stringResource(R.string.import_)) - GroupItem( - onClick = { - importKeystoreLauncher.launch("*/*") - }, - headline = R.string.import_keystore, - description = R.string.import_keystore_description - ) - GroupItem( - onClick = vm::importSelection, - headline = R.string.import_patch_selection, - description = R.string.import_patch_selection_description - ) - - GroupHeader(stringResource(R.string.export)) + GroupHeader(stringResource(R.string.keystore)) GroupItem( onClick = { if (!vm.canExport()) { - context.toast(context.getString(R.string.export_keystore_unavailable)) + context.toast(context.getString(R.string.backup_keystore_unavailable)) return@GroupItem } exportKeystoreLauncher.launch("Manager.keystore") }, - headline = R.string.export_keystore, - description = R.string.export_keystore_description + headline = R.string.backup, + description = R.string.backup_keystore_description ) GroupItem( - onClick = vm::exportSelection, - headline = R.string.export_patch_selection, - description = R.string.export_patch_selection_description + onClick = { + importKeystoreLauncher.launch("*/*") + }, + headline = R.string.restore, + description = R.string.restore_keystore_description ) - - GroupHeader(stringResource(R.string.reset)) GroupItem( onClick = vm::regenerateKeystore, - headline = R.string.regenerate_keystore, + headline = { + Text( + stringResource(R.string.regenerate_keystore), + color = MaterialTheme.colorScheme.error + ) + }, description = R.string.regenerate_keystore_description ) + + GroupHeader(stringResource(R.string.patch_selection)) + GroupItem( + onClick = vm::exportSelection, + headline = R.string.backup, + description = R.string.restore_patch_selection_description + ) + GroupItem( + onClick = vm::importSelection, + headline = R.string.restore, + description = R.string.backup_patch_selection_description + ) GroupItem( onClick = vm::resetSelection, // TODO: allow resetting selection for specific bundle or package name. - headline = R.string.reset_patch_selection, + headline = { + Text( + stringResource(R.string.reset), + color = MaterialTheme.colorScheme.error + ) + }, description = R.string.reset_patch_selection_description ) + + GroupHeader(stringResource(R.string.patch_options)) + // TODO: patch options import/export. GroupItem( - onClick = vm::resetOptions, // TODO: patch options import/export. - headline = R.string.patch_options_reset_all, + onClick = vm::resetOptions, + headline = { + Text( + stringResource(R.string.reset), + color = MaterialTheme.colorScheme.error + ) + }, description = R.string.patch_options_reset_all_description, ) GroupItem( onClick = { showPackageSelector = true }, - headline = R.string.patch_options_reset_package, + headline = { + Text( + stringResource(R.string.patch_options_reset_package), + color = MaterialTheme.colorScheme.error + ) + }, description = R.string.patch_options_reset_package_description ) if (patchBundles.size > 1) { GroupItem( onClick = { showBundleSelector = true }, - headline = R.string.patch_options_reset_bundle, + headline = { + Text( + stringResource(R.string.patch_options_reset_bundle), + color = MaterialTheme.colorScheme.error + ) + }, description = R.string.patch_options_reset_bundle_description, ) } @@ -282,6 +309,19 @@ private fun GroupItem( ) } +@Composable +private fun GroupItem( + onClick: () -> Unit, + headline: @Composable () -> Unit, + @StringRes description: Int? = null +) { + SettingsListItem( + modifier = Modifier.clickable { onClick() }, + headlineContent = headline, + supportingContent = description?.let { stringResource(it) } + ) +} + @Composable fun KeystoreCredentialsDialog( onDismissRequest: () -> Unit, @@ -298,7 +338,7 @@ fun KeystoreCredentialsDialog( onSubmit(cn, pass) } ) { - Text(stringResource(R.string.import_keystore_dialog_button)) + Text(stringResource(R.string.restore_keystore_dialog_button)) } }, dismissButton = { @@ -311,7 +351,7 @@ fun KeystoreCredentialsDialog( }, title = { Text( - text = stringResource(R.string.import_keystore_dialog_title), + text = stringResource(R.string.restore_keystore_dialog_title), style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center), color = MaterialTheme.colorScheme.onSurface, ) @@ -322,19 +362,19 @@ fun KeystoreCredentialsDialog( verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( - text = stringResource(R.string.import_keystore_dialog_description), + text = stringResource(R.string.restore_keystore_dialog_description), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) OutlinedTextField( value = cn, onValueChange = { cn = it }, - label = { Text(stringResource(R.string.import_keystore_dialog_alias_field)) } + label = { Text(stringResource(R.string.restore_keystore_dialog_alias_field)) } ) PasswordField( value = pass, onValueChange = { pass = it }, - label = { Text(stringResource(R.string.import_keystore_dialog_password_field)) } + label = { Text(stringResource(R.string.restore_keystore_dialog_password_field)) } ) } } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/ContributorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/ContributorScreen.kt index a6be70bd..06f40a05 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/ContributorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/ContributorScreen.kt @@ -53,9 +53,9 @@ import org.koin.androidx.compose.koinViewModel @Composable fun ContributorScreen( onBackClick: () -> Unit, - viewModel: ContributorViewModel = koinViewModel() + vm: ContributorViewModel = koinViewModel() ) { - val repositories = viewModel.repositories + val repositories = vm.repositories val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) Scaffold( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/DeveloperOptionsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/DeveloperOptionsScreen.kt index 40a1e937..7bdabd37 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/DeveloperOptionsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/DeveloperOptionsScreen.kt @@ -44,7 +44,13 @@ fun DeveloperOptionsScreen( ) SettingsListItem( headlineContent = stringResource(R.string.patch_bundles_reset), - modifier = Modifier.clickable(onClick = vm::redownloadBundles) + modifier = Modifier.clickable(onClick = vm::resetBundles) + ) + + GroupHeader(stringResource(R.string.testing)) + SettingsListItem( + headlineContent = stringResource(R.string.disable_safeguard), + modifier = Modifier.clickable(onClick = vm::disableSafeguard) ) } } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt index 96247a03..6d4919a9 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt @@ -53,22 +53,22 @@ import java.security.MessageDigest @Composable fun DownloadsSettingsScreen( onBackClick: () -> Unit, - viewModel: DownloadsViewModel = koinViewModel() + vm: DownloadsViewModel = koinViewModel() ) { val pullRefreshState = rememberPullToRefreshState() - val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList()) - val pluginStates by viewModel.downloaderPluginStates.collectAsStateWithLifecycle() + val downloadedApps by vm.downloadedApps.collectAsStateWithLifecycle(emptyList()) + val pluginStates by vm.downloaderPluginStates.collectAsStateWithLifecycle() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) Scaffold( topBar = { AppTopBar( - title = stringResource(R.string.downloads), + title = stringResource(R.string.extensions), scrollBehavior = scrollBehavior, onBackClick = onBackClick, actions = { - if (viewModel.appSelection.isNotEmpty()) { - IconButton(onClick = { viewModel.deleteApps() }) { + if (vm.appSelection.isNotEmpty()) { + IconButton(onClick = { vm.deleteApps() }) { Icon(Icons.Default.Delete, stringResource(R.string.delete)) } } @@ -86,7 +86,7 @@ fun DownloadsSettingsScreen( ) { PullToRefreshDefaults.Indicator( state = pullRefreshState, - isRefreshing = viewModel.isRefreshingPlugins + isRefreshing = vm.isRefreshingPlugins ) } @@ -95,9 +95,9 @@ fun DownloadsSettingsScreen( .fillMaxSize() .padding(paddingValues) .pullToRefresh( - isRefreshing = viewModel.isRefreshingPlugins, + isRefreshing = vm.isRefreshingPlugins, state = pullRefreshState, - onRefresh = viewModel::refreshPlugins + onRefresh = vm::refreshPlugins ) ) { item { @@ -115,7 +115,7 @@ fun DownloadsSettingsScreen( val packageInfo = remember(packageName) { - viewModel.pm.getPackageInfo( + vm.pm.getPackageInfo( packageName ) } ?: return@item @@ -124,7 +124,7 @@ fun DownloadsSettingsScreen( val signature = remember(packageName) { val androidSignature = - viewModel.pm.getSignature(packageName) + vm.pm.getSignature(packageName) val hash = MessageDigest.getInstance("SHA-256") .digest(androidSignature.toByteArray()) hash.toHexString(format = HexFormat.UpperCase) @@ -140,7 +140,7 @@ fun DownloadsSettingsScreen( ), onDismiss = ::dismiss, onConfirm = { - viewModel.revokePluginTrust(packageName) + vm.revokePluginTrust(packageName) dismiss() } ) @@ -161,7 +161,7 @@ fun DownloadsSettingsScreen( ), onDismiss = ::dismiss, onConfirm = { - viewModel.trustPlugin(packageName) + vm.trustPlugin(packageName) dismiss() } ) @@ -201,17 +201,17 @@ fun DownloadsSettingsScreen( GroupHeader(stringResource(R.string.downloaded_apps)) } items(downloadedApps, key = { it.packageName to it.version }) { app -> - val selected = app in viewModel.appSelection + val selected = app in vm.appSelection SettingsListItem( - modifier = Modifier.clickable { viewModel.toggleApp(app) }, + modifier = Modifier.clickable { vm.toggleApp(app) }, headlineContent = app.packageName, leadingContent = (@Composable { HapticCheckbox( checked = selected, - onCheckedChange = { viewModel.toggleApp(app) } + onCheckedChange = { vm.toggleApp(app) } ) - }).takeIf { viewModel.appSelection.isNotEmpty() }, + }).takeIf { vm.appSelection.isNotEmpty() }, supportingContent = app.version, tonalElevation = if (selected) 8.dp else 0.dp ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt index 16dc0a65..e74f78c1 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Scaffold @@ -35,6 +36,7 @@ import app.revanced.manager.ui.component.settings.BooleanItem import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.theme.Theme import app.revanced.manager.ui.viewmodel.GeneralSettingsViewModel +import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject @@ -42,16 +44,17 @@ import org.koin.compose.koinInject @Composable fun GeneralSettingsScreen( onBackClick: () -> Unit, - viewModel: GeneralSettingsViewModel = koinViewModel() + vm: GeneralSettingsViewModel = koinViewModel(), + onUpdateClick: () -> Unit, ) { - val prefs = viewModel.prefs - val coroutineScope = viewModel.viewModelScope + val prefs = vm.prefs + val coroutineScope = vm.viewModelScope var showThemePicker by rememberSaveable { mutableStateOf(false) } if (showThemePicker) { ThemePicker( onDismiss = { showThemePicker = false }, - onConfirm = { viewModel.setTheme(it) } + onConfirm = { vm.setTheme(it) } ) } val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) @@ -76,10 +79,10 @@ fun GeneralSettingsScreen( val theme by prefs.theme.getAsState() SettingsListItem( modifier = Modifier.clickable { showThemePicker = true }, - headlineContent = stringResource(R.string.theme), - supportingContent = stringResource(R.string.theme_description), + headlineContent = stringResource(R.string.theme_mode), + supportingContent = stringResource(R.string.theme_mode_description), trailingContent = { - FilledTonalButton( + Button ( onClick = { showThemePicker = true } @@ -92,10 +95,23 @@ fun GeneralSettingsScreen( BooleanItem( preference = prefs.dynamicColor, coroutineScope = coroutineScope, - headline = R.string.dynamic_color, - description = R.string.dynamic_color_description + headline = R.string.personalized_color, + description = R.string.personalized_color_description ) } + + GroupHeader(stringResource(R.string.update)) + BooleanItem( + preference = vm.managerAutoUpdates, + headline = R.string.check_for_update, + description = R.string.check_for_update_auto_description + ) + FilledTonalButton ( + modifier = Modifier.padding(top = paddingValues.calculateTopPadding()), + onClick = onUpdateClick + ) { + Text(stringResource(R.string.check_for_update)) + } } } } @@ -110,7 +126,7 @@ private fun ThemePicker( AlertDialog( onDismissRequest = onDismiss, - title = { Text(stringResource(R.string.theme)) }, + title = { Text(stringResource(R.string.theme_mode)) }, text = { Column { Theme.entries.forEach { 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 deleted file mode 100644 index af43ff93..00000000 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt +++ /dev/null @@ -1,81 +0,0 @@ -package app.revanced.manager.ui.screen.settings.update - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.stringResource -import app.revanced.manager.R -import app.revanced.manager.ui.component.AppTopBar -import app.revanced.manager.ui.component.ColumnWithScrollbar -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.koinViewModel - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun UpdatesSettingsScreen( - onBackClick: () -> Unit, - onChangelogClick: () -> Unit, - onUpdateClick: () -> Unit, - vm: UpdatesSettingsViewModel = koinViewModel(), -) { - val coroutineScope = rememberCoroutineScope() - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - - Scaffold( - topBar = { - AppTopBar( - title = stringResource(R.string.updates), - scrollBehavior = scrollBehavior, - onBackClick = onBackClick - ) - }, - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - ) { paddingValues -> - ColumnWithScrollbar( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - ) { - 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) - ) - - 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 - ) - - BooleanItem( - preference = vm.showManagerUpdateDialogOnLaunch, - headline = R.string.show_manager_update_dialog_on_launch, - 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/AdvancedSettingsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/AdvancedSettingsViewModel.kt index cbad0045..23644a48 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/AdvancedSettingsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/AdvancedSettingsViewModel.kt @@ -27,6 +27,7 @@ class AdvancedSettingsViewModel( private val app: Application, private val patchBundleRepository: PatchBundleRepository ) : ViewModel() { + val showManagerUpdateDialogOnLaunch = prefs.showManagerUpdateDialogOnLaunch val debugLogFileName: String get() { val time = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now()) @@ -34,7 +35,7 @@ class AdvancedSettingsViewModel( return "revanced-manager_logcat_$time" } - fun setApiUrl(value: String) = viewModelScope.launch(Dispatchers.Default) { + fun setApiSource(value: String) = viewModelScope.launch(Dispatchers.Default) { if (value == prefs.api.get()) return@launch prefs.api.update(value) diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/DeveloperOptionsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/DeveloperOptionsViewModel.kt index bc8d0527..bb7aebc9 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/DeveloperOptionsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/DeveloperOptionsViewModel.kt @@ -24,4 +24,10 @@ class DeveloperOptionsViewModel( fun resetBundles() = viewModelScope.launch { patchBundleRepository.reset() } + + fun disableSafeguard() = viewModelScope.launch { + prefs.allowIncompatibleMixing.update(true) + prefs.allowChangingPatchSelection.update(true) + prefs.allowUniversalPatch.update(true) + } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/GeneralSettingsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/GeneralSettingsViewModel.kt index ea15c757..466d98c8 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/GeneralSettingsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/GeneralSettingsViewModel.kt @@ -1,15 +1,38 @@ package app.revanced.manager.ui.viewmodel +import android.app.Application import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import app.revanced.manager.R import app.revanced.manager.domain.manager.PreferencesManager +import app.revanced.manager.network.api.ReVancedAPI import app.revanced.manager.ui.theme.Theme +import app.revanced.manager.util.toast +import app.revanced.manager.util.uiSafe import kotlinx.coroutines.launch class GeneralSettingsViewModel( - val prefs: PreferencesManager + val prefs: PreferencesManager, + private val app: Application, + private val reVancedAPI: ReVancedAPI, ) : ViewModel() { fun setTheme(theme: Theme) = viewModelScope.launch { prefs.theme.update(theme) } + + val managerAutoUpdates = prefs.managerAutoUpdates + val showManagerUpdateDialogOnLaunch = prefs.showManagerUpdateDialogOnLaunch + + 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/java/app/revanced/manager/ui/viewmodel/ImportExportViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/ImportExportViewModel.kt index 6fa042b7..347d55b5 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/ImportExportViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/ImportExportViewModel.kt @@ -103,7 +103,7 @@ class ImportExportViewModel( private suspend fun tryKeystoreImport(cn: String, pass: String, path: Path): Boolean { path.inputStream().use { stream -> if (keystoreManager.import(cn, pass, stream)) { - app.toast(app.getString(R.string.import_keystore_success)) + app.toast(app.getString(R.string.restore_keystore_success)) cancelKeystoreImport() return true } @@ -122,7 +122,7 @@ class ImportExportViewModel( fun exportKeystore(target: Uri) = viewModelScope.launch { keystoreManager.export(contentResolver.openOutputStream(target)!!) - app.toast(app.getString(R.string.export_keystore_success)) + app.toast(app.getString(R.string.backup_keystore_success)) } fun regenerateKeystore() = viewModelScope.launch { @@ -171,7 +171,7 @@ class ImportExportViewModel( override val activityArg = JSON_MIMETYPE override suspend fun execute(bundleUid: Int, location: Uri) = uiSafe( app, - R.string.import_patch_selection_fail, + R.string.restore_patch_selection_fail, "Failed to restore patch selection" ) { val selection = withContext(Dispatchers.IO) { @@ -181,7 +181,7 @@ class ImportExportViewModel( } selectionRepository.import(bundleUid, selection) - app.toast(app.getString(R.string.import_patch_selection_success)) + app.toast(app.getString(R.string.restore_patch_selection_success)) } } @@ -190,7 +190,7 @@ class ImportExportViewModel( override val activityArg = "selection.json" override suspend fun execute(bundleUid: Int, location: Uri) = uiSafe( app, - R.string.export_patch_selection_fail, + R.string.backup_patch_selection_fail, "Failed to backup patch selection" ) { val selection = selectionRepository.export(bundleUid) @@ -200,7 +200,7 @@ class ImportExportViewModel( Json.Default.encodeToStream(selection, it) } } - app.toast(app.getString(R.string.export_patch_selection_success)) + app.toast(app.getString(R.string.backup_patch_selection_success)) } } 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 dd87c084..a5e9b5e0 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 @@ -124,7 +124,7 @@ class MainViewModel( prefs.api.update(api.removeSuffix("/")) } settings.experimentalPatchesEnabled?.let { allowExperimental -> - prefs.disablePatchVersionCompatCheck.update(allowExperimental) + prefs.allowIncompatibleMixing.update(allowExperimental) } settings.patchesAutoUpdate?.let { autoUpdate -> with(patchBundleRepository) { diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt index 54f2b7b8..7e24b55c 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt @@ -58,13 +58,13 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi private set val allowIncompatiblePatches = - get().disablePatchVersionCompatCheck.getBlocking() + get().allowIncompatibleMixing.getBlocking() val bundlesFlow = get().bundleInfoFlow(packageName, input.app.version) init { viewModelScope.launch { - universalPatchWarningEnabled = !prefs.disableUniversalPatchWarning.get() + universalPatchWarningEnabled = !prefs.allowUniversalPatch.get() if (prefs.disableSelectionWarning.get()) { selectionWarningEnabled = false diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt index 302b5e89..33c30658 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt @@ -30,7 +30,6 @@ import app.revanced.manager.domain.repository.PatchOptionsRepository import app.revanced.manager.domain.repository.PatchSelectionRepository import app.revanced.manager.network.downloader.LoadedDownloaderPlugin import app.revanced.manager.network.downloader.ParceledDownloaderData -import app.revanced.manager.patcher.patch.PatchInfo import app.revanced.manager.plugin.downloader.GetScope import app.revanced.manager.plugin.downloader.PluginHostApi import app.revanced.manager.plugin.downloader.UserInteractionException @@ -118,7 +117,7 @@ class SelectedAppInfoViewModel( } val requiredVersion = combine( - prefs.suggestedVersionSafeguard.flow, + prefs.allowIncompatibleMixing.flow, bundleRepository.suggestedVersions ) { suggestedVersionSafeguard, suggestedVersions -> if (!suggestedVersionSafeguard) return@combine null @@ -275,7 +274,7 @@ class SelectedAppInfoViewModel( ) suspend fun getPatcherParams(): Patcher.ViewModelParams { - val allowUnsupported = prefs.disablePatchVersionCompatCheck.get() + val allowUnsupported = prefs.allowIncompatibleMixing.get() val bundles = bundleInfoFlow.first() return Patcher.ViewModelParams( selectedApp, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fab90c15..542ecce1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,8 @@ CLI Manager + ReVanced Patcher + ReVanced Manager plugin host Used to control access to ReVanced Manager plugins. Only ReVanced Manager has this. @@ -66,71 +68,73 @@ These settings can be changed later. General - Theme, dynamic color + Appearances, Updates + Updates Check for updates and view changelogs - Downloads - Downloader plugins and downloaded apps - Import & export - Keystore, patch options and selection + + Extensions + Downloader plugins, downloaded apps + Backup & Restore + Keystore, Patch selections, Patch options Advanced - API URL, memory limit, debugging + API Source, memory limits, debug logs + Experimental features + + About Open source licenses View all the libraries used to make this application Contributors View the contributors of ReVanced - Dynamic color - Adapt colors to the wallpaper - Theme - Choose between light or dark theme + Personalized color + Use color provided by your device\'s palette + Theme mode + Choose between light, dark, and system provided mode Safeguards - Disable version compatibility check - The check restricts patches to supported app versions - Selecting incompatible patches can result in a broken app.\n\nDo you want to proceed anyways? - Require suggested app version - Enforce selection of the suggested app version - Selecting an app that is not the suggested version may cause unexpected issues.\n\nDo you want to proceed anyways? + Allow unsupported compatibility + Permit apps and patches to be mixed in unsupported state + Selecting incompatible patches can result in a broken app.\n\nAllow anyways? Allow changing patch selection - Do not prevent selecting or deselecting patches - Changing the selection of patches may cause unexpected issues.\n\nEnable anyways? - Disable universal patch warning - Disables the warning that appears when you try to select universal patches - Universal patches are not as well tested as those that target specific apps.\n\nEnable anyways? - Import keystore - Import a custom keystore - Enter keystore credentials - You\'ll need enter the keystore’s credentials to import it. - Username (Alias) - Password - Import - Wrong keystore credentials - Imported keystore - Export keystore - Export the current keystore - No keystore to export - Exported keystore - Regenerate keystore - Generate a new keystore + Permit selecting or deselecting patches from default + Changing the selection of patches may cause unexpected issues.\n\nAllow anyways? + Allow universal patch + Permit selecting app\'s generic patch + Universal patch are not as well tested as those that target specific apps.\n\nAllow anyways? + Backup + Restore + Keystore + Patch selection + Patch options + Restore keystore from external source + Enter keystore credentials + You\'ll need enter the keystore’s credentials to restore it. + Username (Alias) + Password + Import + Wrong keystore credentials + Keystore successfully restored + Export app’s keystore into usable file + No keystore to backup + Keystore successfully backed up + Regeneration + Replace current keystore with new one The keystore has been successfully replaced - Import patch selection - Import patch selection from a JSON file - Could not import patch selection: %s - Imported patch selection - Export patch selection - Export patch selection to a JSON file - Could not export patch selection: %s - Exported patch selection + Export app’s patch selections into usable file + Could not import patch selection: %s + Patch selection successfully restored + Import app’s patch selections from external source + Could not backup patch selection: %s + Patch selection successfully restored Reset patch selection Reset the stored patch selection - Patch selection has been reset - Reset patch options for app - Resets patch options for a single app - Resets patch options for bundle - Resets patch options for all patches in a bundle - Reset patch options - Resets all patch options + Patch selection successfully reset + Reset for app + Use the default patch options configuration for a single app + Reset for bundle + Use the default patch options configuration for all patches in a bundle + Use the default patch options configuration Plugins Trusted Failed to load. Click for more details @@ -170,21 +174,22 @@ Dark Appearance Downloaded apps - Run Patcher in another process (experimental) - This is faster and allows Patcher to use more memory. + Run patcher in another process + Faster and allows patcher to use more memory Patcher process memory limit - The max amount of memory that the Patcher process can use (in megabytes) + The max amount of memory that the patcher process can use (in megabytes) Export debug logs Failed to read logs (exit code %d) Failed to export logs Exported logs - API URL - The API used to download necessary files. - Set custom API URL - Set the API URL of ReVanced Manager. ReVanced Manager uses the API to download patches and updates. - ReVanced Manager connects to the API to download patches and updates. Make sure that you trust it. - Set - Reset API URL + API Source + Set custom API Source + Set the API Source of ReVanced Manager. ReVanced Manager uses the API to download patches and updates. + ReVanced Manager connects to the API to download patches and updates. Make sure that you trust it. + Set + Reset API Source + Testing + Disable all safeguards Device Android version Model @@ -341,10 +346,8 @@ Ready to install update Update installed Failed to install update - Check for updates - Manually check for updates - Auto check for updates - Check for new versions of ReVanced Manager when the application starts + Check for update + Automatically check for new version when the app launched View changelogs Loading changelog Failed to download changelog: %s