not supporting parcelables is cringe

This commit is contained in:
Ax333l 2024-12-12 16:01:52 +01:00
parent 8a20d8cf9b
commit 251b9eef69
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
18 changed files with 595 additions and 225 deletions

View File

@ -126,6 +126,7 @@ dependencies {
implementation(libs.compose.livedata) implementation(libs.compose.livedata)
implementation(libs.compose.material.icons.extended) implementation(libs.compose.material.icons.extended)
implementation(libs.compose.material3) implementation(libs.compose.material3)
implementation(libs.navigation.compose)
// Accompanist // Accompanist
implementation(libs.accompanist.drawablepainter) implementation(libs.accompanist.drawablepainter)
@ -173,6 +174,7 @@ dependencies {
// Koin // Koin
implementation(libs.koin.android) implementation(libs.koin.android)
implementation(libs.koin.compose) implementation(libs.koin.compose)
implementation(libs.koin.compose.navigation)
implementation(libs.koin.workmanager) implementation(libs.koin.workmanager)
// Compose Navigation // Compose Navigation

View File

@ -6,28 +6,31 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import app.revanced.manager.ui.destination.Destination import androidx.navigation.NavBackStackEntry
import app.revanced.manager.ui.destination.SettingsDestination import androidx.navigation.NavController
import app.revanced.manager.ui.screen.AppSelectorScreen import androidx.navigation.compose.NavHost
import app.revanced.manager.ui.screen.DashboardScreen import androidx.navigation.compose.composable
import app.revanced.manager.ui.screen.InstalledAppInfoScreen import androidx.navigation.compose.navigation
import app.revanced.manager.ui.screen.PatcherScreen import androidx.navigation.compose.rememberNavController
import app.revanced.manager.ui.screen.SelectedAppInfoScreen import androidx.navigation.toRoute
import app.revanced.manager.ui.screen.SettingsScreen 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.ReVancedManagerTheme
import app.revanced.manager.ui.theme.Theme import app.revanced.manager.ui.theme.Theme
import app.revanced.manager.ui.viewmodel.MainViewModel 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.ui.viewmodel.SelectedAppInfoViewModel
import app.revanced.manager.util.EventEffect import app.revanced.manager.util.EventEffect
import dev.olshevski.navigation.reimagined.AnimatedNavHost import org.koin.androidx.compose.koinViewModel
import dev.olshevski.navigation.reimagined.NavBackHandler import org.koin.androidx.compose.navigation.koinNavViewModel
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.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koin.androidx.compose.koinViewModel as getComposeViewModel 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 getAndroidViewModel
@ -52,6 +55,200 @@ class MainActivity : ComponentActivity() {
darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK, darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK,
dynamicColor = dynamicColor 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<Dashboard> {
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<InstalledApplicationInfo> {
val data = it.toRoute<InstalledApplicationInfo>()
InstalledAppInfoScreen(
onPatchClick = vm::selectApp,
onBackClick = navController::popBackStack,
viewModel = getComposeViewModel { parametersOf(data.packageName) }
)
}
composable<AppSelector> {
AppSelectorScreen(
onSelect = vm::selectApp,
onStorageSelect = vm::selectApp,
onBackClick = navController::popBackStack
)
}
composable<Patcher> {
PatcherScreen(
onBackClick = {
navController.navigate(route = Dashboard) {
launchSingleTop = true
popUpTo<Dashboard> {
inclusive = false
}
}
},
vm = koinViewModel { parametersOf(it.toRoute<Patcher>()) }
)
}
navigation<SelectedApplicationInfo>(startDestination = SelectedApplicationInfo.Main) {
composable<SelectedApplicationInfo.Main> {
val parentBackStackEntry = navController.navGraphEntry(it)
val data = parentBackStackEntry.toRoute<SelectedApplicationInfo>()
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<SelectedAppInfoViewModel>(viewModelStoreOwner = parentBackStackEntry) {
parametersOf(
SelectedAppInfoViewModel.Params(
data.selectedApp,
data.patchSelection
)
)
}
)
}
composable<SelectedApplicationInfo.PatchesSelector>(
// typeMap = mapOf(typeOf<SelectedApplicationInfo.PatchesSelector>() to SelectedApplicationInfo.PatchesSelector.navType)
) {
val data = it.toRoute<SelectedApplicationInfo.PatchesSelector>()
val selectedAppInfoVm = koinNavViewModel<SelectedAppInfoViewModel>(
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<Settings>(startDestination = Settings.Main) {
composable<Settings.Main> {
SettingsScreen(
onBackClick = navController::popBackStack,
navigate = navController::navigate
)
}
composable<Settings.General> {
GeneralSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Advanced> {
AdvancedSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Updates> {
UpdatesSettingsScreen(
onBackClick = navController::popBackStack,
onChangelogClick = { navController.navigate(Settings.Changelogs) },
onUpdateClick = { navController.navigate(Update()) }
)
}
composable<Settings.Downloads> {
DownloadsSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.ImportExport> {
ImportExportSettingsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.About> {
AboutSettingsScreen(
onBackClick = navController::popBackStack,
navigate = navController::navigate
)
}
composable<Settings.Changelogs> {
ChangelogsScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Contributors> {
ContributorScreen(onBackClick = navController::popBackStack)
}
composable<Settings.Licenses> {
LicensesScreen(onBackClick = navController::popBackStack)
}
composable<Settings.DeveloperOptions> {
DeveloperOptionsScreen(onBackClick = navController::popBackStack)
}
}
}
}
@Composable
private fun NavController.navGraphEntry(entry: NavBackStackEntry) = remember(entry) {
getBackStackEntry(entry.destination.parent!!)
}
/*
val navController = val navController =
rememberNavController<Destination>(startDestination = Destination.Dashboard) rememberNavController<Destination>(startDestination = Destination.Dashboard)
NavBackHandler(navController) NavBackHandler(navController)
@ -124,7 +321,4 @@ class MainActivity : ComponentActivity() {
) )
} }
} }
} */
}
}
}

View File

@ -1,18 +1,15 @@
package app.revanced.manager.data.room.apps.installed package app.revanced.manager.data.room.apps.installed
import android.os.Parcelable
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import app.revanced.manager.R import app.revanced.manager.R
import kotlinx.parcelize.Parcelize
enum class InstallType(val stringResource: Int) { enum class InstallType(val stringResource: Int) {
DEFAULT(R.string.default_install), DEFAULT(R.string.default_install),
MOUNT(R.string.mount_install) MOUNT(R.string.mount_install)
} }
@Parcelize
@Entity(tableName = "installed_app") @Entity(tableName = "installed_app")
data class InstalledApp( data class InstalledApp(
@PrimaryKey @PrimaryKey
@ -20,4 +17,4 @@ data class InstalledApp(
@ColumnInfo(name = "original_package_name") val originalPackageName: String, @ColumnInfo(name = "original_package_name") val originalPackageName: String,
@ColumnInfo(name = "version") val version: String, @ColumnInfo(name = "version") val version: String,
@ColumnInfo(name = "install_type") val installType: InstallType @ColumnInfo(name = "install_type") val installType: InstallType
) : Parcelable )

View File

@ -9,7 +9,7 @@ val viewModelModule = module {
viewModelOf(::DashboardViewModel) viewModelOf(::DashboardViewModel)
viewModelOf(::SelectedAppInfoViewModel) viewModelOf(::SelectedAppInfoViewModel)
viewModelOf(::PatchesSelectorViewModel) viewModelOf(::PatchesSelectorViewModel)
viewModelOf(::SettingsViewModel) viewModelOf(::GeneralSettingsViewModel)
viewModelOf(::AdvancedSettingsViewModel) viewModelOf(::AdvancedSettingsViewModel)
viewModelOf(::AppSelectorViewModel) viewModelOf(::AppSelectorViewModel)
viewModelOf(::PatcherViewModel) viewModelOf(::PatcherViewModel)

View File

@ -13,8 +13,8 @@ sealed interface Destination : Parcelable {
@Parcelize @Parcelize
data object Dashboard : Destination data object Dashboard : Destination
@Parcelize // @Parcelize
data class InstalledApplicationInfo(val installedApp: InstalledApp) : Destination // data class InstalledApplicationInfo(val installedApp: InstalledApp) : Destination
@Parcelize @Parcelize
data object AppSelector : Destination data object AppSelector : Destination

View File

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

View File

@ -77,10 +77,12 @@ fun InstalledAppInfoScreen(
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .padding(paddingValues)
) { ) {
AppInfo(viewModel.appInfo) { val installedApp = viewModel.installedApp ?: return@ColumnWithScrollbar
Text(viewModel.installedApp.version, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium)
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(
text = if (viewModel.isMounted) { text = if (viewModel.isMounted) {
stringResource(R.string.mounted) stringResource(R.string.mounted)
@ -104,7 +106,7 @@ fun InstalledAppInfoScreen(
onClick = viewModel::launch onClick = viewModel::launch
) )
when (viewModel.installedApp.installType) { when (installedApp.installType) {
InstallType.DEFAULT -> SegmentedButton( InstallType.DEFAULT -> SegmentedButton(
icon = Icons.Outlined.Delete, icon = Icons.Outlined.Delete,
text = stringResource(R.string.uninstall), text = stringResource(R.string.uninstall),
@ -133,9 +135,9 @@ fun InstalledAppInfoScreen(
icon = Icons.Outlined.Update, icon = Icons.Outlined.Update,
text = stringResource(R.string.repatch), text = stringResource(R.string.repatch),
onClick = { 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( SettingsListItem(
headlineContent = stringResource(R.string.package_name), 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( SettingsListItem(
headlineContent = stringResource(R.string.original_package_name), headlineContent = stringResource(R.string.original_package_name),
supportingContent = viewModel.installedApp.originalPackageName supportingContent = installedApp.originalPackageName
) )
} }
SettingsListItem( SettingsListItem(
headlineContent = stringResource(R.string.install_type), headlineContent = stringResource(R.string.install_type),
supportingContent = stringResource(viewModel.installedApp.installType.stringResource) supportingContent = stringResource(installedApp.installType.stringResource)
) )
} }
} }

View File

@ -50,6 +50,7 @@ import org.koin.core.parameter.parametersOf
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SelectedAppInfoScreen( fun SelectedAppInfoScreen(
onPatchSelectorClick: (SelectedApp, PatchSelection?, Options) -> Unit,
onPatchClick: (SelectedApp, PatchSelection, Options) -> Unit, onPatchClick: (SelectedApp, PatchSelection, Options) -> Unit,
onBackClick: () -> Unit, onBackClick: () -> Unit,
vm: SelectedAppInfoViewModel vm: SelectedAppInfoViewModel
@ -82,131 +83,135 @@ fun SelectedAppInfoScreen(
launcher.launch(intent) launcher.launch(intent)
} }
val navController = val error by vm.errorFlow.collectAsStateWithLifecycle(null)
rememberNavController<SelectedAppInfoDestination>(startDestination = SelectedAppInfoDestination.Main) Scaffold(
topBar = {
AppTopBar(
title = stringResource(R.string.app_info),
onBackClick = onBackClick
)
},
floatingActionButton = {
if (error != null) return@Scaffold
NavBackHandler(controller = navController) HapticExtendedFloatingActionButton(
text = { Text(stringResource(R.string.patch)) },
AnimatedNavHost(controller = navController) { destination -> icon = {
val error by vm.errorFlow.collectAsStateWithLifecycle(null) Icon(
when (destination) { Icons.Default.AutoFixHigh,
is SelectedAppInfoDestination.Main -> Scaffold( stringResource(R.string.patch)
topBar = {
AppTopBar(
title = stringResource(R.string.app_info),
onBackClick = onBackClick
) )
}, },
floatingActionButton = { onClick = patchClick@{
if (error != null) return@Scaffold if (selectedPatchCount == 0) {
context.toast(context.getString(R.string.no_patches_selected))
HapticExtendedFloatingActionButton( return@patchClick
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,
)
} }
onPatchClick(
PageItem( vm.selectedApp,
R.string.patch_selector_item, patches,
stringResource( vm.getOptionsFiltered(bundles)
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)
)
}
} }
)
}
) { 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 -> onSave = { patches, options ->
vm.updateConfiguration(patches, options, bundles) vm.updateConfiguration(patches, options, bundles)
navController.pop() navController.pop()
@ -222,9 +227,7 @@ fun SelectedAppInfoScreen(
) )
} }
) )
} */
}
}
@Composable @Composable
private fun PageItem(@StringRes title: Int, description: String, onClick: () -> Unit) { private fun PageItem(@StringRes title: Int, description: String, onClick: () -> Unit) {

View File

@ -13,21 +13,75 @@ import app.revanced.manager.R
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.destination.SettingsDestination import app.revanced.manager.ui.model.navigation.Settings
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
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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
startDestination: SettingsDestination, startDestination: Settings.Destination,
viewModel: SettingsViewModel = koinViewModel() viewModel: SettingsViewModel = koinViewModel()
) { ) {
val navController = rememberNavController(startDestination) val navController = rememberNavController(startDestination)
@ -156,4 +210,4 @@ fun SettingsScreen(
} }
} }
} }
} }*/

View File

@ -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.AnimatedVisibility
import androidx.compose.animation.core.spring import androidx.compose.animation.core.spring

View File

@ -37,6 +37,7 @@ import app.revanced.manager.network.dto.ReVancedSocial
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.settings.SettingsListItem 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
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon
import app.revanced.manager.util.openUrl import app.revanced.manager.util.openUrl
@ -47,9 +48,7 @@ import org.koin.androidx.compose.koinViewModel
@Composable @Composable
fun AboutSettingsScreen( fun AboutSettingsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
onContributorsClick: () -> Unit, navigate: (Settings.Destination) -> Unit,
onLicensesClick: () -> Unit,
onDeveloperOptionsClick: () -> Unit,
viewModel: AboutViewModel = koinViewModel() viewModel: AboutViewModel = koinViewModel()
) { ) {
val context = LocalContext.current val context = LocalContext.current
@ -114,17 +113,17 @@ fun AboutSettingsScreen(
Triple( Triple(
stringResource(R.string.contributors), stringResource(R.string.contributors),
stringResource(R.string.contributors_description), stringResource(R.string.contributors_description),
third = onContributorsClick third = { navigate(Settings.Contributors) }
), ),
Triple( Triple(
stringResource(R.string.developer_options), stringResource(R.string.developer_options),
stringResource(R.string.developer_options_description), stringResource(R.string.developer_options_description),
third = onDeveloperOptionsClick third = { navigate(Settings.DeveloperOptions) }
), ),
Triple( Triple(
stringResource(R.string.opensource_licenses), stringResource(R.string.opensource_licenses),
stringResource(R.string.opensource_licenses_description), stringResource(R.string.opensource_licenses_description),
third = onLicensesClick third = { navigate(Settings.Licenses) }
) )
) )

View File

@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton 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.BooleanItem
import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.theme.Theme import app.revanced.manager.ui.theme.Theme
import app.revanced.manager.ui.viewmodel.SettingsViewModel import app.revanced.manager.ui.viewmodel.GeneralSettingsViewModel
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject import org.koin.compose.koinInject
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GeneralSettingsScreen( fun GeneralSettingsScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
viewModel: SettingsViewModel viewModel: GeneralSettingsViewModel = koinViewModel()
) { ) {
val prefs = viewModel.prefs val prefs = viewModel.prefs
val coroutineScope = viewModel.viewModelScope val coroutineScope = viewModel.viewModelScope

View File

@ -6,7 +6,7 @@ import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.ui.theme.Theme import app.revanced.manager.ui.theme.Theme
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SettingsViewModel( class GeneralSettingsViewModel(
val prefs: PreferencesManager val prefs: PreferencesManager
) : ViewModel() { ) : ViewModel() {
fun setTheme(theme: Theme) = viewModelScope.launch { fun setTheme(theme: Theme) = viewModelScope.launch {

View File

@ -32,15 +32,17 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
class InstalledAppInfoViewModel( class InstalledAppInfoViewModel(
val installedApp: InstalledApp packageName: String
) : ViewModel(), KoinComponent { ) : ViewModel(), KoinComponent {
private val app: Application by inject() private val context: Application by inject()
private val pm: PM by inject() private val pm: PM by inject()
private val installedAppRepository: InstalledAppRepository by inject() private val installedAppRepository: InstalledAppRepository by inject()
val rootInstaller: RootInstaller by inject() val rootInstaller: RootInstaller by inject()
lateinit var onBackClick: () -> Unit lateinit var onBackClick: () -> Unit
var installedApp: InstalledApp? by mutableStateOf(null)
private set
var appInfo: PackageInfo? by mutableStateOf(null) var appInfo: PackageInfo? by mutableStateOf(null)
private set private set
var appliedPatches: PatchSelection? by mutableStateOf(null) var appliedPatches: PatchSelection? by mutableStateOf(null)
@ -49,38 +51,48 @@ class InstalledAppInfoViewModel(
init { init {
viewModelScope.launch { 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 { fun mountOrUnmount() = viewModelScope.launch {
val pkgName = installedApp?.currentPackageName ?: return@launch
try { try {
if (isMounted) if (isMounted)
rootInstaller.unmount(installedApp.currentPackageName) rootInstaller.unmount(pkgName)
else else
rootInstaller.mount(installedApp.currentPackageName) rootInstaller.mount(pkgName)
} catch (e: Exception) { } catch (e: Exception) {
if (isMounted) { 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) Log.e(tag, "Failed to unmount", e)
} else { } 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) Log.e(tag, "Failed to mount", e)
} }
} finally { } finally {
isMounted = rootInstaller.isAppMounted(installedApp.currentPackageName) isMounted = rootInstaller.isAppMounted(pkgName)
} }
} }
fun uninstall() { fun uninstall() {
when (installedApp.installType) { val app = installedApp ?: return
InstallType.DEFAULT -> pm.uninstallPackage(installedApp.currentPackageName) when (app.installType) {
InstallType.DEFAULT -> pm.uninstallPackage(app.currentPackageName)
InstallType.MOUNT -> viewModelScope.launch { InstallType.MOUNT -> viewModelScope.launch {
rootInstaller.uninstall(installedApp.currentPackageName) rootInstaller.uninstall(app.currentPackageName)
installedAppRepository.delete(installedApp) installedAppRepository.delete(app)
onBackClick() onBackClick()
} }
} }
@ -97,34 +109,22 @@ class InstalledAppInfoViewModel(
if (extraStatus == PackageInstaller.STATUS_SUCCESS) { if (extraStatus == PackageInstaller.STATUS_SUCCESS) {
viewModelScope.launch { viewModelScope.launch {
installedAppRepository.delete(installedApp) installedApp?.let {
installedAppRepository.delete(it)
}
onBackClick() onBackClick()
} }
} else if (extraStatus != PackageInstaller.STATUS_FAILURE_ABORTED) { } 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))
} }
} }
} }
} }
} }.also {
init {
viewModelScope.launch {
appInfo = withContext(Dispatchers.IO) {
pm.getPackageInfo(installedApp.currentPackageName)
}
}
viewModelScope.launch {
appliedPatches = withContext(Dispatchers.IO) {
installedAppRepository.getAppliedPatches(installedApp.currentPackageName)
}
}
ContextCompat.registerReceiver( ContextCompat.registerReceiver(
app, context,
uninstallBroadcastReceiver, it,
IntentFilter(UninstallService.APP_UNINSTALL_ACTION), IntentFilter(UninstallService.APP_UNINSTALL_ACTION),
ContextCompat.RECEIVER_NOT_EXPORTED ContextCompat.RECEIVER_NOT_EXPORTED
) )
@ -132,6 +132,6 @@ class InstalledAppInfoViewModel(
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
app.unregisterReceiver(uninstallBroadcastReceiver) context.unregisterReceiver(uninstallBroadcastReceiver)
} }
} }

View File

@ -47,6 +47,7 @@ import app.revanced.manager.ui.model.State
import app.revanced.manager.ui.model.Step import app.revanced.manager.ui.model.Step
import app.revanced.manager.ui.model.StepCategory import app.revanced.manager.ui.model.StepCategory
import app.revanced.manager.ui.model.StepProgressProvider 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.PM
import app.revanced.manager.util.saveableVar import app.revanced.manager.util.saveableVar
import app.revanced.manager.util.saver.snapshotStateListSaver import app.revanced.manager.util.saver.snapshotStateListSaver
@ -69,10 +70,11 @@ import org.koin.core.component.inject
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.time.Duration import java.time.Duration
import java.util.UUID
@OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class) @OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
class PatcherViewModel( class PatcherViewModel(
private val input: Destination.Patcher private val input: Patcher
) : ViewModel(), KoinComponent, StepProgressProvider, InstallerModel { ) : ViewModel(), KoinComponent, StepProgressProvider, InstallerModel {
private val app: Application by inject() private val app: Application by inject()
private val fs: Filesystem by inject() private val fs: Filesystem by inject()

View File

@ -33,6 +33,7 @@ import app.revanced.manager.plugin.downloader.GetScope
import app.revanced.manager.plugin.downloader.PluginHostApi import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.UserInteractionException import app.revanced.manager.plugin.downloader.UserInteractionException
import app.revanced.manager.ui.model.BundleInfo 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.BundleInfo.Extensions.toPatchSelection
import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.util.Options 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 if (!suggestedVersionSafeguard) return@combine null
suggestedVersions[input.app.packageName] suggestedVersions[input.app.packageName]
@ -264,17 +268,15 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
): PatchSelection? = ): PatchSelection? =
(selectionState as? SelectionState.Customized)?.patches(bundles, allowUnsupported) (selectionState as? SelectionState.Customized)?.patches(bundles, allowUnsupported)
fun updateConfiguration( fun updateConfiguration(selection: PatchSelection?, options: Options) = viewModelScope.launch {
selection: PatchSelection?, val bundles = bundlesRepo.bundleInfoFlow(packageName, selectedApp.version).first()
options: Options,
bundles: List<BundleInfo>
) {
selectionState = selection?.let(SelectionState::Customized) ?: SelectionState.Default selectionState = selection?.let(SelectionState::Customized) ?: SelectionState.Default
val filteredOptions = options.filtered(bundles) val filteredOptions = options.filtered(bundles)
this.options = filteredOptions this@SelectedAppInfoViewModel.options = filteredOptions
if (!persistConfiguration) return if (!persistConfiguration) return@launch
viewModelScope.launch(Dispatchers.Default) { viewModelScope.launch(Dispatchers.Default) {
selection?.let { selectionRepository.updateSelection(packageName, it) } selection?.let { selectionRepository.updateSelection(packageName, it) }
?: selectionRepository.clearSelection(packageName) ?: selectionRepository.clearSelection(packageName)

View File

@ -3,11 +3,6 @@ package app.revanced.manager.util
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ApplicationInfo 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.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.annotation.MainThread import androidx.annotation.MainThread
@ -54,13 +49,14 @@ import kotlinx.datetime.format.MonthNames
import kotlinx.datetime.format.char import kotlinx.datetime.format.char
import kotlinx.datetime.toInstant import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.Contextual
import java.util.Locale import java.util.Locale
import kotlin.properties.PropertyDelegateProvider import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadWriteProperty import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
typealias PatchSelection = Map<Int, Set<String>> typealias PatchSelection = Map<Int, Set<String>>
typealias Options = Map<Int, Map<String, Map<String, Any?>>> typealias Options = Map<Int, Map<String, Map<String, @Contextual Any?>>>
val Context.isDebuggable get() = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE val Context.isDebuggable get() = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE

View File

@ -9,6 +9,7 @@ appcompat = "1.7.0"
preferences-datastore = "1.1.1" preferences-datastore = "1.1.1"
work-runtime = "2.10.0" work-runtime = "2.10.0"
compose-bom = "2024.12.01" compose-bom = "2024.12.01"
navigation = "2.8.4"
accompanist = "0.34.0" accompanist = "0.34.0"
placeholder = "1.1.2" placeholder = "1.1.2"
reorderable = "1.5.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-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "ui-tooling" } compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "ui-tooling" }
compose-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata" } 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" } 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
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "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
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin-version" } 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 = { 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" } koin-workmanager = { group = "io.insert-koin", name = "koin-androidx-workmanager", version.ref = "koin-version" }
# Compose Navigation # Compose Navigation