diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 87f37444..de997d72 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,11 +174,9 @@ dependencies { // Koin implementation(libs.koin.android) implementation(libs.koin.compose) + implementation(libs.koin.compose.navigation) implementation(libs.koin.workmanager) - // Compose Navigation - implementation(libs.reimagined.navigation) - // Licenses implementation(libs.about.libraries) diff --git a/app/src/main/java/app/revanced/manager/MainActivity.kt b/app/src/main/java/app/revanced/manager/MainActivity.kt index ff01094c..61393481 100644 --- a/app/src/main/java/app/revanced/manager/MainActivity.kt +++ b/app/src/main/java/app/revanced/manager/MainActivity.kt @@ -1,36 +1,38 @@ package app.revanced.manager import android.os.Bundle +import android.os.Parcelable import androidx.activity.ComponentActivity 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.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 +import org.koin.androidx.viewmodel.ext.android.getViewModel as getActivityViewModel class MainActivity : ComponentActivity() { @ExperimentalAnimationApi @@ -41,7 +43,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() installSplashScreen() - val vm: MainViewModel = getAndroidViewModel() + val vm: MainViewModel = getActivityViewModel() vm.importLegacySettings(this) setContent { @@ -52,79 +54,203 @@ class MainActivity : ComponentActivity() { darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK, dynamicColor = dynamicColor ) { - val navController = - rememberNavController(startDestination = Destination.Dashboard) - NavBackHandler(navController) - - EventEffect(vm.appSelectFlow) { app -> - navController.navigate(Destination.SelectedApplicationInfo(app)) - } - - AnimatedNavHost( - controller = navController - ) { destination -> - when (destination) { - is Destination.Dashboard -> DashboardScreen( - onSettingsClick = { navController.navigate(Destination.Settings()) }, - onAppSelectorClick = { navController.navigate(Destination.AppSelector) }, - onUpdateClick = { - navController.navigate(Destination.Settings(SettingsDestination.Update())) - }, - onDownloaderPluginClick = { - navController.navigate(Destination.Settings(SettingsDestination.Downloads)) - }, - onAppClick = { installedApp -> - navController.navigate( - Destination.InstalledApplicationInfo( - installedApp - ) - ) - } - ) - - is Destination.InstalledApplicationInfo -> InstalledAppInfoScreen( - onPatchClick = vm::selectApp, - onBackClick = { navController.pop() }, - viewModel = getComposeViewModel { parametersOf(destination.installedApp) } - ) - - is Destination.Settings -> SettingsScreen( - onBackClick = { navController.pop() }, - startDestination = destination.startDestination - ) - - is Destination.AppSelector -> AppSelectorScreen( - onSelect = vm::selectApp, - onStorageSelect = vm::selectApp, - onBackClick = { navController.pop() } - ) - - is Destination.SelectedApplicationInfo -> SelectedAppInfoScreen( - onPatchClick = { app, patches, options -> - navController.navigate( - Destination.Patcher( - app, patches, options - ) - ) - }, - onBackClick = navController::pop, - vm = getComposeViewModel { - parametersOf( - SelectedAppInfoViewModel.Params( - destination.selectedApp, - destination.patchSelection - ) - ) - } - ) - - is Destination.Patcher -> PatcherScreen( - onBackClick = { navController.popUpTo { it is Destination.Dashboard } }, - vm = getComposeViewModel { parametersOf(destination) } - ) - } - } + ReVancedManager(vm) } } } } + +@Composable +private fun ReVancedManager(vm: MainViewModel) { + val navController = rememberNavController() + + EventEffect(vm.appSelectFlow) { app -> + navController.navigateComplex( + SelectedApplicationInfo, + SelectedApplicationInfo.ViewModelParams(app) + ) + } + + NavHost( + navController = navController, + startDestination = Dashboard, + ) { + composable { + DashboardScreen( + onSettingsClick = { navController.navigate(Settings) }, + onAppSelectorClick = { + navController.navigate(AppSelector) + }, + onUpdateClick = { + navController.navigate(Update()) + }, + onDownloaderPluginClick = { + navController.navigate(Settings.Downloads) + }, + onAppClick = { packageName -> + navController.navigate(InstalledApplicationInfo(packageName)) + } + ) + } + + composable { + val data = it.toRoute() + + InstalledAppInfoScreen( + onPatchClick = vm::selectApp, + onBackClick = navController::popBackStack, + viewModel = koinViewModel { 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.getComplexArg()) } + ) + } + + composable { + val data = it.toRoute() + + UpdateScreen( + onBackClick = navController::popBackStack, + vm = koinViewModel { parametersOf(data.downloadOnScreenEntry) } + ) + } + + navigation(startDestination = SelectedApplicationInfo.Main) { + composable { + val parentBackStackEntry = navController.navGraphEntry(it) + val data = + parentBackStackEntry.getComplexArg() + + SelectedAppInfoScreen( + onBackClick = navController::popBackStack, + onPatchClick = { app, patches, options -> + navController.navigateComplex( + Patcher, + Patcher.ViewModelParams(app, patches, options) + ) + }, + onPatchSelectorClick = { app, patches, options -> + navController.navigateComplex( + SelectedApplicationInfo.PatchesSelector, + SelectedApplicationInfo.PatchesSelector.ViewModelParams( + app, + patches, + options + ) + ) + }, + vm = koinNavViewModel(viewModelStoreOwner = parentBackStackEntry) { + parametersOf(data) + } + ) + } + + composable { + val data = + it.getComplexArg() + val selectedAppInfoVm = koinNavViewModel( + viewModelStoreOwner = navController.navGraphEntry(it) + ) + + PatchesSelectorScreen( + onBackClick = navController::popBackStack, + onSave = { patches, options -> + selectedAppInfoVm.updateConfiguration(patches, options) + navController.popBackStack() + }, + vm = koinViewModel { parametersOf(data) } + ) + } + } + + 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!!.id) } + +// Androidx Navigation does not support storing complex types in route objects, so we have to store them inside the saved state handle of the back stack entry instead. +private fun > NavController.navigateComplex( + route: R, + data: T +) { + navigate(route) + getBackStackEntry(route).savedStateHandle["args"] = data +} + +private fun NavBackStackEntry.getComplexArg() = savedStateHandle.get("args")!! \ 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 deleted file mode 100644 index 93c59411..00000000 --- a/app/src/main/java/app/revanced/manager/ui/destination/Destination.kt +++ /dev/null @@ -1,31 +0,0 @@ -package app.revanced.manager.ui.destination - -import android.os.Parcelable -import app.revanced.manager.data.room.apps.installed.InstalledApp -import app.revanced.manager.ui.model.SelectedApp -import app.revanced.manager.util.Options -import app.revanced.manager.util.PatchSelection -import kotlinx.parcelize.Parcelize -import kotlinx.parcelize.RawValue - -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 - -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/destination/SelectedAppInfoDestination.kt b/app/src/main/java/app/revanced/manager/ui/destination/SelectedAppInfoDestination.kt deleted file mode 100644 index 9a1f3e29..00000000 --- a/app/src/main/java/app/revanced/manager/ui/destination/SelectedAppInfoDestination.kt +++ /dev/null @@ -1,16 +0,0 @@ -package app.revanced.manager.ui.destination - -import android.os.Parcelable -import app.revanced.manager.ui.model.SelectedApp -import app.revanced.manager.util.Options -import app.revanced.manager.util.PatchSelection -import kotlinx.parcelize.Parcelize -import kotlinx.parcelize.RawValue - -sealed interface SelectedAppInfoDestination : Parcelable { - @Parcelize - data object Main : SelectedAppInfoDestination - - @Parcelize - data class PatchesSelector(val app: SelectedApp, val currentSelection: PatchSelection?, val options: @RawValue Options) : SelectedAppInfoDestination -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/destination/SettingsDestination.kt b/app/src/main/java/app/revanced/manager/ui/destination/SettingsDestination.kt deleted file mode 100644 index e62ab4f4..00000000 --- a/app/src/main/java/app/revanced/manager/ui/destination/SettingsDestination.kt +++ /dev/null @@ -1,43 +0,0 @@ -package app.revanced.manager.ui.destination - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -sealed interface SettingsDestination : Parcelable { - - @Parcelize - data object Settings : SettingsDestination - - @Parcelize - data object General : SettingsDestination - - @Parcelize - data object Advanced : SettingsDestination - - @Parcelize - data object Updates : SettingsDestination - - @Parcelize - data object Downloads : SettingsDestination - - @Parcelize - data object ImportExport : SettingsDestination - - @Parcelize - data object About : SettingsDestination - - @Parcelize - data class Update(val downloadOnScreenEntry: Boolean = false) : SettingsDestination - - @Parcelize - data object Changelogs : SettingsDestination - - @Parcelize - data object Contributors: SettingsDestination - - @Parcelize - data object Licenses: SettingsDestination - - @Parcelize - data object DeveloperOptions: SettingsDestination -} \ No newline at end of file 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..ad235a3a --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/model/navigation/Nav.kt @@ -0,0 +1,93 @@ +package app.revanced.manager.ui.model.navigation + +import android.os.Parcelable +import app.revanced.manager.ui.model.SelectedApp +import app.revanced.manager.util.Options +import app.revanced.manager.util.PatchSelection +import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.RawValue +import kotlinx.serialization.Serializable + +interface ComplexParameter + +@Serializable +object Dashboard + +@Serializable +object AppSelector + +@Serializable +data class InstalledApplicationInfo(val packageName: String) + +@Serializable +data class Update(val downloadOnScreenEntry: Boolean = false) + +@Serializable +data object SelectedApplicationInfo : ComplexParameter { + @Parcelize + data class ViewModelParams( + val app: SelectedApp, + val patches: PatchSelection? = null + ) : Parcelable + + @Serializable + object Main + + @Serializable + data object PatchesSelector : ComplexParameter { + @Parcelize + data class ViewModelParams( + val app: SelectedApp, + val currentSelection: PatchSelection?, + val options: @RawValue Options, + ) : Parcelable + } +} + +@Serializable +data object Patcher : ComplexParameter { + @Parcelize + data class ViewModelParams( + val selectedApp: SelectedApp, + val selectedPatches: PatchSelection, + val options: @RawValue Options + ) : Parcelable +} + +@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/DashboardScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt index 55fc771b..2557c436 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 @@ -25,7 +25,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.revanced.manager.R -import app.revanced.manager.data.room.apps.installed.InstalledApp import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault import app.revanced.manager.patcher.aapt.Aapt import app.revanced.manager.ui.component.AlertDialogExtended @@ -60,7 +59,7 @@ fun DashboardScreen( onSettingsClick: () -> Unit, onUpdateClick: () -> Unit, onDownloaderPluginClick: () -> Unit, - onAppClick: (InstalledApp) -> Unit + onAppClick: (String) -> Unit ) { val bundlesSelectable by remember { derivedStateOf { vm.selectedSources.size > 0 } } val availablePatches by vm.availablePatches.collectAsStateWithLifecycle(0) @@ -289,7 +288,7 @@ fun DashboardScreen( when (DashboardPage.entries[index]) { DashboardPage.DASHBOARD -> { InstalledAppsScreen( - onAppClick = onAppClick + onAppClick = { onAppClick(it.currentPackageName) } ) } 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..00441dae 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 @@ -32,10 +32,8 @@ import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.LoadingIndicator import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton -import app.revanced.manager.ui.destination.SelectedAppInfoDestination import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow import app.revanced.manager.ui.model.SelectedApp -import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel import app.revanced.manager.util.EventEffect import app.revanced.manager.util.Options @@ -43,13 +41,11 @@ import app.revanced.manager.util.PatchSelection import app.revanced.manager.util.enabled import app.revanced.manager.util.toast import app.revanced.manager.util.transparentListItemColors -import dev.olshevski.navigation.reimagined.* -import org.koin.androidx.compose.koinViewModel -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,147 +78,119 @@ 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 - ) - ) - } - ) - 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) - ) - } - } - } - - is SelectedAppInfoDestination.PatchesSelector -> PatchesSelectorScreen( - onSave = { patches, options -> - vm.updateConfiguration(patches, options, bundles) - navController.pop() - }, - onBackClick = navController::pop, - vm = koinViewModel { - parametersOf( - PatchesSelectorViewModel.Params( - destination.app, - destination.currentSelection, - destination.options, - ) + 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, + ) + } + + PageItem( + R.string.patch_selector_item, + stringResource( + R.string.patch_selector_item_description, + selectedPatchCount + ), + onClick = { + onPatchSelectorClick( + 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) + ) + } + } } } 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..46a25b10 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,146 +13,64 @@ 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, - startDestination: SettingsDestination, - viewModel: SettingsViewModel = koinViewModel() -) { - val navController = rememberNavController(startDestination) - - val backClick: () -> Unit = { - if (navController.backstack.entries.size == 1) - onBackClick() - else navController.pop() - } - - val settingsSections = listOf( - Triple( - R.string.general, - R.string.general_description, - Icons.Outlined.Settings - ) to SettingsDestination.General, - Triple( - R.string.updates, - R.string.updates_description, - Icons.Outlined.Update - ) to SettingsDestination.Updates, - Triple( - R.string.downloads, - R.string.downloads_description, - Icons.Outlined.Download - ) to SettingsDestination.Downloads, - Triple( - R.string.import_export, - R.string.import_export_description, - Icons.Outlined.SwapVert - ) to SettingsDestination.ImportExport, - Triple( - R.string.advanced, - R.string.advanced_description, - Icons.Outlined.Tune - ) to SettingsDestination.Advanced, - Triple( - R.string.about, - R.string.app_name, - Icons.Outlined.Info - ) to SettingsDestination.About, - ) - NavBackHandler(navController) - - AnimatedNavHost( - controller = navController - ) { destination -> - when (destination) { - is SettingsDestination.General -> GeneralSettingsScreen( - onBackClick = backClick, - viewModel = viewModel +fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) -> Unit) { + Scaffold( + topBar = { + AppTopBar( + title = stringResource(R.string.settings), + onBackClick = onBackClick, ) - - is SettingsDestination.Advanced -> AdvancedSettingsScreen( - onBackClick = backClick - ) - - is SettingsDestination.Updates -> UpdatesSettingsScreen( - onBackClick = backClick, - onChangelogClick = { navController.navigate(SettingsDestination.Changelogs) }, - onUpdateClick = { navController.navigate(SettingsDestination.Update(false)) } - ) - - is SettingsDestination.Downloads -> DownloadsSettingsScreen( - onBackClick = backClick - ) - - is SettingsDestination.ImportExport -> ImportExportSettingsScreen( - onBackClick = backClick - ) - - is SettingsDestination.About -> AboutSettingsScreen( - onBackClick = backClick, - onContributorsClick = { navController.navigate(SettingsDestination.Contributors) }, - onDeveloperOptionsClick = { navController.navigate(SettingsDestination.DeveloperOptions) }, - onLicensesClick = { navController.navigate(SettingsDestination.Licenses) }, - ) - - is SettingsDestination.Update -> UpdateScreen( - onBackClick = backClick, - vm = koinViewModel { - parametersOf( - destination.downloadOnScreenEntry - ) - } - ) - - is SettingsDestination.Changelogs -> ChangelogsScreen( - onBackClick = backClick, - ) - - is SettingsDestination.Contributors -> ContributorScreen( - onBackClick = backClick, - ) - - is SettingsDestination.Licenses -> LicensesScreen( - onBackClick = backClick, - ) - - is SettingsDestination.DeveloperOptions -> DeveloperOptionsScreen(onBackClick = backClick) - - is SettingsDestination.Settings -> { - Scaffold( - topBar = { - AppTopBar( - title = stringResource(R.string.settings), - onBackClick = backClick, - ) - } - ) { paddingValues -> - ColumnWithScrollbar( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - ) { - settingsSections.forEach { (titleDescIcon, destination) -> - SettingsListItem( - modifier = Modifier.clickable { navController.navigate(destination) }, - headlineContent = stringResource(titleDescIcon.first), - supportingContent = stringResource(titleDescIcon.second), - leadingContent = { Icon(titleDescIcon.third, null) } - ) - } - } - } + } + ) { 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) } + ) } } } 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..4ca53667 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 @@ -39,7 +39,6 @@ import app.revanced.manager.plugin.downloader.PluginHostApi import app.revanced.manager.plugin.downloader.UserInteractionException import app.revanced.manager.service.InstallService import app.revanced.manager.service.UninstallService -import app.revanced.manager.ui.destination.Destination import app.revanced.manager.ui.model.InstallerModel import app.revanced.manager.ui.model.ProgressKey import app.revanced.manager.ui.model.SelectedApp @@ -47,6 +46,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 @@ -72,7 +72,7 @@ import java.time.Duration @OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class) class PatcherViewModel( - private val input: Destination.Patcher + private val input: Patcher.ViewModelParams ) : 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/PatchesSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt index b2986fbb..ff815266 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 @@ -23,6 +23,7 @@ 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.ui.model.navigation.SelectedApplicationInfo import app.revanced.manager.util.Options import app.revanced.manager.util.PatchSelection import app.revanced.manager.util.saver.Nullable @@ -40,7 +41,7 @@ import kotlinx.coroutines.flow.map @Stable @OptIn(SavedStateHandleSaveableApi::class) -class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent { +class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.ViewModelParams) : ViewModel(), KoinComponent { private val app: Application = get() private val savedStateHandle: SavedStateHandle = get() private val prefs: PreferencesManager = get() @@ -214,12 +215,6 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent { private val selectionSaver: Saver> = nullableSaver(persistentMapSaver(valueSaver = persistentSetSaver())) } - - data class Params( - val app: SelectedApp, - val currentSelection: PatchSelection?, - val options: Options, - ) } // Versions of other types, but utilizing persistent/observable collection types. 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..cdf87267 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,8 +33,10 @@ 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.ui.model.navigation.SelectedApplicationInfo import app.revanced.manager.util.Options import app.revanced.manager.util.PM import app.revanced.manager.util.PatchSelection @@ -57,7 +59,9 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.get @OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class) -class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent { +class SelectedAppInfoViewModel( + input: SelectedApplicationInfo.ViewModelParams +) : ViewModel(), KoinComponent { private val app: Application = get() val bundlesRepo: PatchBundleRepository = get() private val bundleRepository: PatchBundleRepository = get() @@ -110,7 +114,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 +271,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) @@ -283,11 +288,6 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent { } } - data class Params( - val app: SelectedApp, - val patches: PatchSelection?, - ) - enum class Error(@StringRes val resourceId: Int) { NoPlugins(R.string.downloader_no_plugins_available) } 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..09c22022 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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 325e1127..08cbb2d3 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.5" accompanist = "0.34.0" placeholder = "1.1.2" reorderable = "1.5.2" @@ -18,9 +19,7 @@ datetime = "0.6.0" room-version = "2.6.1" revanced-patcher = "21.0.0" revanced-library = "3.0.2" -koin-version = "3.5.3" -koin-version-compose = "3.5.3" -reimagined-navigation = "1.5.0" +koin = "3.5.3" ktor = "2.3.9" markdown-renderer = "0.22.0" fading-edges = "1.0.4" @@ -57,8 +56,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" } @@ -85,12 +85,10 @@ revanced-patcher = { group = "app.revanced", name = "revanced-patcher", version. revanced-library = { group = "app.revanced", name = "revanced-library", version.ref = "revanced-library" } # 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-workmanager = { group = "io.insert-koin", name = "koin-androidx-workmanager", version.ref = "koin-version" } - -# Compose Navigation -reimagined-navigation = { group = "dev.olshevski.navigation", name = "reimagined", version.ref = "reimagined-navigation" } +koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" } +koin-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin" } +koin-compose-navigation = { group = "io.insert-koin", name = "koin-androidx-compose-navigation", version.ref = "koin" } +koin-workmanager = { group = "io.insert-koin", name = "koin-androidx-workmanager", version.ref = "koin" } # About Libraries about-libraries = { group = "com.mikepenz", name = "aboutlibraries-compose", version.ref = "about-libraries-gradle-plugin" }