From 251b9eef697de61a23c2804f04cebdda5df3a365 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Thu, 12 Dec 2024 16:01:52 +0100 Subject: [PATCH] not supporting parcelables is cringe --- app/build.gradle.kts | 2 + .../java/app/revanced/manager/MainActivity.kt | 230 +++++++++++++++-- .../data/room/apps/installed/InstalledApp.kt | 5 +- .../revanced/manager/di/ViewModelModule.kt | 2 +- .../manager/ui/destination/Destination.kt | 4 +- .../manager/ui/model/navigation/Nav.kt | 116 +++++++++ .../ui/screen/InstalledAppInfoScreen.kt | 22 +- .../ui/screen/SelectedAppInfoScreen.kt | 241 +++++++++--------- .../manager/ui/screen/SettingsScreen.kt | 76 +++++- .../{settings/update => }/UpdateScreen.kt | 2 +- .../ui/screen/settings/AboutSettingsScreen.kt | 11 +- .../screen/settings/GeneralSettingsScreen.kt | 6 +- ...ewModel.kt => GeneralSettingsViewModel.kt} | 2 +- .../ui/viewmodel/InstalledAppInfoViewModel.kt | 66 ++--- .../manager/ui/viewmodel/PatcherViewModel.kt | 4 +- .../ui/viewmodel/SelectedAppInfoViewModel.kt | 18 +- .../java/app/revanced/manager/util/Util.kt | 8 +- gradle/libs.versions.toml | 5 +- 18 files changed, 595 insertions(+), 225 deletions(-) create mode 100644 app/src/main/java/app/revanced/manager/ui/model/navigation/Nav.kt rename app/src/main/java/app/revanced/manager/ui/screen/{settings/update => }/UpdateScreen.kt (99%) rename app/src/main/java/app/revanced/manager/ui/viewmodel/{SettingsViewModel.kt => GeneralSettingsViewModel.kt} (92%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 87f37444..4246598d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -126,6 +126,7 @@ dependencies { implementation(libs.compose.livedata) implementation(libs.compose.material.icons.extended) implementation(libs.compose.material3) + implementation(libs.navigation.compose) // Accompanist implementation(libs.accompanist.drawablepainter) @@ -173,6 +174,7 @@ dependencies { // Koin implementation(libs.koin.android) implementation(libs.koin.compose) + implementation(libs.koin.compose.navigation) implementation(libs.koin.workmanager) // Compose Navigation diff --git a/app/src/main/java/app/revanced/manager/MainActivity.kt b/app/src/main/java/app/revanced/manager/MainActivity.kt index ff01094c..0bda88a9 100644 --- a/app/src/main/java/app/revanced/manager/MainActivity.kt +++ b/app/src/main/java/app/revanced/manager/MainActivity.kt @@ -6,28 +6,31 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat -import app.revanced.manager.ui.destination.Destination -import app.revanced.manager.ui.destination.SettingsDestination -import app.revanced.manager.ui.screen.AppSelectorScreen -import app.revanced.manager.ui.screen.DashboardScreen -import app.revanced.manager.ui.screen.InstalledAppInfoScreen -import app.revanced.manager.ui.screen.PatcherScreen -import app.revanced.manager.ui.screen.SelectedAppInfoScreen -import app.revanced.manager.ui.screen.SettingsScreen +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.navigation +import androidx.navigation.compose.rememberNavController +import androidx.navigation.toRoute +import app.revanced.manager.ui.model.navigation.* +import app.revanced.manager.ui.screen.* +import app.revanced.manager.ui.screen.settings.* +import app.revanced.manager.ui.screen.settings.update.ChangelogsScreen +import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen import app.revanced.manager.ui.theme.ReVancedManagerTheme import app.revanced.manager.ui.theme.Theme import app.revanced.manager.ui.viewmodel.MainViewModel +import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel import app.revanced.manager.util.EventEffect -import dev.olshevski.navigation.reimagined.AnimatedNavHost -import dev.olshevski.navigation.reimagined.NavBackHandler -import dev.olshevski.navigation.reimagined.navigate -import dev.olshevski.navigation.reimagined.pop -import dev.olshevski.navigation.reimagined.popUpTo -import dev.olshevski.navigation.reimagined.rememberNavController +import org.koin.androidx.compose.koinViewModel +import org.koin.androidx.compose.navigation.koinNavViewModel import org.koin.core.parameter.parametersOf import org.koin.androidx.compose.koinViewModel as getComposeViewModel import org.koin.androidx.viewmodel.ext.android.getViewModel as getAndroidViewModel @@ -52,6 +55,200 @@ class MainActivity : ComponentActivity() { darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK, dynamicColor = dynamicColor ) { + ReVancedManager(vm) + } + } + } +} + +@Composable +private fun ReVancedManager(vm: MainViewModel) { + val navController = rememberNavController() + + EventEffect(vm.appSelectFlow) { app -> + // navController.navigate(SelectedApplicationInfo(app)) + } + + NavHost( + navController = navController, + startDestination = Dashboard, + ) { + composable { + DashboardScreen( + onSettingsClick = { navController.navigate(Settings) }, + onAppSelectorClick = { + println("before: ${navController.currentBackStackEntry?.id}") + navController.navigate(AppSelector) + println("after: ${navController.currentBackStackEntry?.id}") + }, + onUpdateClick = { + navController.navigate(Update()) + // navController.navigate(Destination.Settings(SettingsDestination.Update())) + }, + onDownloaderPluginClick = { + // navController.navigate(Destination.Settings(SettingsDestination.Downloads)) + navController.navigate(Settings.Downloads) + }, + onAppClick = { installedApp -> + navController.navigate(InstalledApplicationInfo(installedApp.currentPackageName)) + } + ) + } + + composable { + val data = it.toRoute() + + InstalledAppInfoScreen( + onPatchClick = vm::selectApp, + onBackClick = navController::popBackStack, + viewModel = getComposeViewModel { parametersOf(data.packageName) } + ) + } + + composable { + AppSelectorScreen( + onSelect = vm::selectApp, + onStorageSelect = vm::selectApp, + onBackClick = navController::popBackStack + ) + } + + composable { + PatcherScreen( + onBackClick = { + navController.navigate(route = Dashboard) { + launchSingleTop = true + popUpTo { + inclusive = false + } + } + }, + vm = koinViewModel { parametersOf(it.toRoute()) } + ) + } + + navigation(startDestination = SelectedApplicationInfo.Main) { + composable { + val parentBackStackEntry = navController.navGraphEntry(it) + val data = parentBackStackEntry.toRoute() + + SelectedAppInfoScreen( + onBackClick = navController::popBackStack, + onPatchClick = { app, patches, options -> + // navController.navigate(Patcher(app, patches, options)) + }, + onPatchSelectorClick = { app, patches, options -> + /* + navController.navigate( + SelectedApplicationInfo.PatchesSelector( + app, + patches, + options + ) + )*/ + }, + vm = koinNavViewModel(viewModelStoreOwner = parentBackStackEntry) { + parametersOf( + SelectedAppInfoViewModel.Params( + data.selectedApp, + data.patchSelection + ) + ) + } + ) + } + + composable( + // typeMap = mapOf(typeOf() to SelectedApplicationInfo.PatchesSelector.navType) + ) { + val data = it.toRoute() + val selectedAppInfoVm = koinNavViewModel( + viewModelStoreOwner = navController.navGraphEntry(it) + ) + + PatchesSelectorScreen( + onBackClick = navController::popBackStack, + onSave = { patches, options -> + selectedAppInfoVm.updateConfiguration(patches, options) + navController.popBackStack() + }, + vm = koinViewModel { + parametersOf( + PatchesSelectorViewModel.Params( + data.app, + data.currentSelection, + data.options, + ) + ) + } + ) + } + } + + navigation(startDestination = Settings.Main) { + composable { + SettingsScreen( + onBackClick = navController::popBackStack, + navigate = navController::navigate + ) + } + + composable { + GeneralSettingsScreen(onBackClick = navController::popBackStack) + } + + composable { + AdvancedSettingsScreen(onBackClick = navController::popBackStack) + } + + composable { + UpdatesSettingsScreen( + onBackClick = navController::popBackStack, + onChangelogClick = { navController.navigate(Settings.Changelogs) }, + onUpdateClick = { navController.navigate(Update()) } + ) + } + + composable { + DownloadsSettingsScreen(onBackClick = navController::popBackStack) + } + + composable { + ImportExportSettingsScreen(onBackClick = navController::popBackStack) + } + + composable { + AboutSettingsScreen( + onBackClick = navController::popBackStack, + navigate = navController::navigate + ) + } + + composable { + ChangelogsScreen(onBackClick = navController::popBackStack) + } + + composable { + ContributorScreen(onBackClick = navController::popBackStack) + } + + composable { + LicensesScreen(onBackClick = navController::popBackStack) + } + + composable { + DeveloperOptionsScreen(onBackClick = navController::popBackStack) + } + } + } +} + +@Composable +private fun NavController.navGraphEntry(entry: NavBackStackEntry) = remember(entry) { + getBackStackEntry(entry.destination.parent!!) +} + +/* val navController = rememberNavController(startDestination = Destination.Dashboard) NavBackHandler(navController) @@ -124,7 +321,4 @@ class MainActivity : ComponentActivity() { ) } } - } - } - } -} + */ \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledApp.kt b/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledApp.kt index 290a226d..c0986dfd 100644 --- a/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledApp.kt +++ b/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledApp.kt @@ -1,18 +1,15 @@ package app.revanced.manager.data.room.apps.installed -import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import app.revanced.manager.R -import kotlinx.parcelize.Parcelize enum class InstallType(val stringResource: Int) { DEFAULT(R.string.default_install), MOUNT(R.string.mount_install) } -@Parcelize @Entity(tableName = "installed_app") data class InstalledApp( @PrimaryKey @@ -20,4 +17,4 @@ data class InstalledApp( @ColumnInfo(name = "original_package_name") val originalPackageName: String, @ColumnInfo(name = "version") val version: String, @ColumnInfo(name = "install_type") val installType: InstallType -) : Parcelable \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt b/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt index a59d65a2..4846510f 100644 --- a/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt +++ b/app/src/main/java/app/revanced/manager/di/ViewModelModule.kt @@ -9,7 +9,7 @@ val viewModelModule = module { viewModelOf(::DashboardViewModel) viewModelOf(::SelectedAppInfoViewModel) viewModelOf(::PatchesSelectorViewModel) - viewModelOf(::SettingsViewModel) + viewModelOf(::GeneralSettingsViewModel) viewModelOf(::AdvancedSettingsViewModel) viewModelOf(::AppSelectorViewModel) viewModelOf(::PatcherViewModel) diff --git a/app/src/main/java/app/revanced/manager/ui/destination/Destination.kt b/app/src/main/java/app/revanced/manager/ui/destination/Destination.kt index 93c59411..f45020c6 100644 --- a/app/src/main/java/app/revanced/manager/ui/destination/Destination.kt +++ b/app/src/main/java/app/revanced/manager/ui/destination/Destination.kt @@ -13,8 +13,8 @@ sealed interface Destination : Parcelable { @Parcelize data object Dashboard : Destination - @Parcelize - data class InstalledApplicationInfo(val installedApp: InstalledApp) : Destination + // @Parcelize + // data class InstalledApplicationInfo(val installedApp: InstalledApp) : Destination @Parcelize data object AppSelector : Destination diff --git a/app/src/main/java/app/revanced/manager/ui/model/navigation/Nav.kt b/app/src/main/java/app/revanced/manager/ui/model/navigation/Nav.kt new file mode 100644 index 00000000..6ee30516 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/model/navigation/Nav.kt @@ -0,0 +1,116 @@ +package app.revanced.manager.ui.model.navigation + +import app.revanced.manager.ui.model.SelectedApp +import app.revanced.manager.util.Options +import app.revanced.manager.util.PatchSelection +import kotlinx.serialization.Serializable + +/* +sealed interface Destination : Parcelable { + + @Parcelize + data object Dashboard : Destination + + @Parcelize + data class InstalledApplicationInfo(val installedApp: InstalledApp) : Destination + + @Parcelize + data object AppSelector : Destination + + @Parcelize + data class Settings(val startDestination: SettingsDestination = SettingsDestination.Settings) : Destination + + @Parcelize + data class SelectedApplicationInfo(val selectedApp: SelectedApp, val patchSelection: PatchSelection? = null) : Destination + + @Parcelize + data class Patcher(val selectedApp: SelectedApp, val selectedPatches: PatchSelection, val options: @RawValue Options) : Destination + +}*/ + +@Serializable +object Dashboard + +@Serializable +object AppSelector + +@Serializable +data class InstalledApplicationInfo(val packageName: String) + +@Serializable +data class Update(val downloadOnScreenEntry: Boolean = false) + +@Serializable +data class SelectedApplicationInfo( + val h: Int = 0, + // val selectedApp: SelectedApp, + // val patchSelection: PatchSelection? = null +) { + val patchSelection: PatchSelection? get() = TODO() + val selectedApp: SelectedApp get() = TODO() + + @Serializable + object Main + + @Serializable + data class PatchesSelector( + val h: Int, + //val app: SelectedApp, + // val currentSelection: PatchSelection?, + // val options: Options + ) { + val app: SelectedApp get() = TODO() + val currentSelection: PatchSelection? get() = TODO() + val options: Options get() = TODO() + } +} + +@Serializable +data class Patcher( + val h: Int = 0, + //val selectedApp: SelectedApp, + // val selectedPatches: PatchSelection, + // val options: Options +) { + val selectedApp: SelectedApp get() = TODO() + val options: Options get() = TODO() + val selectedPatches: PatchSelection get() = TODO() +} + +@Serializable +object Settings { + sealed interface Destination + + @Serializable + data object Main : Destination + + @Serializable + data object General : Destination + + @Serializable + data object Advanced : Destination + + @Serializable + data object Updates : Destination + + @Serializable + data object Downloads : Destination + + @Serializable + data object ImportExport : Destination + + @Serializable + data object About : Destination + + @Serializable + data object Changelogs : Destination + + @Serializable + data object Contributors : Destination + + @Serializable + data object Licenses : Destination + + @Serializable + data object DeveloperOptions : Destination +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/screen/InstalledAppInfoScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/InstalledAppInfoScreen.kt index 9ddf2ef8..360b4aae 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/InstalledAppInfoScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/InstalledAppInfoScreen.kt @@ -77,10 +77,12 @@ fun InstalledAppInfoScreen( .fillMaxSize() .padding(paddingValues) ) { - AppInfo(viewModel.appInfo) { - Text(viewModel.installedApp.version, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium) + val installedApp = viewModel.installedApp ?: return@ColumnWithScrollbar - if (viewModel.installedApp.installType == InstallType.MOUNT) { + AppInfo(viewModel.appInfo) { + Text(installedApp.version, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium) + + if (installedApp.installType == InstallType.MOUNT) { Text( text = if (viewModel.isMounted) { stringResource(R.string.mounted) @@ -104,7 +106,7 @@ fun InstalledAppInfoScreen( onClick = viewModel::launch ) - when (viewModel.installedApp.installType) { + when (installedApp.installType) { InstallType.DEFAULT -> SegmentedButton( icon = Icons.Outlined.Delete, text = stringResource(R.string.uninstall), @@ -133,9 +135,9 @@ fun InstalledAppInfoScreen( icon = Icons.Outlined.Update, text = stringResource(R.string.repatch), onClick = { - onPatchClick(viewModel.installedApp.originalPackageName) + onPatchClick(installedApp.originalPackageName) }, - enabled = viewModel.installedApp.installType != InstallType.MOUNT || viewModel.rootInstaller.hasRootAccess() + enabled = installedApp.installType != InstallType.MOUNT || viewModel.rootInstaller.hasRootAccess() ) } @@ -158,19 +160,19 @@ fun InstalledAppInfoScreen( SettingsListItem( headlineContent = stringResource(R.string.package_name), - supportingContent = viewModel.installedApp.currentPackageName + supportingContent = installedApp.currentPackageName ) - if (viewModel.installedApp.originalPackageName != viewModel.installedApp.currentPackageName) { + if (installedApp.originalPackageName != installedApp.currentPackageName) { SettingsListItem( headlineContent = stringResource(R.string.original_package_name), - supportingContent = viewModel.installedApp.originalPackageName + supportingContent = installedApp.originalPackageName ) } SettingsListItem( headlineContent = stringResource(R.string.install_type), - supportingContent = stringResource(viewModel.installedApp.installType.stringResource) + supportingContent = stringResource(installedApp.installType.stringResource) ) } } 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 9fb48372..0ca71cc9 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 @@ -50,6 +50,7 @@ import org.koin.core.parameter.parametersOf @OptIn(ExperimentalMaterial3Api::class) @Composable fun SelectedAppInfoScreen( + onPatchSelectorClick: (SelectedApp, PatchSelection?, Options) -> Unit, onPatchClick: (SelectedApp, PatchSelection, Options) -> Unit, onBackClick: () -> Unit, vm: SelectedAppInfoViewModel @@ -82,131 +83,135 @@ fun SelectedAppInfoScreen( launcher.launch(intent) } - val navController = - rememberNavController(startDestination = SelectedAppInfoDestination.Main) + val error by vm.errorFlow.collectAsStateWithLifecycle(null) + Scaffold( + topBar = { + AppTopBar( + title = stringResource(R.string.app_info), + onBackClick = onBackClick + ) + }, + floatingActionButton = { + if (error != null) return@Scaffold - NavBackHandler(controller = navController) - - AnimatedNavHost(controller = navController) { destination -> - val error by vm.errorFlow.collectAsStateWithLifecycle(null) - when (destination) { - is SelectedAppInfoDestination.Main -> Scaffold( - topBar = { - AppTopBar( - title = stringResource(R.string.app_info), - onBackClick = onBackClick + HapticExtendedFloatingActionButton( + text = { Text(stringResource(R.string.patch)) }, + icon = { + Icon( + Icons.Default.AutoFixHigh, + stringResource(R.string.patch) ) }, - floatingActionButton = { - if (error != null) return@Scaffold + onClick = patchClick@{ + if (selectedPatchCount == 0) { + context.toast(context.getString(R.string.no_patches_selected)) - HapticExtendedFloatingActionButton( - text = { Text(stringResource(R.string.patch)) }, - icon = { - Icon( - Icons.Default.AutoFixHigh, - stringResource(R.string.patch) - ) - }, - onClick = patchClick@{ - if (selectedPatchCount == 0) { - context.toast(context.getString(R.string.no_patches_selected)) - - return@patchClick - } - onPatchClick( - vm.selectedApp, - patches, - vm.getOptionsFiltered(bundles) - ) - } - ) - } - ) { paddingValues -> - val plugins by vm.plugins.collectAsStateWithLifecycle(emptyList()) - - if (vm.showSourceSelector) { - val requiredVersion by vm.requiredVersion.collectAsStateWithLifecycle(null) - - AppSourceSelectorDialog( - plugins = plugins, - installedApp = vm.installedAppData, - searchApp = SelectedApp.Search( - vm.packageName, - vm.desiredVersion - ), - activeSearchJob = vm.activePluginAction, - hasRoot = vm.hasRoot, - onDismissRequest = vm::dismissSourceSelector, - onSelectPlugin = vm::searchUsingPlugin, - requiredVersion = requiredVersion, - onSelect = { - vm.selectedApp = it - vm.dismissSourceSelector() - } - ) - } - - ColumnWithScrollbar( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - ) { - AppInfo(vm.selectedAppInfo, placeholderLabel = packageName) { - Text( - version ?: stringResource(R.string.selected_app_meta_any_version), - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodyMedium, - ) + return@patchClick } - - PageItem( - R.string.patch_selector_item, - stringResource( - R.string.patch_selector_item_description, - selectedPatchCount - ), - onClick = { - navController.navigate( - SelectedAppInfoDestination.PatchesSelector( - vm.selectedApp, - vm.getCustomPatches( - bundles, - allowIncompatiblePatches - ), - vm.options - ) - ) - } + onPatchClick( + vm.selectedApp, + patches, + vm.getOptionsFiltered(bundles) ) - PageItem( - R.string.apk_source_selector_item, - when (val app = vm.selectedApp) { - is SelectedApp.Search -> stringResource(R.string.apk_source_auto) - is SelectedApp.Installed -> stringResource(R.string.apk_source_installed) - is SelectedApp.Download -> stringResource( - R.string.apk_source_downloader, - plugins.find { it.packageName == app.data.pluginPackageName }?.name - ?: app.data.pluginPackageName - ) - - is SelectedApp.Local -> stringResource(R.string.apk_source_local) - }, - onClick = { - vm.showSourceSelector() - } - ) - error?.let { - Text( - stringResource(it.resourceId), - color = MaterialTheme.colorScheme.error, - modifier = Modifier.padding(horizontal = 24.dp) - ) - } } + ) + } + ) { paddingValues -> + val plugins by vm.plugins.collectAsStateWithLifecycle(emptyList()) + + if (vm.showSourceSelector) { + val requiredVersion by vm.requiredVersion.collectAsStateWithLifecycle(null) + + AppSourceSelectorDialog( + plugins = plugins, + installedApp = vm.installedAppData, + searchApp = SelectedApp.Search( + vm.packageName, + vm.desiredVersion + ), + activeSearchJob = vm.activePluginAction, + hasRoot = vm.hasRoot, + onDismissRequest = vm::dismissSourceSelector, + onSelectPlugin = vm::searchUsingPlugin, + requiredVersion = requiredVersion, + onSelect = { + vm.selectedApp = it + vm.dismissSourceSelector() + } + ) + } + + ColumnWithScrollbar( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + AppInfo(vm.selectedAppInfo, placeholderLabel = packageName) { + Text( + version ?: stringResource(R.string.selected_app_meta_any_version), + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyMedium, + ) } - is SelectedAppInfoDestination.PatchesSelector -> PatchesSelectorScreen( + PageItem( + R.string.patch_selector_item, + stringResource( + R.string.patch_selector_item_description, + selectedPatchCount + ), + onClick = { + onPatchSelectorClick( + vm.selectedApp, + vm.getCustomPatches( + bundles, + allowIncompatiblePatches + ), + vm.options + ) + /* + navController.navigate( + SelectedAppInfoDestination.PatchesSelector( + vm.selectedApp, + vm.getCustomPatches( + bundles, + allowIncompatiblePatches + ), + vm.options + ) + )*/ + } + ) + PageItem( + R.string.apk_source_selector_item, + when (val app = vm.selectedApp) { + is SelectedApp.Search -> stringResource(R.string.apk_source_auto) + is SelectedApp.Installed -> stringResource(R.string.apk_source_installed) + is SelectedApp.Download -> stringResource( + R.string.apk_source_downloader, + plugins.find { it.packageName == app.data.pluginPackageName }?.name + ?: app.data.pluginPackageName + ) + + is SelectedApp.Local -> stringResource(R.string.apk_source_local) + }, + onClick = { + vm.showSourceSelector() + } + ) + error?.let { + Text( + stringResource(it.resourceId), + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(horizontal = 24.dp) + ) + } + } + } +} + +/* +PatchesSelectorScreen( onSave = { patches, options -> vm.updateConfiguration(patches, options, bundles) navController.pop() @@ -222,9 +227,7 @@ fun SelectedAppInfoScreen( ) } ) - } - } -} + */ @Composable private fun PageItem(@StringRes title: Int, description: String, onClick: () -> Unit) { diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt index 0d0bd46e..adce1507 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt @@ -13,21 +13,75 @@ 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.SettingsListItem -import app.revanced.manager.ui.destination.SettingsDestination -import app.revanced.manager.ui.screen.settings.* -import app.revanced.manager.ui.screen.settings.update.ChangelogsScreen -import app.revanced.manager.ui.screen.settings.update.UpdateScreen -import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen -import app.revanced.manager.ui.viewmodel.SettingsViewModel -import dev.olshevski.navigation.reimagined.* -import org.koin.androidx.compose.koinViewModel -import org.koin.core.parameter.parametersOf +import app.revanced.manager.ui.model.navigation.Settings +private val settingsSections = listOf( + Triple( + R.string.general, + R.string.general_description, + Icons.Outlined.Settings + ) to Settings.General, + Triple( + R.string.updates, + R.string.updates_description, + Icons.Outlined.Update + ) to Settings.Updates, + Triple( + R.string.downloads, + R.string.downloads_description, + Icons.Outlined.Download + ) to Settings.Downloads, + Triple( + R.string.import_export, + R.string.import_export_description, + Icons.Outlined.SwapVert + ) to Settings.ImportExport, + Triple( + R.string.advanced, + R.string.advanced_description, + Icons.Outlined.Tune + ) to Settings.Advanced, + Triple( + R.string.about, + R.string.app_name, + Icons.Outlined.Info + ) to Settings.About, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) -> Unit) { + Scaffold( + topBar = { + AppTopBar( + title = stringResource(R.string.settings), + onBackClick = onBackClick, + ) + } + ) { paddingValues -> + ColumnWithScrollbar( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + ) { + settingsSections.forEach { (titleDescIcon, destination) -> + SettingsListItem( + modifier = Modifier.clickable { navigate(destination) }, + headlineContent = stringResource(titleDescIcon.first), + supportingContent = stringResource(titleDescIcon.second), + leadingContent = { Icon(titleDescIcon.third, null) } + ) + } + } + } +} + +/* @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( onBackClick: () -> Unit, - startDestination: SettingsDestination, + startDestination: Settings.Destination, viewModel: SettingsViewModel = koinViewModel() ) { val navController = rememberNavController(startDestination) @@ -156,4 +210,4 @@ fun SettingsScreen( } } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/UpdateScreen.kt similarity index 99% rename from app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateScreen.kt rename to app/src/main/java/app/revanced/manager/ui/screen/UpdateScreen.kt index 693adc6a..6d8d6b48 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdateScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/UpdateScreen.kt @@ -1,4 +1,4 @@ -package app.revanced.manager.ui.screen.settings.update +package app.revanced.manager.ui.screen import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.spring diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/AboutSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/AboutSettingsScreen.kt index 7ee21609..a3b9f860 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/AboutSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/AboutSettingsScreen.kt @@ -37,6 +37,7 @@ import app.revanced.manager.network.dto.ReVancedSocial import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.settings.SettingsListItem +import app.revanced.manager.ui.model.navigation.Settings import app.revanced.manager.ui.viewmodel.AboutViewModel import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon import app.revanced.manager.util.openUrl @@ -47,9 +48,7 @@ import org.koin.androidx.compose.koinViewModel @Composable fun AboutSettingsScreen( onBackClick: () -> Unit, - onContributorsClick: () -> Unit, - onLicensesClick: () -> Unit, - onDeveloperOptionsClick: () -> Unit, + navigate: (Settings.Destination) -> Unit, viewModel: AboutViewModel = koinViewModel() ) { val context = LocalContext.current @@ -114,17 +113,17 @@ fun AboutSettingsScreen( Triple( stringResource(R.string.contributors), stringResource(R.string.contributors_description), - third = onContributorsClick + third = { navigate(Settings.Contributors) } ), Triple( stringResource(R.string.developer_options), stringResource(R.string.developer_options_description), - third = onDeveloperOptionsClick + third = { navigate(Settings.DeveloperOptions) } ), Triple( stringResource(R.string.opensource_licenses), stringResource(R.string.opensource_licenses_description), - third = onLicensesClick + third = { navigate(Settings.Licenses) } ) ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt index 56242679..89cd223c 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -32,14 +31,15 @@ import app.revanced.manager.ui.component.haptics.HapticRadioButton import app.revanced.manager.ui.component.settings.BooleanItem import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.theme.Theme -import app.revanced.manager.ui.viewmodel.SettingsViewModel +import app.revanced.manager.ui.viewmodel.GeneralSettingsViewModel +import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject @OptIn(ExperimentalMaterial3Api::class) @Composable fun GeneralSettingsScreen( onBackClick: () -> Unit, - viewModel: SettingsViewModel + viewModel: GeneralSettingsViewModel = koinViewModel() ) { val prefs = viewModel.prefs val coroutineScope = viewModel.viewModelScope diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/SettingsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/GeneralSettingsViewModel.kt similarity index 92% rename from app/src/main/java/app/revanced/manager/ui/viewmodel/SettingsViewModel.kt rename to app/src/main/java/app/revanced/manager/ui/viewmodel/GeneralSettingsViewModel.kt index 1e75bdea..ea15c757 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/GeneralSettingsViewModel.kt @@ -6,7 +6,7 @@ import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.ui.theme.Theme import kotlinx.coroutines.launch -class SettingsViewModel( +class GeneralSettingsViewModel( val prefs: PreferencesManager ) : ViewModel() { fun setTheme(theme: Theme) = viewModelScope.launch { diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppInfoViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppInfoViewModel.kt index 93e2cb74..27b86263 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppInfoViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppInfoViewModel.kt @@ -32,15 +32,17 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject class InstalledAppInfoViewModel( - val installedApp: InstalledApp + packageName: String ) : ViewModel(), KoinComponent { - private val app: Application by inject() + private val context: Application by inject() private val pm: PM by inject() private val installedAppRepository: InstalledAppRepository by inject() val rootInstaller: RootInstaller by inject() lateinit var onBackClick: () -> Unit + var installedApp: InstalledApp? by mutableStateOf(null) + private set var appInfo: PackageInfo? by mutableStateOf(null) private set var appliedPatches: PatchSelection? by mutableStateOf(null) @@ -49,38 +51,48 @@ class InstalledAppInfoViewModel( init { viewModelScope.launch { - isMounted = rootInstaller.isAppMounted(installedApp.currentPackageName) + installedApp = installedAppRepository.get(packageName)?.also { + isMounted = rootInstaller.isAppMounted(it.currentPackageName) + appInfo = withContext(Dispatchers.IO) { + pm.getPackageInfo(it.currentPackageName) + } + appliedPatches = withContext(Dispatchers.IO) { + installedAppRepository.getAppliedPatches(it.currentPackageName) + } + } } } - fun launch() = pm.launch(installedApp.currentPackageName) + fun launch() = installedApp?.currentPackageName?.let(pm::launch) fun mountOrUnmount() = viewModelScope.launch { + val pkgName = installedApp?.currentPackageName ?: return@launch try { if (isMounted) - rootInstaller.unmount(installedApp.currentPackageName) + rootInstaller.unmount(pkgName) else - rootInstaller.mount(installedApp.currentPackageName) + rootInstaller.mount(pkgName) } catch (e: Exception) { if (isMounted) { - app.toast(app.getString(R.string.failed_to_unmount, e.simpleMessage())) + context.toast(context.getString(R.string.failed_to_unmount, e.simpleMessage())) Log.e(tag, "Failed to unmount", e) } else { - app.toast(app.getString(R.string.failed_to_mount, e.simpleMessage())) + context.toast(context.getString(R.string.failed_to_mount, e.simpleMessage())) Log.e(tag, "Failed to mount", e) } } finally { - isMounted = rootInstaller.isAppMounted(installedApp.currentPackageName) + isMounted = rootInstaller.isAppMounted(pkgName) } } fun uninstall() { - when (installedApp.installType) { - InstallType.DEFAULT -> pm.uninstallPackage(installedApp.currentPackageName) + val app = installedApp ?: return + when (app.installType) { + InstallType.DEFAULT -> pm.uninstallPackage(app.currentPackageName) InstallType.MOUNT -> viewModelScope.launch { - rootInstaller.uninstall(installedApp.currentPackageName) - installedAppRepository.delete(installedApp) + rootInstaller.uninstall(app.currentPackageName) + installedAppRepository.delete(app) onBackClick() } } @@ -97,34 +109,22 @@ class InstalledAppInfoViewModel( if (extraStatus == PackageInstaller.STATUS_SUCCESS) { viewModelScope.launch { - installedAppRepository.delete(installedApp) + installedApp?.let { + installedAppRepository.delete(it) + } onBackClick() } } else if (extraStatus != PackageInstaller.STATUS_FAILURE_ABORTED) { - app.toast(app.getString(R.string.uninstall_app_fail, extraStatusMessage)) + this@InstalledAppInfoViewModel.context.toast(this@InstalledAppInfoViewModel.context.getString(R.string.uninstall_app_fail, extraStatusMessage)) } } } } - } - - init { - viewModelScope.launch { - appInfo = withContext(Dispatchers.IO) { - pm.getPackageInfo(installedApp.currentPackageName) - } - } - - viewModelScope.launch { - appliedPatches = withContext(Dispatchers.IO) { - installedAppRepository.getAppliedPatches(installedApp.currentPackageName) - } - } - + }.also { ContextCompat.registerReceiver( - app, - uninstallBroadcastReceiver, + context, + it, IntentFilter(UninstallService.APP_UNINSTALL_ACTION), ContextCompat.RECEIVER_NOT_EXPORTED ) @@ -132,6 +132,6 @@ class InstalledAppInfoViewModel( override fun onCleared() { super.onCleared() - app.unregisterReceiver(uninstallBroadcastReceiver) + context.unregisterReceiver(uninstallBroadcastReceiver) } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt index c8577295..17ef0aef 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt @@ -47,6 +47,7 @@ import app.revanced.manager.ui.model.State import app.revanced.manager.ui.model.Step import app.revanced.manager.ui.model.StepCategory import app.revanced.manager.ui.model.StepProgressProvider +import app.revanced.manager.ui.model.navigation.Patcher import app.revanced.manager.util.PM import app.revanced.manager.util.saveableVar import app.revanced.manager.util.saver.snapshotStateListSaver @@ -69,10 +70,11 @@ import org.koin.core.component.inject import java.io.File import java.nio.file.Files import java.time.Duration +import java.util.UUID @OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class) class PatcherViewModel( - private val input: Destination.Patcher + private val input: Patcher ) : ViewModel(), KoinComponent, StepProgressProvider, InstallerModel { private val app: Application by inject() private val fs: Filesystem by inject() 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 3e16d69e..23f13685 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 @@ -33,6 +33,7 @@ import app.revanced.manager.plugin.downloader.GetScope import app.revanced.manager.plugin.downloader.PluginHostApi import app.revanced.manager.plugin.downloader.UserInteractionException import app.revanced.manager.ui.model.BundleInfo +import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow import app.revanced.manager.ui.model.BundleInfo.Extensions.toPatchSelection import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.util.Options @@ -110,7 +111,10 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent { } } - val requiredVersion = combine(prefs.suggestedVersionSafeguard.flow, bundleRepository.suggestedVersions) { suggestedVersionSafeguard, suggestedVersions -> + val requiredVersion = combine( + prefs.suggestedVersionSafeguard.flow, + bundleRepository.suggestedVersions + ) { suggestedVersionSafeguard, suggestedVersions -> if (!suggestedVersionSafeguard) return@combine null suggestedVersions[input.app.packageName] @@ -264,17 +268,15 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent { ): PatchSelection? = (selectionState as? SelectionState.Customized)?.patches(bundles, allowUnsupported) - fun updateConfiguration( - selection: PatchSelection?, - options: Options, - bundles: List - ) { + fun updateConfiguration(selection: PatchSelection?, options: Options) = viewModelScope.launch { + val bundles = bundlesRepo.bundleInfoFlow(packageName, selectedApp.version).first() + selectionState = selection?.let(SelectionState::Customized) ?: SelectionState.Default val filteredOptions = options.filtered(bundles) - this.options = filteredOptions + this@SelectedAppInfoViewModel.options = filteredOptions - if (!persistConfiguration) return + if (!persistConfiguration) return@launch viewModelScope.launch(Dispatchers.Default) { selection?.let { selectionRepository.updateSelection(packageName, it) } ?: selectionRepository.clearSelection(packageName) diff --git a/app/src/main/java/app/revanced/manager/util/Util.kt b/app/src/main/java/app/revanced/manager/util/Util.kt index 1359ae24..6c0282ad 100644 --- a/app/src/main/java/app/revanced/manager/util/Util.kt +++ b/app/src/main/java/app/revanced/manager/util/Util.kt @@ -3,11 +3,6 @@ package app.revanced.manager.util import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo -import android.icu.number.Notation -import android.icu.number.NumberFormatter -import android.icu.number.Precision -import android.icu.text.CompactDecimalFormat -import android.os.Build import android.util.Log import android.widget.Toast import androidx.annotation.MainThread @@ -54,13 +49,14 @@ import kotlinx.datetime.format.MonthNames import kotlinx.datetime.format.char import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime +import kotlinx.serialization.Contextual import java.util.Locale import kotlin.properties.PropertyDelegateProvider import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty typealias PatchSelection = Map> -typealias Options = Map>> +typealias Options = Map>> val Context.isDebuggable get() = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 325e1127..979f2802 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ appcompat = "1.7.0" preferences-datastore = "1.1.1" work-runtime = "2.10.0" compose-bom = "2024.12.01" +navigation = "2.8.4" accompanist = "0.34.0" placeholder = "1.1.2" reorderable = "1.5.2" @@ -57,8 +58,9 @@ compose-ui = { group = "androidx.compose.ui", name = "ui" } compose-ui-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "ui-tooling" } compose-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata" } -compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3"} +compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } +navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } # Coil coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" } @@ -87,6 +89,7 @@ revanced-library = { group = "app.revanced", name = "revanced-library", version. # Koin koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin-version" } koin-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin-version-compose" } +koin-compose-navigation = { group = "io.insert-koin", name = "koin-androidx-compose-navigation", version.ref = "koin-version-compose" } koin-workmanager = { group = "io.insert-koin", name = "koin-androidx-workmanager", version.ref = "koin-version" } # Compose Navigation