diff --git a/app/src/main/java/app/revanced/manager/data/room/options/OptionDao.kt b/app/src/main/java/app/revanced/manager/data/room/options/OptionDao.kt index 5a147f6f..66c69b43 100644 --- a/app/src/main/java/app/revanced/manager/data/room/options/OptionDao.kt +++ b/app/src/main/java/app/revanced/manager/data/room/options/OptionDao.kt @@ -27,10 +27,10 @@ abstract class OptionDao { abstract suspend fun createOptionGroup(group: OptionGroup) @Query("DELETE FROM option_groups WHERE patch_bundle = :uid") - abstract suspend fun clearForPatchBundle(uid: Int) + abstract suspend fun resetOptionsForPatchBundle(uid: Int) @Query("DELETE FROM option_groups WHERE package_name = :packageName") - abstract suspend fun clearForPackage(packageName: String) + abstract suspend fun resetOptionsForPackage(packageName: String) @Query("DELETE FROM option_groups") abstract suspend fun reset() diff --git a/app/src/main/java/app/revanced/manager/data/room/selection/SelectionDao.kt b/app/src/main/java/app/revanced/manager/data/room/selection/SelectionDao.kt index 14ad1d87..93f87f51 100644 --- a/app/src/main/java/app/revanced/manager/data/room/selection/SelectionDao.kt +++ b/app/src/main/java/app/revanced/manager/data/room/selection/SelectionDao.kt @@ -5,6 +5,7 @@ import androidx.room.Insert import androidx.room.MapColumn import androidx.room.Query import androidx.room.Transaction +import kotlinx.coroutines.flow.Flow @Dao abstract class SelectionDao { @@ -34,11 +35,14 @@ abstract class SelectionDao { @Insert abstract suspend fun createSelection(selection: PatchSelection) + @Query("SELECT package_name FROM patch_selections") + abstract fun getPackagesWithSelection(): Flow> + @Query("DELETE FROM patch_selections WHERE patch_bundle = :uid") - abstract suspend fun clearForPatchBundle(uid: Int) + abstract suspend fun resetForPatchBundle(uid: Int) @Query("DELETE FROM patch_selections WHERE package_name = :packageName") - abstract suspend fun clearForPackage(packageName: String) + abstract suspend fun resetForPackage(packageName: String) @Query("DELETE FROM patch_selections") abstract suspend fun reset() diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchOptionsRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchOptionsRepository.kt index 9fe5fdc2..a2f79280 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchOptionsRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchOptionsRepository.kt @@ -76,7 +76,7 @@ class PatchOptionsRepository(db: AppDatabase) { fun getPackagesWithSavedOptions() = dao.getPackagesWithOptions().map(Iterable::toSet).distinctUntilChanged() - suspend fun clearOptionsForPackage(packageName: String) = dao.clearForPackage(packageName) - suspend fun clearOptionsForPatchBundle(uid: Int) = dao.clearForPatchBundle(uid) + suspend fun resetOptionsForPackage(packageName: String) = dao.resetOptionsForPackage(packageName) + suspend fun resetOptionsForPatchBundle(uid: Int) = dao.resetOptionsForPatchBundle(uid) suspend fun reset() = dao.reset() } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchSelectionRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchSelectionRepository.kt index c34e5efd..22f8187c 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchSelectionRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchSelectionRepository.kt @@ -3,6 +3,8 @@ package app.revanced.manager.domain.repository import app.revanced.manager.data.room.AppDatabase import app.revanced.manager.data.room.AppDatabase.Companion.generateUid import app.revanced.manager.data.room.selection.PatchSelection +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map class PatchSelectionRepository(db: AppDatabase) { private val dao = db.selectionDao() @@ -25,8 +27,15 @@ class PatchSelectionRepository(db: AppDatabase) { ) }) - suspend fun clearSelection(packageName: String) { - dao.clearForPackage(packageName) + fun getPackagesWithSavedSelection() = + dao.getPackagesWithSelection().map(Iterable::toSet).distinctUntilChanged() + + suspend fun resetSelectionForPackage(packageName: String) { + dao.resetForPackage(packageName) + } + + suspend fun resetSelectionForPatchBundle(uid: Int) { + dao.resetForPatchBundle(uid) } suspend fun reset() = dao.reset() @@ -34,7 +43,7 @@ class PatchSelectionRepository(db: AppDatabase) { suspend fun export(bundleUid: Int): SerializedSelection = dao.exportSelection(bundleUid) suspend fun import(bundleUid: Int, selection: SerializedSelection) { - dao.clearForPatchBundle(bundleUid) + dao.resetForPatchBundle(bundleUid) dao.updateSelections(selection.entries.associate { (packageName, patches) -> getOrCreateSelection(bundleUid, packageName) to patches.toSet() }) diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleSelector.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleSelector.kt index 8ea55e22..b4dbbd17 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleSelector.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleSelector.kt @@ -13,11 +13,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.revanced.manager.domain.bundles.PatchBundleSource import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState +import kotlinx.coroutines.flow.map @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -54,6 +57,10 @@ fun BundleSelector(bundles: List, onFinish: (PatchBundleSourc } bundles.forEach { val name by it.nameState + val version by remember(it) { + it.propsFlow().map { props -> props?.version } + }.collectAsStateWithLifecycle(null) + Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, @@ -65,7 +72,7 @@ fun BundleSelector(bundles: List, onFinish: (PatchBundleSourc } ) { Text( - name, + "$name $version", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface ) diff --git a/app/src/main/java/app/revanced/manager/ui/component/settings/SettingsListItem.kt b/app/src/main/java/app/revanced/manager/ui/component/settings/SettingsListItem.kt index 7c680477..b09c82a2 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/settings/SettingsListItem.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/settings/SettingsListItem.kt @@ -1,6 +1,17 @@ package app.revanced.manager.ui.component.settings +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ExpandLess +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material3.Icon import androidx.compose.material3.ListItemColors import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme @@ -10,6 +21,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.material3.ListItem +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue @Composable fun SettingsListItem( @@ -67,4 +82,48 @@ fun SettingsListItem( colors = colors, tonalElevation = tonalElevation, shadowElevation = shadowElevation -) \ No newline at end of file +) + +@Composable +fun ExpandableSettingListItem( + headlineContent: String, + supportingContent: String, + expandableContent: @Composable () -> Unit +) { + var expanded by remember { mutableStateOf(false) } + + Column( + modifier = Modifier + .fillMaxWidth() + .animateContentSize() + ) { + SettingsListItem( + modifier = Modifier + .clickable{ expanded = !expanded }, + headlineContent = headlineContent, + supportingContent = supportingContent, + trailingContent = { + Icon( + imageVector = if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, + contentDescription = null + ) + } + ) + + AnimatedVisibility(visible = expanded) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp, start = 16.dp, end = 16.dp) + .animateContentSize( + animationSpec = tween( + durationMillis = 500, + easing = FastOutSlowInEasing + ) + ) + ) { + expandableContent() + } + } + } +} 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/ImportExportSettingsScreen.kt index 7f8083f3..2d88bb37 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/ImportExportSettingsScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Key +import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -28,6 +29,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -42,11 +44,14 @@ import androidx.lifecycle.viewModelScope 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.ConfirmDialog import app.revanced.manager.ui.component.GroupHeader import app.revanced.manager.ui.component.PasswordField import app.revanced.manager.ui.component.bundle.BundleSelector +import app.revanced.manager.ui.component.settings.ExpandableSettingListItem import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.viewmodel.ImportExportViewModel +import app.revanced.manager.ui.viewmodel.ResetDialogState import app.revanced.manager.util.toast import app.revanced.manager.util.uiSafe import kotlinx.coroutines.launch @@ -59,6 +64,8 @@ fun ImportExportSettingsScreen( vm: ImportExportViewModel = koinViewModel() ) { val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + var selectorDialog by rememberSaveable { mutableStateOf<(@Composable () -> Unit)?>(null) } val importKeystoreLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { @@ -70,6 +77,7 @@ fun ImportExportSettingsScreen( } val patchBundles by vm.patchBundles.collectAsStateWithLifecycle(initialValue = emptyList()) + val packagesWithSelections by vm.packagesWithSelection.collectAsStateWithLifecycle(initialValue = emptySet()) val packagesWithOptions by vm.packagesWithOptions.collectAsStateWithLifecycle(initialValue = emptySet()) vm.selectionAction?.let { action -> @@ -107,6 +115,20 @@ fun ImportExportSettingsScreen( ) } + vm.resetDialogState?.let { + with(vm.resetDialogState!!) { + ConfirmDialog( + onDismiss = { vm.resetDialogState = null }, + onConfirm = onConfirm, + title = stringResource(titleResId), + description = dialogOptionName?.let { + stringResource(descriptionResId, it) + } ?: stringResource(descriptionResId), + icon = Icons.Outlined.WarningAmber + ) + } + } + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) Scaffold( @@ -124,28 +146,7 @@ fun ImportExportSettingsScreen( .fillMaxSize() .padding(paddingValues) ) { - var showPackageSelector by rememberSaveable { - mutableStateOf(false) - } - var showBundleSelector by rememberSaveable { - mutableStateOf(false) - } - - if (showPackageSelector) { - PackageSelector(packages = packagesWithOptions) { selected -> - selected?.let(vm::resetOptionsForPackage) - - showPackageSelector = false - } - } - - if (showBundleSelector) { - BundleSelector(bundles = patchBundles) { bundle -> - bundle?.let(vm::clearOptionsForBundle) - - showBundleSelector = false - } - } + selectorDialog?.invoke() GroupHeader(stringResource(R.string.import_)) GroupItem( @@ -181,32 +182,126 @@ fun ImportExportSettingsScreen( GroupHeader(stringResource(R.string.reset)) GroupItem( - onClick = vm::regenerateKeystore, + onClick = { + vm.resetDialogState = ResetDialogState.Keystore { + vm.regenerateKeystore() + } + }, headline = R.string.regenerate_keystore, description = R.string.regenerate_keystore_description ) - GroupItem( - onClick = vm::resetSelection, // TODO: allow resetting selection for specific bundle or package name. - headline = R.string.reset_patch_selection, - description = R.string.reset_patch_selection_description + + ExpandableSettingListItem( + headlineContent = stringResource(R.string.reset_patch_selection), + supportingContent = stringResource(R.string.reset_patch_selection_description), + expandableContent = { + GroupItem( + onClick = { + vm.resetDialogState = ResetDialogState.PatchSelectionAll { + vm.resetSelection() + } + }, + headline = R.string.patch_selection_reset_all, + description = R.string.patch_selection_reset_all_description + ) + + GroupItem( + onClick = { + selectorDialog = { + PackageSelector(packages = packagesWithSelections) { packageName -> + packageName?.also { + vm.resetDialogState = + ResetDialogState.PatchSelectionPackage(packageName) { + vm.resetSelectionForPackage(packageName) + } + } + selectorDialog = null + } + } + }, + headline = R.string.patch_selection_reset_package, + description = R.string.patch_selection_reset_package_description + ) + + if (patchBundles.isNotEmpty()) { + GroupItem( + onClick = { + selectorDialog = { + BundleSelector(bundles = patchBundles) { bundle -> + bundle?.also { + coroutineScope.launch { + vm.resetDialogState = + ResetDialogState.PatchSelectionBundle(bundle.getName()) { + vm.resetSelectionForPatchBundle(bundle) + } + } + } + selectorDialog = null + } + } + }, + headline = R.string.patch_selection_reset_bundle, + description = R.string.patch_selection_reset_bundle_description + ) + } + } ) - GroupItem( - onClick = vm::resetOptions, // TODO: patch options import/export. - headline = R.string.patch_options_reset_all, - description = R.string.patch_options_reset_all_description, + + ExpandableSettingListItem( + headlineContent = stringResource(R.string.reset_patch_options), + supportingContent = stringResource(R.string.reset_patch_options_description), + expandableContent = { + GroupItem( + onClick = { + vm.resetDialogState = ResetDialogState.PatchOptionsAll { + vm.resetOptions() + } + }, // TODO: patch options import/export. + headline = R.string.patch_options_reset_all, + description = R.string.patch_options_reset_all_description, + ) + + GroupItem( + onClick = { + selectorDialog = { + PackageSelector(packages = packagesWithOptions) { packageName -> + packageName?.also { + vm.resetDialogState = + ResetDialogState.PatchOptionPackage(packageName) { + vm.resetOptionsForPackage(packageName) + } + } + selectorDialog = null + } + } + }, + headline = R.string.patch_options_reset_package, + description = R.string.patch_options_reset_package_description + ) + + if (patchBundles.isNotEmpty()) { + GroupItem( + onClick = { + selectorDialog = { + BundleSelector(bundles = patchBundles) { bundle -> + bundle?.also { + coroutineScope.launch { + vm.resetDialogState = + ResetDialogState.PatchOptionBundle(bundle.getName()) { + vm.resetOptionsForBundle(bundle) + } + } + } + selectorDialog = null + } + } + }, + headline = R.string.patch_options_reset_bundle, + description = R.string.patch_options_reset_bundle_description, + ) + } + } ) - GroupItem( - onClick = { showPackageSelector = true }, - headline = R.string.patch_options_reset_package, - description = R.string.patch_options_reset_package_description - ) - if (patchBundles.size > 1) { - GroupItem( - onClick = { showBundleSelector = true }, - headline = R.string.patch_options_reset_bundle, - description = R.string.patch_options_reset_bundle_description, - ) - } } } } 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..3628f0d7 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 @@ -4,6 +4,7 @@ import android.app.Application import android.net.Uri import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.StringRes import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -34,6 +35,59 @@ import java.nio.file.StandardCopyOption import kotlin.io.path.deleteExisting import kotlin.io.path.inputStream +sealed class ResetDialogState( + @StringRes val titleResId: Int, + @StringRes val descriptionResId: Int, + val onConfirm: () -> Unit, + val dialogOptionName: String? = null +) { + class Keystore(onConfirm: () -> Unit) : ResetDialogState( + titleResId = R.string.regenerate_keystore, + descriptionResId = R.string.regenerate_keystore_dialog_description, + onConfirm = onConfirm + ) + + class PatchSelectionAll(onConfirm: () -> Unit) : ResetDialogState( + titleResId = R.string.patch_selection_reset_all, + descriptionResId = R.string.patch_selection_reset_all_dialog_description, + onConfirm = onConfirm + ) + + class PatchSelectionPackage(dialogOptionName:String, onConfirm: () -> Unit) : ResetDialogState( + titleResId = R.string.patch_selection_reset_package, + descriptionResId = R.string.patch_selection_reset_package_dialog_description, + onConfirm = onConfirm, + dialogOptionName = dialogOptionName + ) + + class PatchSelectionBundle(dialogOptionName: String, onConfirm: () -> Unit) : ResetDialogState( + titleResId = R.string.patch_selection_reset_bundle, + descriptionResId = R.string.patch_selection_reset_bundle_dialog_description, + onConfirm = onConfirm, + dialogOptionName = dialogOptionName + ) + + class PatchOptionsAll(onConfirm: () -> Unit) : ResetDialogState( + titleResId = R.string.patch_options_reset_all, + descriptionResId = R.string.patch_options_reset_all_dialog_description, + onConfirm = onConfirm + ) + + class PatchOptionPackage(dialogOptionName:String, onConfirm: () -> Unit) : ResetDialogState( + titleResId = R.string.patch_options_reset_package, + descriptionResId = R.string.patch_options_reset_package_dialog_description, + onConfirm = onConfirm, + dialogOptionName = dialogOptionName + ) + + class PatchOptionBundle(dialogOptionName: String, onConfirm: () -> Unit) : ResetDialogState( + titleResId = R.string.patch_options_reset_bundle, + descriptionResId = R.string.patch_options_reset_bundle_dialog_description, + onConfirm = onConfirm, + dialogOptionName = dialogOptionName + ) +} + @OptIn(ExperimentalSerializationApi::class) class ImportExportViewModel( private val app: Application, @@ -51,15 +105,18 @@ class ImportExportViewModel( private var keystoreImportPath by mutableStateOf(null) val showCredentialsDialog by derivedStateOf { keystoreImportPath != null } + var resetDialogState by mutableStateOf(null) + val packagesWithOptions = optionsRepository.getPackagesWithSavedOptions() + val packagesWithSelection = selectionRepository.getPackagesWithSavedSelection() fun resetOptionsForPackage(packageName: String) = viewModelScope.launch { - optionsRepository.clearOptionsForPackage(packageName) + optionsRepository.resetOptionsForPackage(packageName) app.toast(app.getString(R.string.patch_options_reset_toast)) } - fun clearOptionsForBundle(patchBundle: PatchBundleSource) = viewModelScope.launch { - optionsRepository.clearOptionsForPatchBundle(patchBundle.uid) + fun resetOptionsForBundle(patchBundle: PatchBundleSource) = viewModelScope.launch { + optionsRepository.resetOptionsForPatchBundle(patchBundle.uid) app.toast(app.getString(R.string.patch_options_reset_toast)) } @@ -135,6 +192,16 @@ class ImportExportViewModel( app.toast(app.getString(R.string.reset_patch_selection_success)) } + fun resetSelectionForPackage(packageName: String) = viewModelScope.launch { + selectionRepository.resetSelectionForPackage(packageName) + app.toast(app.getString(R.string.reset_patch_selection_success)) + } + + fun resetSelectionForPatchBundle(patchBundle: PatchBundleSource) = viewModelScope.launch { + selectionRepository.resetSelectionForPatchBundle(patchBundle.uid) + app.toast(app.getString(R.string.reset_patch_selection_success)) + } + fun executeSelectionAction(target: Uri) = viewModelScope.launch { val source = selectedBundle!! val action = selectionAction!! 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 4067a659..6f82fb6b 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 @@ -305,7 +305,7 @@ class SelectedAppInfoViewModel( if (!persistConfiguration) return@launch viewModelScope.launch(Dispatchers.Default) { selection?.let { selectionRepository.updateSelection(packageName, it) } - ?: selectionRepository.clearSelection(packageName) + ?: selectionRepository.resetSelectionForPackage(packageName) optionsRepository.saveOptions(packageName, filteredOptions) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a1ab4a8f..2148b082 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,6 +118,7 @@ Exported keystore Regenerate keystore Generate a new keystore + You are about to regenerate your keystore the manager will use during the patching process.\n\nYou will not be able to update the previously installed apps from this source. The keystore has been successfully replaced Import patch selection Import patch selection from a JSON file @@ -129,12 +130,26 @@ Exported patch selection Reset patch selection Reset the stored patch selection + Reset patch options + Reset the stored patch options Patch selection has been reset + Reset all patch selection + You are about to reset all the patch selections. You will need to manually select each patch again. + Reset all the patch selections + Reset patch selection for app + You are about to reset the patch selection for the app \"%s\". You will have to manually select each patch again. + Resets patch selection for a single app + Resets patch selection for bundle + You are about to reset the patch selection for the bundle \"%s\". You will have to manually select each patch again. + Resets the patch selection for all patches in a bundle Reset patch options for app + You are about to reset the patch options for the app \"%s\". You will have to reapply each option again. Resets patch options for a single app Resets patch options for bundle + You are about to reset the patch options for the bundle \"%s\". You will have to reapply each option again. Resets patch options for all patches in a bundle Reset patch options + You are about to reset patch options. You will have to reapply each option again. Resets all patch options Plugins Trusted