feat: Settings v2

Signed-off-by: validcube <pun.butrach@gmail.com>
This commit is contained in:
validcube 2025-02-02 00:38:34 +07:00
parent e10e5e4e3f
commit c012a3e9c0
No known key found for this signature in database
GPG Key ID: DBA94253E1D3F267
22 changed files with 330 additions and 308 deletions

View File

@ -47,7 +47,6 @@ android {
isPseudoLocalesEnabled = true isPseudoLocalesEnabled = true
} }
buildConfigField("long", "BUILD_ID", "0L") buildConfigField("long", "BUILD_ID", "0L")
} }
} }

View File

@ -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.DeveloperOptionsScreen
import app.revanced.manager.ui.screen.settings.DownloadsSettingsScreen import app.revanced.manager.ui.screen.settings.DownloadsSettingsScreen
import app.revanced.manager.ui.screen.settings.GeneralSettingsScreen 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.LicensesScreen
import app.revanced.manager.ui.screen.settings.update.ChangelogsScreen import app.revanced.manager.ui.screen.settings.update.ChangelogsScreen
import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen
@ -290,7 +290,7 @@ private fun ReVancedManager(vm: MainViewModel) {
} }
composable<Settings.ImportExport> { composable<Settings.ImportExport> {
ImportExportSettingsScreen(onBackClick = navController::popBackStack) BackupRestoreSettingsScreen(onBackClick = navController::popBackStack)
} }
composable<Settings.About> { composable<Settings.About> {

View File

@ -22,10 +22,9 @@ class PreferencesManager(
val managerAutoUpdates = booleanPreference("manager_auto_updates", false) val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
val showManagerUpdateDialogOnLaunch = booleanPreference("show_manager_update_dialog_on_launch", true) val showManagerUpdateDialogOnLaunch = booleanPreference("show_manager_update_dialog_on_launch", true)
val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", false) val allowIncompatibleMixing = booleanPreference("allow_incompatible_mixing", false)
val disableSelectionWarning = booleanPreference("disable_selection_warning", false) val allowChangingPatchSelection = booleanPreference("allow_changing_patch_selection", false)
val disableUniversalPatchWarning = booleanPreference("disable_universal_patch_warning", false) val allowUniversalPatch = booleanPreference("allow_universal_patch", false)
val suggestedVersionSafeguard = booleanPreference("suggested_version_safeguard", true)
val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet()) val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet())
} }

View File

