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 08b6a94d93..548df41a68 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 @@ -23,6 +23,7 @@ class PreferencesManager( val firstLaunch = booleanPreference("first_launch", true) 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) diff --git a/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt index 57810a05e4..2b04bd41c7 100644 --- a/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt +++ b/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt @@ -56,7 +56,7 @@ class EditorContext(private val prefs: MutablePreferences) { abstract class Preference( private val dataStore: DataStore, - protected val default: T + val default: T ) { internal abstract fun Preferences.read(): T internal abstract fun MutablePreferences.write(value: T) diff --git a/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt b/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt index f81ba2f406..406c9e9d1f 100644 --- a/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt +++ b/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt @@ -4,7 +4,7 @@ import android.content.Context import app.revanced.manager.patcher.LibraryResolver import android.os.Build.SUPPORTED_ABIS as DEVICE_ABIS object Aapt : LibraryResolver() { - private val WORKING_ABIS = setOf("arm64-v8a", "x86", "x86_64") + private val WORKING_ABIS = setOf("arm64-v8a", "x86", "x86_64", "armeabi-v7a") fun supportsDevice() = (DEVICE_ABIS intersect WORKING_ABIS).isNotEmpty() diff --git a/app/src/main/java/app/revanced/manager/ui/component/AlertDialogExtended.kt b/app/src/main/java/app/revanced/manager/ui/component/AlertDialogExtended.kt index cceb189f32..c2089d5811 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/AlertDialogExtended.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/AlertDialogExtended.kt @@ -55,7 +55,7 @@ fun AlertDialogExtended( ) { Column(modifier = Modifier.padding(vertical = 24.dp)) { Column( - modifier = Modifier.padding(horizontal = 24.dp) + modifier = Modifier.padding(horizontal = 24.dp).fillMaxWidth() ) { icon?.let { ContentStyle(color = iconContentColor) { diff --git a/app/src/main/java/app/revanced/manager/ui/component/AppIcon.kt b/app/src/main/java/app/revanced/manager/ui/component/AppIcon.kt index a308f17432..0d8cd822a0 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/AppIcon.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/AppIcon.kt @@ -33,11 +33,9 @@ fun AppIcon( Image( image, contentDescription, - Modifier.placeholder(visible = showPlaceHolder, color = MaterialTheme.colorScheme.inverseOnSurface, shape = RoundedCornerShape(100)).then(modifier), + modifier, colorFilter = colorFilter ) - - showPlaceHolder = false } else { AsyncImage( packageInfo, diff --git a/app/src/main/java/app/revanced/manager/ui/component/AvailableUpdateDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/AvailableUpdateDialog.kt new file mode 100644 index 0000000000..7059ad0da9 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/AvailableUpdateDialog.kt @@ -0,0 +1,81 @@ +package app.revanced.manager.ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Update +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.revanced.manager.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AvailableUpdateDialog( + onDismiss: () -> Unit, + onConfirm: () -> Unit, + setShowManagerUpdateDialogOnLaunch: (Boolean) -> Unit, + newVersion: String +) { + var dontShowAgain by rememberSaveable { mutableStateOf(false) } + val dismissDialog = { + setShowManagerUpdateDialogOnLaunch(!dontShowAgain) + onDismiss() + } + + AlertDialogExtended( + onDismissRequest = dismissDialog, + confirmButton = { + TextButton( + onClick = { + dismissDialog() + onConfirm() + } + ) { + Text(stringResource(R.string.show)) + } + }, + dismissButton = { + TextButton( + onClick = dismissDialog + ) { + Text(stringResource(R.string.dismiss)) + } + }, + icon = { + Icon(imageVector = Icons.Outlined.Update, contentDescription = null) + }, + title = { + Text(stringResource(R.string.update_available)) + }, + text = { + Column( + modifier = Modifier.padding(horizontal = 8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(R.string.update_available_dialog_description, newVersion) + ) + ListItem( + modifier = Modifier.clickable { dontShowAgain = !dontShowAgain }, + headlineContent = { + Text(stringResource(R.string.never_show_again)) + }, + leadingContent = { + CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { + Checkbox(checked = dontShowAgain, onCheckedChange = { dontShowAgain = it }) + } + } + ) + } + }, + textHorizontalPadding = PaddingValues(0.dp) + ) +} diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt index f2bb3de5c3..2b67860315 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt @@ -46,11 +46,6 @@ fun BaseBundleDialog( ColumnWithScrollbar( modifier = Modifier .fillMaxWidth() - .padding( - start = 8.dp, - top = 8.dp, - end = 4.dp, - ) .then(modifier) ) { if (name != null) { diff --git a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt index 1168182411..bf310fc71a 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt @@ -6,43 +6,17 @@ import android.net.Uri import android.provider.Settings import androidx.activity.compose.BackHandler import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.BatteryAlert import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.outlined.Apps -import androidx.compose.material.icons.outlined.DeleteOutline -import androidx.compose.material.icons.outlined.Refresh -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material.icons.outlined.Source -import androidx.compose.material.icons.outlined.Update -import androidx.compose.material.icons.outlined.WarningAmber -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Tab -import androidx.compose.material3.TabRow -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.surfaceColorAtElevation -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext @@ -55,6 +29,7 @@ import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefaul import app.revanced.manager.patcher.aapt.Aapt import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AutoUpdatesDialog +import app.revanced.manager.ui.component.AvailableUpdateDialog import app.revanced.manager.ui.component.NotificationCard import app.revanced.manager.ui.component.bundle.BundleItem import app.revanced.manager.ui.component.bundle.BundleTopBar @@ -113,6 +88,20 @@ fun DashboardScreen( ) } + var showDialog by rememberSaveable { mutableStateOf(vm.prefs.showManagerUpdateDialogOnLaunch.getBlocking()) } + val availableUpdate by remember { + derivedStateOf { vm.updatedManagerVersion.takeIf { showDialog } } + } + + availableUpdate?.let { version -> + AvailableUpdateDialog( + onDismiss = { showDialog = false }, + setShowManagerUpdateDialogOnLaunch = vm::setShowManagerUpdateDialogOnLaunch, + onConfirm = onUpdateClick, + newVersion = version + ) + } + Scaffold( topBar = { if (bundlesSelectable) { @@ -154,6 +143,23 @@ fun DashboardScreen( AppTopBar( title = stringResource(R.string.app_name), actions = { + if (!vm.updatedManagerVersion.isNullOrEmpty()) { + IconButton( + onClick = onUpdateClick, + ) { + BadgedBox( + badge = { + Badge( + // A size value above 6.dp forces the Badge icon to be closer to the center, fixing a clipping issue + modifier = Modifier.size(7.dp), + containerColor = MaterialTheme.colorScheme.primary, + ) + } + ) { + Icon(Icons.Outlined.Update, stringResource(R.string.update)) + } + } + } IconButton(onClick = onSettingsClick) { Icon(Icons.Outlined.Settings, stringResource(R.string.settings)) } @@ -230,23 +236,7 @@ fun DashboardScreen( } ) } - } else null, - vm.updatedManagerVersion?.let { - { - NotificationCard( - text = stringResource(R.string.update_available_dialog_description, it), - icon = Icons.Outlined.Update, - actions = { - TextButton(onClick = vm::dismissUpdateDialog) { - Text(stringResource(R.string.dismiss)) - } - TextButton(onClick = onUpdateClick) { - Text(stringResource(R.string.show)) - } - } - ) - } - } + } else null ) HorizontalPager( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt index e6a32f25c8..04632f1ba1 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt @@ -2,12 +2,7 @@ package app.revanced.manager.ui.screen import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items @@ -15,28 +10,8 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.HelpOutline -import androidx.compose.material.icons.outlined.FilterList -import androidx.compose.material.icons.outlined.Restore -import androidx.compose.material.icons.outlined.Save -import androidx.compose.material.icons.outlined.Search -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material.icons.outlined.WarningAmber -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Checkbox -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.FilterChip -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.ListItem -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Scaffold -import androidx.compose.material3.ScrollableTabRow -import androidx.compose.material3.Tab -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -57,8 +32,8 @@ import app.revanced.manager.R import app.revanced.manager.patcher.patch.Option import app.revanced.manager.patcher.patch.PatchInfo import app.revanced.manager.ui.component.AppTopBar -import app.revanced.manager.ui.component.SafeguardDialog import app.revanced.manager.ui.component.LazyColumnWithScrollbar +import app.revanced.manager.ui.component.SafeguardDialog import app.revanced.manager.ui.component.SearchView import app.revanced.manager.ui.component.patches.OptionItem import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel @@ -93,6 +68,21 @@ fun PatchesSelectorScreen( derivedStateOf { vm.selectionIsValid(bundles) } } + val availablePatchCount by remember { + derivedStateOf { + bundles.sumOf { it.patchCount } + } + } + + val defaultPatchSelectionCount by vm.defaultSelectionCount + .collectAsStateWithLifecycle(initialValue = 0) + + val selectedPatchCount by remember { + derivedStateOf { + vm.customPatchSelection?.values?.sumOf { it.size } ?: defaultPatchSelectionCount + } + } + val patchLazyListStates = remember(bundles) { List(bundles.size) { LazyListState() } } if (showBottomSheet) { @@ -142,11 +132,19 @@ fun PatchesSelectorScreen( } if (vm.compatibleVersions.isNotEmpty()) - UnsupportedDialog( + UnsupportedPatchDialog( appVersion = vm.appVersion, supportedVersions = vm.compatibleVersions, onDismissRequest = vm::dismissDialogs ) + var showUnsupportedPatchesDialog by rememberSaveable { + mutableStateOf(false) + } + if (showUnsupportedPatchesDialog) + UnsupportedPatchesDialog( + appVersion = vm.appVersion, + onDismissRequest = { showUnsupportedPatchesDialog = false } + ) vm.optionsDialog?.let { (bundle, patch) -> OptionsDialog( @@ -199,12 +197,20 @@ fun PatchesSelectorScreen( patch ), onToggle = { - if (vm.selectionWarningEnabled) { - showSelectionWarning = true - } else if (vm.universalPatchWarningEnabled && patch.compatiblePackages == null) { - vm.pendingUniversalPatchAction = { vm.togglePatch(uid, patch) } - } else { - vm.togglePatch(uid, patch) + when { + // Open unsupported dialog if the patch is not supported + !supported -> vm.openUnsupportedDialog(patch) + + // Show selection warning if enabled + vm.selectionWarningEnabled -> showSelectionWarning = true + + // Set pending universal patch action if the universal patch warning is enabled and there are no compatible packages + vm.universalPatchWarningEnabled && patch.compatiblePackages == null -> { + vm.pendingUniversalPatchAction = { vm.togglePatch(uid, patch) } + } + + // Toggle the patch otherwise + else -> vm.togglePatch(uid, patch) } }, supported = supported @@ -255,7 +261,7 @@ fun PatchesSelectorScreen( ) { ListHeader( title = stringResource(R.string.unsupported_patches), - onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) } + onHelpClick = { showUnsupportedPatchesDialog = true } ) } } @@ -265,7 +271,7 @@ fun PatchesSelectorScreen( Scaffold( topBar = { AppTopBar( - title = stringResource(R.string.select_patches), + title = stringResource(R.string.patches_selected, selectedPatchCount, availablePatchCount), onBackClick = onBackClick, actions = { IconButton(onClick = vm::reset) { @@ -362,7 +368,7 @@ fun PatchesSelectorScreen( ) { ListHeader( title = stringResource(R.string.unsupported_patches), - onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) } + onHelpClick = { showUnsupportedPatchesDialog = true } ) } } @@ -373,7 +379,7 @@ fun PatchesSelectorScreen( } @Composable -fun SelectionWarningDialog(onDismiss: () -> Unit) { +private fun SelectionWarningDialog(onDismiss: () -> Unit) { SafeguardDialog( onDismiss = onDismiss, title = R.string.warning, @@ -382,7 +388,7 @@ fun SelectionWarningDialog(onDismiss: () -> Unit) { } @Composable -fun UniversalPatchWarningDialog( +private fun UniversalPatchWarningDialog( onCancel: () -> Unit, onConfirm: () -> Unit ) { @@ -414,7 +420,7 @@ fun UniversalPatchWarningDialog( } @Composable -fun PatchItem( +private fun PatchItem( patch: PatchInfo, onOptionsDialog: () -> Unit, selected: Boolean, @@ -423,7 +429,7 @@ fun PatchItem( ) = ListItem( modifier = Modifier .let { if (!supported) it.alpha(0.5f) else it } - .clickable(enabled = supported, onClick = onToggle) + .clickable(onClick = onToggle) .fillMaxSize(), leadingContent = { Checkbox( @@ -444,7 +450,7 @@ fun PatchItem( ) @Composable -fun ListHeader( +private fun ListHeader( title: String, onHelpClick: (() -> Unit)? = null ) { @@ -470,18 +476,46 @@ fun ListHeader( } @Composable -fun UnsupportedDialog( +private fun UnsupportedPatchesDialog( + appVersion: String, + onDismissRequest: () -> Unit +) = AlertDialog( + icon = { + Icon(Icons.Outlined.WarningAmber, null) + }, + onDismissRequest = onDismissRequest, + confirmButton = { + TextButton(onClick = onDismissRequest) { + Text(stringResource(R.string.ok)) + } + }, + title = { Text(stringResource(R.string.unsupported_patches)) }, + text = { + Text( + stringResource( + R.string.unsupported_patches_dialog, + appVersion + ) + ) + } +) + +@Composable +private fun UnsupportedPatchDialog( appVersion: String, supportedVersions: List, onDismissRequest: () -> Unit ) = AlertDialog( + icon = { + Icon(Icons.Outlined.WarningAmber, null) + }, onDismissRequest = onDismissRequest, confirmButton = { TextButton(onClick = onDismissRequest) { Text(stringResource(R.string.ok)) } }, - title = { Text(stringResource(R.string.unsupported_app)) }, + title = { Text(stringResource(R.string.unsupported_patch)) }, text = { Text( stringResource( @@ -495,7 +529,7 @@ fun UnsupportedDialog( @OptIn(ExperimentalMaterial3Api::class) @Composable -fun OptionsDialog( +private fun OptionsDialog( patch: PatchInfo, values: Map?, reset: () -> Unit, @@ -536,4 +570,4 @@ fun OptionsDialog( } } } -} \ No newline at end of file +} 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 0dd786d79f..2057392bbb 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 @@ -69,11 +69,6 @@ fun SelectedAppInfoScreen( patches.values.sumOf { it.size } } } - val availablePatchCount by remember { - derivedStateOf { - bundles.sumOf { it.patchCount } - } - } val navController = rememberNavController(startDestination = SelectedAppInfoDestination.Main) @@ -111,7 +106,6 @@ fun SelectedAppInfoScreen( navController.navigate(SelectedAppInfoDestination.VersionSelector) }, onBackClick = onBackClick, - availablePatchCount = availablePatchCount, selectedPatchCount = selectedPatchCount, packageName = packageName, version = version, @@ -154,7 +148,6 @@ private fun SelectedAppInfoScreen( onPatchSelectorClick: () -> Unit, onVersionSelectorClick: () -> Unit, onBackClick: () -> Unit, - availablePatchCount: Int, selectedPatchCount: Int, packageName: String, version: String, @@ -182,7 +175,7 @@ private fun SelectedAppInfoScreen( ) { AppInfo(packageInfo, placeholderLabel = packageName) { Text( - stringResource(R.string.selected_app_meta, version, availablePatchCount), + stringResource(R.string.selected_app_meta, version), color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium, ) 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 2fca61f15e..087072f251 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 @@ -10,27 +10,13 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Api -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.material.icons.outlined.Restore +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext @@ -87,14 +73,18 @@ fun AdvancedSettingsScreen( var showApiUrlDialog by rememberSaveable { mutableStateOf(false) } if (showApiUrlDialog) { - APIUrlDialog(apiUrl) { - showApiUrlDialog = false - it?.let(vm::setApiUrl) - } + APIUrlDialog( + currentUrl = apiUrl, + defaultUrl = vm.prefs.api.default, + onSubmit = { + showApiUrlDialog = false + it?.let(vm::setApiUrl) + } + ) } SettingsListItem( headlineContent = stringResource(R.string.api_url), - supportingContent = apiUrl, + supportingContent = stringResource(R.string.api_url_description), modifier = Modifier.clickable { showApiUrlDialog = true } @@ -193,7 +183,7 @@ fun AdvancedSettingsScreen( } @Composable -private fun APIUrlDialog(currentUrl: String, onSubmit: (String?) -> Unit) { +private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (String?) -> Unit) { var url by rememberSaveable(currentUrl) { mutableStateOf(currentUrl) } AlertDialog( @@ -237,9 +227,15 @@ private fun APIUrlDialog(currentUrl: String, onSubmit: (String?) -> Unit) { color = MaterialTheme.colorScheme.error ) OutlinedTextField( + modifier = Modifier.fillMaxWidth(), value = url, onValueChange = { url = it }, - label = { Text(stringResource(R.string.api_url)) } + label = { Text(stringResource(R.string.api_url)) }, + trailingIcon = { + IconButton(onClick = { url = defaultUrl }) { + Icon(Icons.Outlined.Restore, stringResource(R.string.api_url_dialog_reset)) + } + } ) } } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt index 96aa2cd2ef..3772f3f4ff 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt @@ -64,6 +64,12 @@ fun UpdatesSettingsScreen( 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/DashboardViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt index 9d2e122433..ce68249d27 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt @@ -64,6 +64,12 @@ class DashboardViewModel( } } + fun setShowManagerUpdateDialogOnLaunch(value: Boolean) { + viewModelScope.launch { + prefs.showManagerUpdateDialogOnLaunch.update(value) + } + } + fun applyAutoUpdatePrefs(manager: Boolean, patches: Boolean) = viewModelScope.launch { prefs.firstLaunch.update(false) 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 331548d6b3..abbec2460e 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 @@ -36,6 +36,7 @@ import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.get import kotlinx.collections.immutable.* +import kotlinx.coroutines.flow.map @Stable @OptIn(SavedStateHandleSaveableApi::class) @@ -77,7 +78,7 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent { } private var hasModifiedSelection = false - private var customPatchSelection: PersistentPatchSelection? by savedStateHandle.saveable( + var customPatchSelection: PersistentPatchSelection? by savedStateHandle.saveable( key = "selection", stateSaver = selectionSaver, ) { @@ -103,12 +104,13 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent { var filter by mutableIntStateOf(SHOW_SUPPORTED or SHOW_UNIVERSAL or SHOW_UNSUPPORTED) private set - private suspend fun generateDefaultSelection(): PersistentPatchSelection { - val bundles = bundlesFlow.first() - val generatedSelection = - bundles.toPatchSelection(allowIncompatiblePatches) { _, patch -> patch.include } + private val defaultPatchSelection = bundlesFlow.map { bundles -> + bundles.toPatchSelection(allowIncompatiblePatches) { _, patch -> patch.include } + .toPersistentPatchSelection() + } - return generatedSelection.toPersistentPatchSelection() + val defaultSelectionCount = defaultPatchSelection.map { selection -> + selection.values.sumOf { it.size } } fun selectionIsValid(bundles: List) = bundles.any { bundle -> @@ -124,7 +126,7 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent { fun togglePatch(bundle: Int, patch: PatchInfo) = viewModelScope.launch { hasModifiedSelection = true - val selection = customPatchSelection ?: generateDefaultSelection() + val selection = customPatchSelection ?: defaultPatchSelection.first() val newPatches = selection[bundle]?.let { patches -> if (patch.name in patches) patches.remove(patch.name) @@ -188,10 +190,8 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent { compatibleVersions.clear() } - fun openUnsupportedDialog(unsupportedPatches: List) { - compatibleVersions.addAll(unsupportedPatches.flatMap { patch -> - patch.compatiblePackages?.find { it.packageName == packageName }?.versions.orEmpty() - }) + fun openUnsupportedDialog(unsupportedPatch: PatchInfo) { + compatibleVersions.addAll(unsupportedPatch.compatiblePackages?.find { it.packageName == packageName }?.versions.orEmpty()) } fun toggleFlag(flag: Int) { 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 1a6653e038..4dba413645 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 @@ -1,6 +1,5 @@ package app.revanced.manager.ui.viewmodel -import android.app.Application import android.content.pm.PackageInfo import android.os.Parcelable import androidx.compose.runtime.MutableState @@ -38,7 +37,6 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent { private val optionsRepository: PatchOptionsRepository = get() private val pm: PM = get() private val savedStateHandle: SavedStateHandle = get() - private val app: Application = get() val prefs: PreferencesManager = get() private val persistConfiguration = input.patches == null @@ -82,20 +80,17 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent { private set private var selectionState by savedStateHandle.saveable { - if (input.patches != null) { + if (input.patches != null) return@saveable mutableStateOf(SelectionState.Customized(input.patches)) - } val selection: MutableState = mutableStateOf(SelectionState.Default) - // Get previous selection (if present). + // Try to get the previous selection if customization is enabled. viewModelScope.launch { - val previous = selectionRepository.getSelection(selectedApp.packageName) - - if (previous.values.sumOf { it.size } == 0) { - return@launch - } + if (!prefs.disableSelectionWarning.get()) return@launch + val previous = selectionRepository.getSelection(selectedApp.packageName) + if (previous.values.sumOf { it.size } == 0) return@launch selection.value = SelectionState.Customized(previous) } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt index cd96e091ce..385aeddf52 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt @@ -14,6 +14,7 @@ class UpdatesSettingsViewModel( private val reVancedAPI: ReVancedAPI, ) : ViewModel() { 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") { diff --git a/app/src/main/jniLibs/armeabi-v7a/libaapt2.so b/app/src/main/jniLibs/armeabi-v7a/libaapt2.so new file mode 100644 index 0000000000..46bdce5ce7 Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libaapt2.so differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cf56f70c54..7e01731f65 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,9 +12,9 @@ Dashboard Settings Select an app - Select patches + %1$d/%2$d selected - Patching on ARMv7 devices is not yet supported and will most likely fail. + Patching on this device architecture is unsupported and will most likely fail. Import Import patch bundle @@ -33,7 +33,7 @@ Default Unnamed - %1$s • %2$d available patches + %1$s Start patching the application Patch selection and options @@ -158,10 +158,12 @@ Failed to export logs Exported logs API URL + The API used to download necessary files. Set custom API URL - You may have issues with features when using a custom API URL. - Only use API\'s you trust! + 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 Device Android version Model @@ -191,7 +193,6 @@ No patched apps found Tap on the patches to get more information about them %s selected - Unsupported app Unsupported patches Universal patches Patch selection and options has been reset to recommended defaults @@ -205,7 +206,7 @@ Universal Unsupported Patch name - Some of the patches do not support this app version (%1$s). The patches only support the following version(s): %2$s. + This patch is not compatible with the selected app version (%1$s).\n\nIt only supports the following version(s): %2$s. Continue with this version? Not all patches support this version (%s). Do you want to continue anyway? Download application? @@ -319,9 +320,9 @@ Failed to install update Check for updates Manually check for updates - Update checking + Auto check for updates Check for new versions of ReVanced Manager when the application starts - Changelog + View changelogs Loading changelog Failed to download changelog: %s Check out the latest changes in this update @@ -368,4 +369,9 @@ Add patch bundle Bundle URL Auto update + These patches are not compatible with the selected app version (%1$s).\n\nClick on the patches to see more details. + Unsupported patch + Never show again + Show update message on launch + Shows a popup notification whenever there is a new update available on launch. diff --git a/docs/0_prerequisites.md b/docs/0_prerequisites.md index a53b46fb9c..9d86cd5438 100644 --- a/docs/0_prerequisites.md +++ b/docs/0_prerequisites.md @@ -5,9 +5,6 @@ In order to use ReVanced Manager, certain requirements must be met. ## 🤝 Requirements - An Android device running Android 8 or higher -- Any device architecture except ARMv7[^1] - -[^1]: This constraint only applies to patches, that require patching APK resources which is why some patches may or may not work on ARMv7 architecture. You can find out, which architectures your device supports here: [⚙️ Configuring ReVanced Manager](2_4_settings.md#%E2%84%B9%EF%B8%8F-about). ## ⏭️ What's next diff --git a/gradle.properties b/gradle.properties index f19c7b9b29..1ee40ba56a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,4 +21,5 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -android.nonFinalResIds=false \ No newline at end of file +android.nonFinalResIds=false +org.gradle.configuration-cache=true