@ -76,7 +76,7 @@ class PatchBundleRepository(
suspend fun isVersionAllowed(packageName: String, version: String) = suspend fun isVersionAllowed(packageName: String, version: String) =
withContext(Dispatchers.Default) { 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 val suggestedVersion = suggestedVersions.first()[packageName] ?: return@withContext true
suggestedVersion == version suggestedVersion == version

View File

@ -66,7 +66,7 @@ fun SelectedAppInfoScreen(
val version = vm.selectedApp.version val version = vm.selectedApp.version
val bundles by vm.bundleInfoFlow.collectAsStateWithLifecycle(emptyList()) 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) { val patches = remember(bundles, allowIncompatiblePatches) {
vm.getPatches(bundles, allowIncompatiblePatches) vm.getPatches(bundles, allowIncompatiblePatches)
} }

View File

@ -22,18 +22,13 @@ private val settingsSections = listOf(
Icons.Outlined.Settings Icons.Outlined.Settings
) to Settings.General, ) to Settings.General,
Triple( Triple(
R.string.updates, R.string.extensions,
R.string.updates_description, R.string.extensions_description,
Icons.Outlined.Update
) to Settings.Updates,
Triple(
R.string.downloads,
R.string.downloads_description,
Icons.Outlined.Download Icons.Outlined.Download
) to Settings.Downloads, ) to Settings.Downloads,
Triple( Triple(
R.string.import_export, R.string.backup_restore,
R.string.import_export_description, R.string.backup_restore_description,
Icons.Outlined.SwapVert Icons.Outlined.SwapVert
) to Settings.ImportExport, ) to Settings.ImportExport,
Triple( Triple(

View File

@ -51,8 +51,9 @@ import org.koin.androidx.compose.koinViewModel
@Composable @Composable
fun AboutSettingsScreen( fun AboutSettingsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
onChangelogClick: () -> Unit,
navigate: (Settings.Destination) -> Unit, navigate: (Settings.Destination) -> Unit,
viewModel: AboutViewModel = koinViewModel() vm: AboutViewModel = koinViewModel()
) { ) {
val context = LocalContext.current val context = LocalContext.current
// painterResource() is broken on release builds for some reason. // painterResource() is broken on release builds for some reason.
@ -60,11 +61,11 @@ fun AboutSettingsScreen(
AppCompatResources.getDrawable(context, R.drawable.ic_logo_ring) AppCompatResources.getDrawable(context, R.drawable.ic_logo_ring)
}) })
val (preferredSocials, socials) = remember(viewModel.socials) { val (preferredSocials, socials) = remember(vm.socials) {
viewModel.socials.partition(ReVancedSocial::preferred) vm.socials.partition(ReVancedSocial::preferred)
} }
val preferredSocialButtons = remember(preferredSocials, viewModel.donate, viewModel.contact) { val preferredSocialButtons = remember(preferredSocials, vm.donate, vm.contact) {
preferredSocials.map { preferredSocials.map {
Triple( Triple(
getSocialIcon(it.name), getSocialIcon(it.name),
@ -74,7 +75,7 @@ fun AboutSettingsScreen(
} }
) )
} + listOfNotNull( } + listOfNotNull(
viewModel.donate?.let { vm.donate?.let {
Triple( Triple(
Icons.Outlined.FavoriteBorder, Icons.Outlined.FavoriteBorder,
context.getString(R.string.donate), context.getString(R.string.donate),
@ -83,7 +84,7 @@ fun AboutSettingsScreen(
} }
) )
}, },
viewModel.contact?.let { vm.contact?.let {
Triple( Triple(
Icons.Outlined.MailOutline, Icons.Outlined.MailOutline,
context.getString(R.string.contact), context.getString(R.string.contact),
@ -108,11 +109,18 @@ fun AboutSettingsScreen(
} }
val listItems = listOfNotNull( 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), stringResource(R.string.submit_feedback_description),
third = { third = {
context.openUrl("https://github.com/ReVanced/revanced-manager/issues/new/choose") context.openUrl("https://github.com/ReVanced/revanced-manager/issues/new/choose")
}), },
),
Triple( Triple(
stringResource(R.string.contributors), stringResource(R.string.contributors),
stringResource(R.string.contributors_description), stringResource(R.string.contributors_description),

View File

@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Api import androidx.compose.material.icons.outlined.Api
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Restore import androidx.compose.material.icons.outlined.Restore
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -89,73 +90,81 @@ fun AdvancedSettingsScreen(
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .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() if (showApiSourceDialog) {
var showApiUrlDialog by rememberSaveable { mutableStateOf(false) } APISourceDialog(
currentUrl = apiSource,
if (showApiUrlDialog) {
APIUrlDialog(
currentUrl = apiUrl,
defaultUrl = vm.prefs.api.default, defaultUrl = vm.prefs.api.default,
onSubmit = { onSubmit = {
showApiUrlDialog = false showApiSourceDialog = false
it?.let(vm::setApiUrl) 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)) GroupHeader(stringResource(R.string.revanced_patcher))
BooleanItem(
preference = vm.prefs.useProcessRuntime,
coroutineScope = vm.viewModelScope,
headline = R.string.process_runtime,
description = R.string.process_runtime_description,
)
IntegerItem( IntegerItem(
preference = vm.prefs.patcherProcessMemoryLimit, preference = vm.prefs.patcherProcessMemoryLimit,
coroutineScope = vm.viewModelScope, coroutineScope = vm.viewModelScope,
headline = R.string.process_runtime_memory_limit, 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)) GroupHeader(stringResource(R.string.manager))
SafeguardBooleanItem( SettingsListItem(
preference = vm.prefs.disablePatchVersionCompatCheck, headlineContent = stringResource(R.string.api_source),
coroutineScope = vm.viewModelScope, supportingContent = apiSource,
headline = R.string.patch_compat_check, modifier = Modifier.clickable {
description = R.string.patch_compat_check_description, showApiSourceDialog = true
confirmationText = R.string.patch_compat_check_confirmation },
trailingContent = {
IconButton(onClick = { showApiSourceDialog = true }) {
Icon(
Icons.Outlined.Edit,
contentDescription = stringResource(R.string.edit)
)
}
}
) )
SafeguardBooleanItem( 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, coroutineScope = vm.viewModelScope,
headline = R.string.universal_patches_safeguard, headline = R.string.universal_patches_safeguard,
description = R.string.universal_patches_safeguard_description, description = R.string.universal_patches_safeguard_description,
confirmationText = R.string.universal_patches_safeguard_confirmation confirmationText = R.string.universal_patches_safeguard_confirmation
) )
SafeguardBooleanItem( SafeguardBooleanItem(
preference = vm.prefs.suggestedVersionSafeguard, preference = vm.prefs.allowChangingPatchSelection,
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,
coroutineScope = vm.viewModelScope, coroutineScope = vm.viewModelScope,
headline = R.string.patch_selection_safeguard, headline = R.string.patch_selection_safeguard,
description = R.string.patch_selection_safeguard_description, description = R.string.patch_selection_safeguard_description,
confirmationText = R.string.patch_selection_safeguard_confirmation 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)) GroupHeader(stringResource(R.string.debugging))
val exportDebugLogsLauncher = val exportDebugLogsLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) {
@ -167,11 +176,10 @@ fun AdvancedSettingsScreen(
) )
val clipboard = remember { context.getSystemService<ClipboardManager>()!! } val clipboard = remember { context.getSystemService<ClipboardManager>()!! }
val deviceContent = """ val deviceContent = """
Version: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) Version: ${BuildConfig.VERSION_NAME}, ${BuildConfig.BUILD_TYPE} (${BuildConfig.VERSION_CODE})
Build type: ${BuildConfig.BUILD_TYPE}
Model: ${Build.MODEL} Model: ${Build.MODEL}
Android version: ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT}) Android version: ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})
Supported Archs: ${Build.SUPPORTED_ABIS.joinToString(", ")} Preferred Architectures: ${Build.SUPPORTED_ABIS.joinToString(", ")}
Memory limit: $memoryLimit Memory limit: $memoryLimit
""".trimIndent() """.trimIndent()
SettingsListItem( SettingsListItem(
@ -194,18 +202,18 @@ fun AdvancedSettingsScreen(
} }
@Composable @Composable
private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (String?) -> Unit) { private fun APISourceDialog(currentUrl: String, defaultUrl: String, onSubmit: (String?) -> Unit) {
var url by rememberSaveable(currentUrl) { mutableStateOf(currentUrl) } var source by rememberSaveable(currentUrl) { mutableStateOf(currentUrl) }
AlertDialog( AlertDialog(
onDismissRequest = { onSubmit(null) }, onDismissRequest = { onSubmit(null) },
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
onSubmit(url) onSubmit(source)
} }
) { ) {
Text(stringResource(R.string.api_url_dialog_save)) Text(stringResource(R.string.api_source_dialog_save))
} }
}, },
dismissButton = { dismissButton = {
@ -218,7 +226,7 @@ private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (Stri
}, },
title = { title = {
Text( 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), style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center),
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
) )
@ -228,23 +236,23 @@ private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (Stri
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Text( Text(
text = stringResource(R.string.api_url_dialog_description), text = stringResource(R.string.api_source_dialog_description),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
Text( Text(
text = stringResource(R.string.api_url_dialog_warning), text = stringResource(R.string.api_source_dialog_warning),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error color = MaterialTheme.colorScheme.error
) )
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
value = url, value = source,
onValueChange = { url = it }, onValueChange = { source = it },
label = { Text(stringResource(R.string.api_url)) }, label = { Text(stringResource(R.string.api_source)) },
trailingIcon = { trailingIcon = {
IconButton(onClick = { url = defaultUrl }) { IconButton(onClick = { source = defaultUrl }) {
Icon(Icons.Outlined.Restore, stringResource(R.string.api_url_dialog_reset)) Icon(Icons.Outlined.Restore, stringResource(R.string.api_source_dialog_reset))
} }
} }
) )

View File

@ -32,6 +32,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -54,7 +55,7 @@ import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ImportExportSettingsScreen( fun BackupRestoreSettingsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
vm: ImportExportViewModel = koinViewModel() vm: ImportExportViewModel = koinViewModel()
) { ) {
@ -100,7 +101,7 @@ fun ImportExportSettingsScreen(
vm.viewModelScope.launch { vm.viewModelScope.launch {
uiSafe(context, R.string.failed_to_import_keystore, "Failed to import keystore") { uiSafe(context, R.string.failed_to_import_keystore, "Failed to import keystore") {
val result = vm.tryKeystoreImport(cn, pass) 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( Scaffold(
topBar = { topBar = {
AppTopBar( AppTopBar(
title = stringResource(R.string.import_export), title = stringResource(R.string.backup_restore),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
onBackClick = onBackClick onBackClick = onBackClick
) )
@ -147,63 +148,89 @@ fun ImportExportSettingsScreen(
} }
} }
GroupHeader(stringResource(R.string.import_)) GroupHeader(stringResource(R.string.keystore))
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))
GroupItem( GroupItem(
onClick = { onClick = {
if (!vm.canExport()) { if (!vm.canExport()) {
context.toast(context.getString(R.string.export_keystore_unavailable)) context.toast(context.getString(R.string.backup_keystore_unavailable))
return@GroupItem return@GroupItem
} }
exportKeystoreLauncher.launch("Manager.keystore") exportKeystoreLauncher.launch("Manager.keystore")
}, },
headline = R.string.export_keystore, headline = R.string.backup,
description = R.string.export_keystore_description description = R.string.backup_keystore_description
) )
GroupItem( GroupItem(
onClick = vm::exportSelection, onClick = {
headline = R.string.export_patch_selection, importKeystoreLauncher.launch("*/*")
description = R.string.export_patch_selection_description },
headline = R.string.restore,
description = R.string.restore_keystore_description
) )
GroupHeader(stringResource(R.string.reset))
GroupItem( GroupItem(
onClick = vm::regenerateKeystore, 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 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( GroupItem(
onClick = vm::resetSelection, // TODO: allow resetting selection for specific bundle or package name. 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 description = R.string.reset_patch_selection_description
) )
GroupHeader(stringResource(R.string.patch_options))
// TODO: patch options import/export.
GroupItem( GroupItem(
onClick = vm::resetOptions, // TODO: patch options import/export. onClick = vm::resetOptions,
headline = R.string.patch_options_reset_all, headline = {
Text(
stringResource(R.string.reset),
color = MaterialTheme.colorScheme.error
)
},
description = R.string.patch_options_reset_all_description, description = R.string.patch_options_reset_all_description,
) )
GroupItem( GroupItem(
onClick = { showPackageSelector = true }, 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 description = R.string.patch_options_reset_package_description
) )
if (patchBundles.size > 1) { if (patchBundles.size > 1) {
GroupItem( GroupItem(
onClick = { showBundleSelector = true }, 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, 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 @Composable
fun KeystoreCredentialsDialog( fun KeystoreCredentialsDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
@ -298,7 +338,7 @@ fun KeystoreCredentialsDialog(
onSubmit(cn, pass) onSubmit(cn, pass)
} }
) { ) {
Text(stringResource(R.string.import_keystore_dialog_button)) Text(stringResource(R.string.restore_keystore_dialog_button))
} }
}, },
dismissButton = { dismissButton = {
@ -311,7 +351,7 @@ fun KeystoreCredentialsDialog(
}, },
title = { title = {
Text( 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), style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center),
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
) )
@ -322,19 +362,19 @@ fun KeystoreCredentialsDialog(
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Text( Text(
text = stringResource(R.string.import_keystore_dialog_description), text = stringResource(R.string.restore_keystore_dialog_description),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
OutlinedTextField( OutlinedTextField(
value = cn, value = cn,
onValueChange = { cn = it }, onValueChange = { cn = it },
label = { Text(stringResource(R.string.import_keystore_dialog_alias_field)) } label = { Text(stringResource(R.string.restore_keystore_dialog_alias_field)) }
) )
PasswordField( PasswordField(
value = pass, value = pass,
onValueChange = { pass = it }, onValueChange = { pass = it },
label = { Text(stringResource(R.string.import_keystore_dialog_password_field)) } label = { Text(stringResource(R.string.restore_keystore_dialog_password_field)) }
) )
} }
} }

View File

@ -53,9 +53,9 @@ import org.koin.androidx.compose.koinViewModel
@Composable @Composable
fun ContributorScreen( fun ContributorScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
viewModel: ContributorViewModel = koinViewModel() vm: ContributorViewModel = koinViewModel()
) { ) {
val repositories = viewModel.repositories val repositories = vm.repositories
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold( Scaffold(

View File

@ -44,7 +44,13 @@ fun DeveloperOptionsScreen(
) )
SettingsListItem( SettingsListItem(
headlineContent = stringResource(R.string.patch_bundles_reset), 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)
) )
} }
} }

View File

@ -53,22 +53,22 @@ import java.security.MessageDigest
@Composable @Composable
fun DownloadsSettingsScreen( fun DownloadsSettingsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
viewModel: DownloadsViewModel = koinViewModel() vm: DownloadsViewModel = koinViewModel()
) { ) {
val pullRefreshState = rememberPullToRefreshState() val pullRefreshState = rememberPullToRefreshState()
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList()) val downloadedApps by vm.downloadedApps.collectAsStateWithLifecycle(emptyList())
val pluginStates by viewModel.downloaderPluginStates.collectAsStateWithLifecycle() val pluginStates by vm.downloaderPluginStates.collectAsStateWithLifecycle()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold( Scaffold(
topBar = { topBar = {
AppTopBar( AppTopBar(
title = stringResource(R.string.downloads), title = stringResource(R.string.extensions),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
onBackClick = onBackClick, onBackClick = onBackClick,
actions = { actions = {
if (viewModel.appSelection.isNotEmpty()) { if (vm.appSelection.isNotEmpty()) {
IconButton(onClick = { viewModel.deleteApps() }) { IconButton(onClick = { vm.deleteApps() }) {
Icon(Icons.Default.Delete, stringResource(R.string.delete)) Icon(Icons.Default.Delete, stringResource(R.string.delete))
} }
} }
@ -86,7 +86,7 @@ fun DownloadsSettingsScreen(
) { ) {
PullToRefreshDefaults.Indicator( PullToRefreshDefaults.Indicator(
state = pullRefreshState, state = pullRefreshState,
isRefreshing = viewModel.isRefreshingPlugins isRefreshing = vm.isRefreshingPlugins
) )
} }
@ -95,9 +95,9 @@ fun DownloadsSettingsScreen(
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .padding(paddingValues)
.pullToRefresh( .pullToRefresh(
isRefreshing = viewModel.isRefreshingPlugins, isRefreshing = vm.isRefreshingPlugins,
state = pullRefreshState, state = pullRefreshState,
onRefresh = viewModel::refreshPlugins onRefresh = vm::refreshPlugins
) )
) { ) {
item { item {
@ -115,7 +115,7 @@ fun DownloadsSettingsScreen(
val packageInfo = val packageInfo =
remember(packageName) { remember(packageName) {
viewModel.pm.getPackageInfo( vm.pm.getPackageInfo(
packageName packageName
) )
} ?: return@item } ?: return@item
@ -124,7 +124,7 @@ fun DownloadsSettingsScreen(
val signature = val signature =
remember(packageName) { remember(packageName) {
val androidSignature = val androidSignature =
viewModel.pm.getSignature(packageName) vm.pm.getSignature(packageName)
val hash = MessageDigest.getInstance("SHA-256") val hash = MessageDigest.getInstance("SHA-256")
.digest(androidSignature.toByteArray()) .digest(androidSignature.toByteArray())
hash.toHexString(format = HexFormat.UpperCase) hash.toHexString(format = HexFormat.UpperCase)
@ -140,7 +140,7 @@ fun DownloadsSettingsScreen(
), ),
onDismiss = ::dismiss, onDismiss = ::dismiss,
onConfirm = { onConfirm = {
viewModel.revokePluginTrust(packageName) vm.revokePluginTrust(packageName)
dismiss() dismiss()
} }
) )
@ -161,7 +161,7 @@ fun DownloadsSettingsScreen(
), ),
onDismiss = ::dismiss, onDismiss = ::dismiss,
onConfirm = { onConfirm = {
viewModel.trustPlugin(packageName) vm.trustPlugin(packageName)
dismiss() dismiss()
} }
) )
@ -201,17 +201,17 @@ fun DownloadsSettingsScreen(
GroupHeader(stringResource(R.string.downloaded_apps)) GroupHeader(stringResource(R.string.downloaded_apps))
} }
items(downloadedApps, key = { it.packageName to it.version }) { app -> items(downloadedApps, key = { it.packageName to it.version }) { app ->
val selected = app in viewModel.appSelection val selected = app in vm.appSelection
SettingsListItem( SettingsListItem(
modifier = Modifier.clickable { viewModel.toggleApp(app) }, modifier = Modifier.clickable { vm.toggleApp(app) },
headlineContent = app.packageName, headlineContent = app.packageName,
leadingContent = (@Composable { leadingContent = (@Composable {
HapticCheckbox( HapticCheckbox(
checked = selected, checked = selected,
onCheckedChange = { viewModel.toggleApp(app) } onCheckedChange = { vm.toggleApp(app) }
) )
}).takeIf { viewModel.appSelection.isNotEmpty() }, }).takeIf { vm.appSelection.isNotEmpty() },
supportingContent = app.version, supportingContent = app.version,
tonalElevation = if (selected) 8.dp else 0.dp tonalElevation = if (selected) 8.dp else 0.dp
) )

View File

@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Scaffold 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.component.settings.SettingsListItem
import app.revanced.manager.ui.theme.Theme import app.revanced.manager.ui.theme.Theme
import app.revanced.manager.ui.viewmodel.GeneralSettingsViewModel import app.revanced.manager.ui.viewmodel.GeneralSettingsViewModel
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject import org.koin.compose.koinInject
@ -42,16 +44,17 @@ import org.koin.compose.koinInject
@Composable @Composable
fun GeneralSettingsScreen( fun GeneralSettingsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
viewModel: GeneralSettingsViewModel = koinViewModel() vm: GeneralSettingsViewModel = koinViewModel(),
onUpdateClick: () -> Unit,
) { ) {
val prefs = viewModel.prefs val prefs = vm.prefs
val coroutineScope = viewModel.viewModelScope val coroutineScope = vm.viewModelScope
var showThemePicker by rememberSaveable { mutableStateOf(false) } var showThemePicker by rememberSaveable { mutableStateOf(false) }
if (showThemePicker) { if (showThemePicker) {
ThemePicker( ThemePicker(
onDismiss = { showThemePicker = false }, onDismiss = { showThemePicker = false },
onConfirm = { viewModel.setTheme(it) } onConfirm = { vm.setTheme(it) }
) )
} }
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
@ -76,10 +79,10 @@ fun GeneralSettingsScreen(
val theme by prefs.theme.getAsState() val theme by prefs.theme.getAsState()
SettingsListItem( SettingsListItem(
modifier = Modifier.clickable { showThemePicker = true }, modifier = Modifier.clickable { showThemePicker = true },
headlineContent = stringResource(R.string.theme), headlineContent = stringResource(R.string.theme_mode),
supportingContent = stringResource(R.string.theme_description), supportingContent = stringResource(R.string.theme_mode_description),
trailingContent = { trailingContent = {
FilledTonalButton( Button (
onClick = { onClick = {
showThemePicker = true showThemePicker = true
} }
@ -92,10 +95,23 @@ fun GeneralSettingsScreen(
BooleanItem( BooleanItem(
preference = prefs.dynamicColor, preference = prefs.dynamicColor,
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
headline = R.string.dynamic_color, headline = R.string.personalized_color,
description = R.string.dynamic_color_description 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( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.theme)) }, title = { Text(stringResource(R.string.theme_mode)) },
text = { text = {
Column { Column {
Theme.entries.forEach { Theme.entries.forEach {

View File

@ -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
)
}
}
}

View File

@ -27,6 +27,7 @@ class AdvancedSettingsViewModel(
private val app: Application, private val app: Application,
private val patchBundleRepository: PatchBundleRepository private val patchBundleRepository: PatchBundleRepository
) : ViewModel() { ) : ViewModel() {
val showManagerUpdateDialogOnLaunch = prefs.showManagerUpdateDialogOnLaunch
val debugLogFileName: String val debugLogFileName: String
get() { get() {
val time = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now()) val time = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now())
@ -34,7 +35,7 @@ class AdvancedSettingsViewModel(
return "revanced-manager_logcat_$time" 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 if (value == prefs.api.get()) return@launch
prefs.api.update(value) prefs.api.update(value)

View File

@ -24,4 +24,10 @@ class DeveloperOptionsViewModel(
fun resetBundles() = viewModelScope.launch { fun resetBundles() = viewModelScope.launch {
patchBundleRepository.reset() patchBundleRepository.reset()
} }
fun disableSafeguard() = viewModelScope.launch {
prefs.allowIncompatibleMixing.update(true)
prefs.allowChangingPatchSelection.update(true)
prefs.allowUniversalPatch.update(true)
}
} }

View File

@ -1,15 +1,38 @@
package app.revanced.manager.ui.viewmodel package app.revanced.manager.ui.viewmodel
import android.app.Application
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.domain.manager.PreferencesManager 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.ui.theme.Theme
import app.revanced.manager.util.toast
import app.revanced.manager.util.uiSafe
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class GeneralSettingsViewModel( class GeneralSettingsViewModel(
val prefs: PreferencesManager val prefs: PreferencesManager,
private val app: Application,
private val reVancedAPI: ReVancedAPI,
) : ViewModel() { ) : ViewModel() {
fun setTheme(theme: Theme) = viewModelScope.launch { fun setTheme(theme: Theme) = viewModelScope.launch {
prefs.theme.update(theme) 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
}
} }

View File

@ -103,7 +103,7 @@ class ImportExportViewModel(
private suspend fun tryKeystoreImport(cn: String, pass: String, path: Path): Boolean { private suspend fun tryKeystoreImport(cn: String, pass: String, path: Path): Boolean {
path.inputStream().use { stream -> path.inputStream().use { stream ->
if (keystoreManager.import(cn, pass, 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() cancelKeystoreImport()
return true return true
} }
@ -122,7 +122,7 @@ class ImportExportViewModel(
fun exportKeystore(target: Uri) = viewModelScope.launch { fun exportKeystore(target: Uri) = viewModelScope.launch {
keystoreManager.export(contentResolver.openOutputStream(target)!!) 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 { fun regenerateKeystore() = viewModelScope.launch {
@ -171,7 +171,7 @@ class ImportExportViewModel(
override val activityArg = JSON_MIMETYPE override val activityArg = JSON_MIMETYPE
override suspend fun execute(bundleUid: Int, location: Uri) = uiSafe( override suspend fun execute(bundleUid: Int, location: Uri) = uiSafe(
app, app,
R.string.import_patch_selection_fail, R.string.restore_patch_selection_fail,
"Failed to restore patch selection" "Failed to restore patch selection"
) { ) {
val selection = withContext(Dispatchers.IO) { val selection = withContext(Dispatchers.IO) {
@ -181,7 +181,7 @@ class ImportExportViewModel(
} }
selectionRepository.import(bundleUid, selection) 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 val activityArg = "selection.json"
override suspend fun execute(bundleUid: Int, location: Uri) = uiSafe( override suspend fun execute(bundleUid: Int, location: Uri) = uiSafe(
app, app,
R.string.export_patch_selection_fail, R.string.backup_patch_selection_fail,
"Failed to backup patch selection" "Failed to backup patch selection"
) { ) {
val selection = selectionRepository.export(bundleUid) val selection = selectionRepository.export(bundleUid)
@ -200,7 +200,7 @@ class ImportExportViewModel(
Json.Default.encodeToStream(selection, it) 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))
} }
} }

View File

@ -124,7 +124,7 @@ class MainViewModel(
prefs.api.update(api.removeSuffix("/")) prefs.api.update(api.removeSuffix("/"))
} }
settings.experimentalPatchesEnabled?.let { allowExperimental -> settings.experimentalPatchesEnabled?.let { allowExperimental ->
prefs.disablePatchVersionCompatCheck.update(allowExperimental) prefs.allowIncompatibleMixing.update(allowExperimental)
} }
settings.patchesAutoUpdate?.let { autoUpdate -> settings.patchesAutoUpdate?.let { autoUpdate ->
with(patchBundleRepository) { with(patchBundleRepository) {

View File

@ -58,13 +58,13 @@ class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.Vi
private set private set
val allowIncompatiblePatches = val allowIncompatiblePatches =
get<PreferencesManager>().disablePatchVersionCompatCheck.getBlocking() get<PreferencesManager>().allowIncompatibleMixing.getBlocking()
val bundlesFlow = val bundlesFlow =
get<PatchBundleRepository>().bundleInfoFlow(packageName, input.app.version) get<PatchBundleRepository>().bundleInfoFlow(packageName, input.app.version)
init { init {
viewModelScope.launch { viewModelScope.launch {
universalPatchWarningEnabled = !prefs.disableUniversalPatchWarning.get() universalPatchWarningEnabled = !prefs.allowUniversalPatch.get()
if (prefs.disableSelectionWarning.get()) { if (prefs.disableSelectionWarning.get()) {
selectionWarningEnabled = false selectionWarningEnabled = false

View File

@ -30,7 +30,6 @@ import app.revanced.manager.domain.repository.PatchOptionsRepository
import app.revanced.manager.domain.repository.PatchSelectionRepository import app.revanced.manager.domain.repository.PatchSelectionRepository
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.network.downloader.ParceledDownloaderData 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.GetScope
import app.revanced.manager.plugin.downloader.PluginHostApi import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.UserInteractionException import app.revanced.manager.plugin.downloader.UserInteractionException
@ -118,7 +117,7 @@ class SelectedAppInfoViewModel(
} }
val requiredVersion = combine( val requiredVersion = combine(
prefs.suggestedVersionSafeguard.flow, prefs.allowIncompatibleMixing.flow,
bundleRepository.suggestedVersions bundleRepository.suggestedVersions
) { suggestedVersionSafeguard, suggestedVersions -> ) { suggestedVersionSafeguard, suggestedVersions ->
if (!suggestedVersionSafeguard) return@combine null if (!suggestedVersionSafeguard) return@combine null
@ -275,7 +274,7 @@ class SelectedAppInfoViewModel(
) )
suspend fun getPatcherParams(): Patcher.ViewModelParams { suspend fun getPatcherParams(): Patcher.ViewModelParams {
val allowUnsupported = prefs.disablePatchVersionCompatCheck.get() val allowUnsupported = prefs.allowIncompatibleMixing.get()
val bundles = bundleInfoFlow.first() val bundles = bundleInfoFlow.first()
return Patcher.ViewModelParams( return Patcher.ViewModelParams(
selectedApp, selectedApp,

View File

@ -5,6 +5,8 @@
<string name="cli">CLI</string> <string name="cli">CLI</string>
<string name="manager">Manager</string> <string name="manager">Manager</string>
<string name="revanced_patcher">ReVanced Patcher</string>
<string name="plugin_host_permission_label">ReVanced Manager plugin host</string> <string name="plugin_host_permission_label">ReVanced Manager plugin host</string>
<string name="plugin_host_permission_description">Used to control access to ReVanced Manager plugins. Only ReVanced Manager has this.</string> <string name="plugin_host_permission_description">Used to control access to ReVanced Manager plugins. Only ReVanced Manager has this.</string>
@ -66,71 +68,73 @@
<string name="auto_updates_dialog_note">These settings can be changed later.</string> <string name="auto_updates_dialog_note">These settings can be changed later.</string>
<string name="general">General</string> <string name="general">General</string>
<string name="general_description">Theme, dynamic color</string> <string name="general_description">Appearances, Updates</string>
<string name="updates">Updates</string> <string name="updates">Updates</string>
<string name="updates_description">Check for updates and view changelogs</string> <string name="updates_description">Check for updates and view changelogs</string>
<string name="downloads">Downloads</string>
<string name="downloads_description">Downloader plugins and downloaded apps</string> <string name="extensions">Extensions</string>
<string name="import_export">Import &amp; export</string> <string name="extensions_description">Downloader plugins, downloaded apps</string>
<string name="import_export_description">Keystore, patch options and selection</string> <string name="backup_restore">Backup &amp; Restore</string>
<string name="backup_restore_description">Keystore, Patch selections, Patch options</string>
<string name="advanced">Advanced</string> <string name="advanced">Advanced</string>
<string name="advanced_description">API URL, memory limit, debugging</string> <string name="advanced_description">API Source, memory limits, debug logs</string>
<string name="experimental_features">Experimental features</string>
<string name="about">About</string> <string name="about">About</string>
<string name="opensource_licenses">Open source licenses</string> <string name="opensource_licenses">Open source licenses</string>
<string name="opensource_licenses_description">View all the libraries used to make this application</string> <string name="opensource_licenses_description">View all the libraries used to make this application</string>
<string name="contributors">Contributors</string> <string name="contributors">Contributors</string>
<string name="contributors_description">View the contributors of ReVanced</string> <string name="contributors_description">View the contributors of ReVanced</string>
<string name="dynamic_color">Dynamic color</string> <string name="personalized_color">Personalized color</string>
<string name="dynamic_color_description">Adapt colors to the wallpaper</string> <string name="personalized_color_description">Use color provided by your device\'s palette</string>
<string name="theme">Theme</string> <string name="theme_mode">Theme mode</string>
<string name="theme_description">Choose between light or dark theme</string> <string name="theme_mode_description">Choose between light, dark, and system provided mode</string>
<string name="safeguards">Safeguards</string> <string name="safeguards">Safeguards</string>
<string name="patch_compat_check">Disable version compatibility check</string> <string name="allow_compatibility_mixing">Allow unsupported compatibility</string>
<string name="patch_compat_check_description">The check restricts patches to supported app versions</string> <string name="allow_compatibility_mixing_description">Permit apps and patches to be mixed in unsupported state</string>
<string name="patch_compat_check_confirmation">Selecting incompatible patches can result in a broken app.\n\nDo you want to proceed anyways?</string> <string name="allow_compatibility_mixing_confirmation">Selecting incompatible patches can result in a broken app.\n\nAllow anyways?</string>
<string name="suggested_version_safeguard">Require suggested app version</string>
<string name="suggested_version_safeguard_description">Enforce selection of the suggested app version</string>
<string name="suggested_version_safeguard_confirmation">Selecting an app that is not the suggested version may cause unexpected issues.\n\nDo you want to proceed anyways?</string>
<string name="patch_selection_safeguard">Allow changing patch selection</string> <string name="patch_selection_safeguard">Allow changing patch selection</string>
<string name="patch_selection_safeguard_description">Do not prevent selecting or deselecting patches</string> <string name="patch_selection_safeguard_description">Permit selecting or deselecting patches from default</string>
<string name="patch_selection_safeguard_confirmation">Changing the selection of patches may cause unexpected issues.\n\nEnable anyways?</string> <string name="patch_selection_safeguard_confirmation">Changing the selection of patches may cause unexpected issues.\n\nAllow anyways?</string>
<string name="universal_patches_safeguard">Disable universal patch warning</string> <string name="universal_patches_safeguard">Allow universal patch</string>
<string name="universal_patches_safeguard_description">Disables the warning that appears when you try to select universal patches</string> <string name="universal_patches_safeguard_description">Permit selecting app\'s generic patch</string>
<string name="universal_patches_safeguard_confirmation">Universal patches are not as well tested as those that target specific apps.\n\nEnable anyways?</string> <string name="universal_patches_safeguard_confirmation">Universal patch are not as well tested as those that target specific apps.\n\nAllow anyways?</string>
<string name="import_keystore">Import keystore</string> <string name="backup">Backup</string>
<string name="import_keystore_description">Import a custom keystore</string> <string name="restore">Restore</string>
<string name="import_keystore_dialog_title">Enter keystore credentials</string> <string name="keystore">Keystore</string>
<string name="import_keystore_dialog_description">You\'ll need enter the keystores credentials to import it.</string> <string name="patch_selection">Patch selection</string>
<string name="import_keystore_dialog_alias_field">Username (Alias)</string> <string name="patch_options">Patch options</string>
<string name="import_keystore_dialog_password_field">Password</string> <string name="restore_keystore_description">Restore keystore from external source</string>
<string name="import_keystore_dialog_button">Import</string> <string name="restore_keystore_dialog_title">Enter keystore credentials</string>
<string name="import_keystore_wrong_credentials">Wrong keystore credentials</string> <string name="restore_keystore_dialog_description">You\'ll need enter the keystores credentials to restore it.</string>
<string name="import_keystore_success">Imported keystore</string> <string name="restore_keystore_dialog_alias_field">Username (Alias)</string>
<string name="export_keystore">Export keystore</string> <string name="restore_keystore_dialog_password_field">Password</string>
<string name="export_keystore_description">Export the current keystore</string> <string name="restore_keystore_dialog_button">Import</string>
<string name="export_keystore_unavailable">No keystore to export</string> <string name="restore_keystore_wrong_credentials">Wrong keystore credentials</string>
<string name="export_keystore_success">Exported keystore</string> <string name="restore_keystore_success">Keystore successfully restored</string>
<string name="regenerate_keystore">Regenerate keystore</string> <string name="backup_keystore_description">Export apps keystore into usable file</string>
<string name="regenerate_keystore_description">Generate a new keystore</string> <string name="backup_keystore_unavailable">No keystore to backup</string>
<string name="backup_keystore_success">Keystore successfully backed up</string>
<string name="regenerate_keystore">Regeneration</string>
<string name="regenerate_keystore_description">Replace current keystore with new one</string>
<string name="regenerate_keystore_success">The keystore has been successfully replaced</string> <string name="regenerate_keystore_success">The keystore has been successfully replaced</string>
<string name="import_patch_selection">Import patch selection</string> <string name="restore_patch_selection_description">Export apps patch selections into usable file</string>
<string name="import_patch_selection_description">Import patch selection from a JSON file</string> <string name="restore_patch_selection_fail">Could not import patch selection: %s</string>
<string name="import_patch_selection_fail">Could not import patch selection: %s</string> <string name="restore_patch_selection_success">Patch selection successfully restored</string>
<string name="import_patch_selection_success">Imported patch selection</string> <string name="backup_patch_selection_description">Import apps patch selections from external source</string>
<string name="export_patch_selection">Export patch selection</string> <string name="backup_patch_selection_fail">Could not backup patch selection: %s</string>
<string name="export_patch_selection_description">Export patch selection to a JSON file</string> <string name="backup_patch_selection_success">Patch selection successfully restored</string>
<string name="export_patch_selection_fail">Could not export patch selection: %s</string>
<string name="export_patch_selection_success">Exported patch selection</string>
<string name="reset_patch_selection">Reset patch selection</string> <string name="reset_patch_selection">Reset patch selection</string>
<string name="reset_patch_selection_description">Reset the stored patch selection</string> <string name="reset_patch_selection_description">Reset the stored patch selection</string>
<string name="reset_patch_selection_success">Patch selection has been reset</string> <string name="reset_patch_selection_success">Patch selection successfully reset</string>
<string name="patch_options_reset_package">Reset patch options for app</string> <string name="patch_options_reset_package">Reset for app</string>
<string name="patch_options_reset_package_description">Resets patch options for a single app</string> <string name="patch_options_reset_package_description">Use the default patch options configuration for a single app</string>
<string name="patch_options_reset_bundle">Resets patch options for bundle</string> <string name="patch_options_reset_bundle">Reset for bundle</string>
<string name="patch_options_reset_bundle_description">Resets patch options for all patches in a bundle</string> <string name="patch_options_reset_bundle_description">Use the default patch options configuration for all patches in a bundle</string>
<string name="patch_options_reset_all">Reset patch options</string> <string name="patch_options_reset_all_description">Use the default patch options configuration</string>
<string name="patch_options_reset_all_description">Resets all patch options</string>
<string name="downloader_plugins">Plugins</string> <string name="downloader_plugins">Plugins</string>
<string name="downloader_plugin_state_trusted">Trusted</string> <string name="downloader_plugin_state_trusted">Trusted</string>
<string name="downloader_plugin_state_failed">Failed to load. Click for more details</string> <string name="downloader_plugin_state_failed">Failed to load. Click for more details</string>
@ -170,21 +174,22 @@
<string name="dark">Dark</string> <string name="dark">Dark</string>
<string name="appearance">Appearance</string> <string name="appearance">Appearance</string>
<string name="downloaded_apps">Downloaded apps</string> <string name="downloaded_apps">Downloaded apps</string>
<string name="process_runtime">Run Patcher in another process (experimental)</string> <string name="process_runtime">Run patcher in another process</string>
<string name="process_runtime_description">This is faster and allows Patcher to use more memory.</string> <string name="process_runtime_description">Faster and allows patcher to use more memory</string>
<string name="process_runtime_memory_limit">Patcher process memory limit</string> <string name="process_runtime_memory_limit">Patcher process memory limit</string>
<string name="process_runtime_memory_limit_description">The max amount of memory that the Patcher process can use (in megabytes)</string> <string name="process_runtime_memory_limit_description">The max amount of memory that the patcher process can use (in megabytes)</string>
<string name="debug_logs_export">Export debug logs</string> <string name="debug_logs_export">Export debug logs</string>
<string name="debug_logs_export_read_failed">Failed to read logs (exit code %d)</string> <string name="debug_logs_export_read_failed">Failed to read logs (exit code %d)</string>
<string name="debug_logs_export_failed">Failed to export logs</string> <string name="debug_logs_export_failed">Failed to export logs</string>
<string name="debug_logs_export_success">Exported logs</string> <string name="debug_logs_export_success">Exported logs</string>
<string name="api_url">API URL</string> <string name="api_source">API Source</string>
<string name="api_url_description">The API used to download necessary files.</string> <string name="api_source_dialog_title">Set custom API Source</string>
<string name="api_url_dialog_title">Set custom API URL</string> <string name="api_source_dialog_description">Set the API Source of ReVanced Manager. ReVanced Manager uses the API to download patches and updates.</string>
<string name="api_url_dialog_description">Set the API URL of ReVanced Manager. ReVanced Manager uses the API to download patches and updates.</string> <string name="api_source_dialog_warning">ReVanced Manager connects to the API to download patches and updates. Make sure that you trust it.</string>
<string name="api_url_dialog_warning">ReVanced Manager connects to the API to download patches and updates. Make sure that you trust it.</string> <string name="api_source_dialog_save">Set</string>
<string name="api_url_dialog_save">Set</string> <string name="api_source_dialog_reset">Reset API Source</string>
<string name="api_url_dialog_reset">Reset API URL</string> <string name="testing">Testing</string>
<string name="disable_safeguard">Disable all safeguards</string>
<string name="device">Device</string> <string name="device">Device</string>
<string name="device_android_version">Android version</string> <string name="device_android_version">Android version</string>
<string name="device_model">Model</string> <string name="device_model">Model</string>
@ -341,10 +346,8 @@
<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="manual_update_check">Check for updates</string> <string name="check_for_update">Check for update</string>
<string name="manual_update_check_description">Manually check for updates</string> <string name="check_for_update_auto_description">Automatically check for new version when the app launched</string>
<string name="update_checking_manager">Auto check for updates</string>
<string name="update_checking_manager_description">Check for new versions of ReVanced Manager when the application starts</string>
<string name="changelog">View changelogs</string> <string name="changelog">View changelogs</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>