mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-05-20 21:47:06 +02:00
feat: switch to androidx.navigation (#2362)
This commit is contained in:
parent
f9831d4da5
commit
5d3a81f4b9
@ -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,11 +174,9 @@ 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
|
|
||||||
implementation(libs.reimagined.navigation)
|
|
||||||
|
|
||||||
// Licenses
|
// Licenses
|
||||||
implementation(libs.about.libraries)
|
implementation(libs.about.libraries)
|
||||||
|
|
||||||
|
@ -1,36 +1,38 @@
|
|||||||
package app.revanced.manager
|
package app.revanced.manager
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
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.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.viewmodel.ext.android.getViewModel as getActivityViewModel
|
||||||
import org.koin.androidx.viewmodel.ext.android.getViewModel as getAndroidViewModel
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@ -41,7 +43,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
|
|
||||||
val vm: MainViewModel = getAndroidViewModel()
|
val vm: MainViewModel = getActivityViewModel()
|
||||||
vm.importLegacySettings(this)
|
vm.importLegacySettings(this)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
@ -52,79 +54,203 @@ class MainActivity : ComponentActivity() {
|
|||||||
darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK,
|
darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK,
|
||||||
dynamicColor = dynamicColor
|
dynamicColor = dynamicColor
|
||||||
) {
|
) {
|
||||||
val navController =
|
ReVancedManager(vm)
|
||||||
rememberNavController<Destination>(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) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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<Dashboard> {
|
||||||
|
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<InstalledApplicationInfo> {
|
||||||
|
val data = it.toRoute<InstalledApplicationInfo>()
|
||||||
|
|
||||||
|
InstalledAppInfoScreen(
|
||||||
|
onPatchClick = vm::selectApp,
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
viewModel = koinViewModel { 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.getComplexArg<Patcher.ViewModelParams>()) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Update> {
|
||||||
|
val data = it.toRoute<Update>()
|
||||||
|
|
||||||
|
UpdateScreen(
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
vm = koinViewModel { parametersOf(data.downloadOnScreenEntry) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
navigation<SelectedApplicationInfo>(startDestination = SelectedApplicationInfo.Main) {
|
||||||
|
composable<SelectedApplicationInfo.Main> {
|
||||||
|
val parentBackStackEntry = navController.navGraphEntry(it)
|
||||||
|
val data =
|
||||||
|
parentBackStackEntry.getComplexArg<SelectedApplicationInfo.ViewModelParams>()
|
||||||
|
|
||||||
|
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<SelectedAppInfoViewModel>(viewModelStoreOwner = parentBackStackEntry) {
|
||||||
|
parametersOf(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<SelectedApplicationInfo.PatchesSelector> {
|
||||||
|
val data =
|
||||||
|
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
|
||||||
|
val selectedAppInfoVm = koinNavViewModel<SelectedAppInfoViewModel>(
|
||||||
|
viewModelStoreOwner = navController.navGraphEntry(it)
|
||||||
|
)
|
||||||
|
|
||||||
|
PatchesSelectorScreen(
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
onSave = { patches, options ->
|
||||||
|
selectedAppInfoVm.updateConfiguration(patches, options)
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
vm = koinViewModel { parametersOf(data) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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!!.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 <T : Parcelable, R : ComplexParameter<T>> NavController.navigateComplex(
|
||||||
|
route: R,
|
||||||
|
data: T
|
||||||
|
) {
|
||||||
|
navigate(route)
|
||||||
|
getBackStackEntry(route).savedStateHandle["args"] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T : Parcelable> NavBackStackEntry.getComplexArg() = savedStateHandle.get<T>("args")!!
|
@ -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
|
)
|
@ -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)
|
||||||
|
@ -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
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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<T : Parcelable>
|
||||||
|
|
||||||
|
@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<SelectedApplicationInfo.ViewModelParams> {
|
||||||
|
@Parcelize
|
||||||
|
data class ViewModelParams(
|
||||||
|
val app: SelectedApp,
|
||||||
|
val patches: PatchSelection? = null
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
object Main
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object PatchesSelector : ComplexParameter<PatchesSelector.ViewModelParams> {
|
||||||
|
@Parcelize
|
||||||
|
data class ViewModelParams(
|
||||||
|
val app: SelectedApp,
|
||||||
|
val currentSelection: PatchSelection?,
|
||||||
|
val options: @RawValue Options,
|
||||||
|
) : Parcelable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object Patcher : ComplexParameter<Patcher.ViewModelParams> {
|
||||||
|
@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
|
||||||
|
}
|
@ -25,7 +25,6 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import app.revanced.manager.R
|
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.domain.bundles.PatchBundleSource.Extensions.isDefault
|
||||||
import app.revanced.manager.patcher.aapt.Aapt
|
import app.revanced.manager.patcher.aapt.Aapt
|
||||||
import app.revanced.manager.ui.component.AlertDialogExtended
|
import app.revanced.manager.ui.component.AlertDialogExtended
|
||||||
@ -60,7 +59,7 @@ fun DashboardScreen(
|
|||||||
onSettingsClick: () -> Unit,
|
onSettingsClick: () -> Unit,
|
||||||
onUpdateClick: () -> Unit,
|
onUpdateClick: () -> Unit,
|
||||||
onDownloaderPluginClick: () -> Unit,
|
onDownloaderPluginClick: () -> Unit,
|
||||||
onAppClick: (InstalledApp) -> Unit
|
onAppClick: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val bundlesSelectable by remember { derivedStateOf { vm.selectedSources.size > 0 } }
|
val bundlesSelectable by remember { derivedStateOf { vm.selectedSources.size > 0 } }
|
||||||
val availablePatches by vm.availablePatches.collectAsStateWithLifecycle(0)
|
val availablePatches by vm.availablePatches.collectAsStateWithLifecycle(0)
|
||||||
@ -289,7 +288,7 @@ fun DashboardScreen(
|
|||||||
when (DashboardPage.entries[index]) {
|
when (DashboardPage.entries[index]) {
|
||||||
DashboardPage.DASHBOARD -> {
|
DashboardPage.DASHBOARD -> {
|
||||||
InstalledAppsScreen(
|
InstalledAppsScreen(
|
||||||
onAppClick = onAppClick
|
onAppClick = { onAppClick(it.currentPackageName) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,8 @@ 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.LoadingIndicator
|
import app.revanced.manager.ui.component.LoadingIndicator
|
||||||
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
|
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.BundleInfo.Extensions.bundleInfoFlow
|
||||||
import app.revanced.manager.ui.model.SelectedApp
|
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.ui.viewmodel.SelectedAppInfoViewModel
|
||||||
import app.revanced.manager.util.EventEffect
|
import app.revanced.manager.util.EventEffect
|
||||||
import app.revanced.manager.util.Options
|
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.enabled
|
||||||
import app.revanced.manager.util.toast
|
import app.revanced.manager.util.toast
|
||||||
import app.revanced.manager.util.transparentListItemColors
|
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)
|
@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,147 +78,119 @@ 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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
) { 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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,146 +13,64 @@ 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
|
private val settingsSections = listOf(
|
||||||
import app.revanced.manager.ui.screen.settings.update.UpdateScreen
|
Triple(
|
||||||
import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen
|
R.string.general,
|
||||||
import app.revanced.manager.ui.viewmodel.SettingsViewModel
|
R.string.general_description,
|
||||||
import dev.olshevski.navigation.reimagined.*
|
Icons.Outlined.Settings
|
||||||
import org.koin.androidx.compose.koinViewModel
|
) to Settings.General,
|
||||||
import org.koin.core.parameter.parametersOf
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(onBackClick: () -> Unit, navigate: (Settings.Destination) -> Unit) {
|
||||||
onBackClick: () -> Unit,
|
Scaffold(
|
||||||
startDestination: SettingsDestination,
|
topBar = {
|
||||||
viewModel: SettingsViewModel = koinViewModel()
|
AppTopBar(
|
||||||
) {
|
title = stringResource(R.string.settings),
|
||||||
val navController = rememberNavController(startDestination)
|
onBackClick = onBackClick,
|
||||||
|
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
is SettingsDestination.Advanced -> AdvancedSettingsScreen(
|
) { paddingValues ->
|
||||||
onBackClick = backClick
|
ColumnWithScrollbar(
|
||||||
)
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
is SettingsDestination.Updates -> UpdatesSettingsScreen(
|
.fillMaxSize()
|
||||||
onBackClick = backClick,
|
) {
|
||||||
onChangelogClick = { navController.navigate(SettingsDestination.Changelogs) },
|
settingsSections.forEach { (titleDescIcon, destination) ->
|
||||||
onUpdateClick = { navController.navigate(SettingsDestination.Update(false)) }
|
SettingsListItem(
|
||||||
)
|
modifier = Modifier.clickable { navigate(destination) },
|
||||||
|
headlineContent = stringResource(titleDescIcon.first),
|
||||||
is SettingsDestination.Downloads -> DownloadsSettingsScreen(
|
supportingContent = stringResource(titleDescIcon.second),
|
||||||
onBackClick = backClick
|
leadingContent = { Icon(titleDescIcon.third, null) }
|
||||||
)
|
)
|
||||||
|
|
||||||
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) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
@ -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) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -39,7 +39,6 @@ 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.service.InstallService
|
import app.revanced.manager.service.InstallService
|
||||||
import app.revanced.manager.service.UninstallService
|
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.InstallerModel
|
||||||
import app.revanced.manager.ui.model.ProgressKey
|
import app.revanced.manager.ui.model.ProgressKey
|
||||||
import app.revanced.manager.ui.model.SelectedApp
|
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.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
|
||||||
@ -72,7 +72,7 @@ import java.time.Duration
|
|||||||
|
|
||||||
@OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
|
@OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
|
||||||
class PatcherViewModel(
|
class PatcherViewModel(
|
||||||
private val input: Destination.Patcher
|
private val input: Patcher.ViewModelParams
|
||||||
) : 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()
|
||||||
|
@ -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.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.ui.model.navigation.SelectedApplicationInfo
|
||||||
import app.revanced.manager.util.Options
|
import app.revanced.manager.util.Options
|
||||||
import app.revanced.manager.util.PatchSelection
|
import app.revanced.manager.util.PatchSelection
|
||||||
import app.revanced.manager.util.saver.Nullable
|
import app.revanced.manager.util.saver.Nullable
|
||||||
@ -40,7 +41,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@OptIn(SavedStateHandleSaveableApi::class)
|
@OptIn(SavedStateHandleSaveableApi::class)
|
||||||
class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
class PatchesSelectorViewModel(input: SelectedApplicationInfo.PatchesSelector.ViewModelParams) : ViewModel(), KoinComponent {
|
||||||
private val app: Application = get()
|
private val app: Application = get()
|
||||||
private val savedStateHandle: SavedStateHandle = get()
|
private val savedStateHandle: SavedStateHandle = get()
|
||||||
private val prefs: PreferencesManager = get()
|
private val prefs: PreferencesManager = get()
|
||||||
@ -214,12 +215,6 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||||||
private val selectionSaver: Saver<PersistentPatchSelection?, Nullable<PatchSelection>> =
|
private val selectionSaver: Saver<PersistentPatchSelection?, Nullable<PatchSelection>> =
|
||||||
nullableSaver(persistentMapSaver(valueSaver = persistentSetSaver()))
|
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.
|
// Versions of other types, but utilizing persistent/observable collection types.
|
||||||
|
@ -33,8 +33,10 @@ 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.ui.model.navigation.SelectedApplicationInfo
|
||||||
import app.revanced.manager.util.Options
|
import app.revanced.manager.util.Options
|
||||||
import app.revanced.manager.util.PM
|
import app.revanced.manager.util.PM
|
||||||
import app.revanced.manager.util.PatchSelection
|
import app.revanced.manager.util.PatchSelection
|
||||||
@ -57,7 +59,9 @@ import org.koin.core.component.KoinComponent
|
|||||||
import org.koin.core.component.get
|
import org.koin.core.component.get
|
||||||
|
|
||||||
@OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
|
@OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
|
||||||
class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
|
class SelectedAppInfoViewModel(
|
||||||
|
input: SelectedApplicationInfo.ViewModelParams
|
||||||
|
) : ViewModel(), KoinComponent {
|
||||||
private val app: Application = get()
|
private val app: Application = get()
|
||||||
val bundlesRepo: PatchBundleRepository = get()
|
val bundlesRepo: PatchBundleRepository = get()
|
||||||
private val bundleRepository: 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
|
if (!suggestedVersionSafeguard) return@combine null
|
||||||
|
|
||||||
suggestedVersions[input.app.packageName]
|
suggestedVersions[input.app.packageName]
|
||||||
@ -264,17 +271,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)
|
||||||
@ -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) {
|
enum class Error(@StringRes val resourceId: Int) {
|
||||||
NoPlugins(R.string.downloader_no_plugins_available)
|
NoPlugins(R.string.downloader_no_plugins_available)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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.5"
|
||||||
accompanist = "0.34.0"
|
accompanist = "0.34.0"
|
||||||
placeholder = "1.1.2"
|
placeholder = "1.1.2"
|
||||||
reorderable = "1.5.2"
|
reorderable = "1.5.2"
|
||||||
@ -18,9 +19,7 @@ datetime = "0.6.0"
|
|||||||
room-version = "2.6.1"
|
room-version = "2.6.1"
|
||||||
revanced-patcher = "21.0.0"
|
revanced-patcher = "21.0.0"
|
||||||
revanced-library = "3.0.2"
|
revanced-library = "3.0.2"
|
||||||
koin-version = "3.5.3"
|
koin = "3.5.3"
|
||||||
koin-version-compose = "3.5.3"
|
|
||||||
reimagined-navigation = "1.5.0"
|
|
||||||
ktor = "2.3.9"
|
ktor = "2.3.9"
|
||||||
markdown-renderer = "0.22.0"
|
markdown-renderer = "0.22.0"
|
||||||
fading-edges = "1.0.4"
|
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-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" }
|
||||||
@ -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" }
|
revanced-library = { group = "app.revanced", name = "revanced-library", version.ref = "revanced-library" }
|
||||||
|
|
||||||
# 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" }
|
||||||
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" }
|
||||||
koin-workmanager = { group = "io.insert-koin", name = "koin-androidx-workmanager", version.ref = "koin-version" }
|
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" }
|
||||||
# Compose Navigation
|
|
||||||
reimagined-navigation = { group = "dev.olshevski.navigation", name = "reimagined", version.ref = "reimagined-navigation" }
|
|
||||||
|
|
||||||
# About Libraries
|
# About Libraries
|
||||||
about-libraries = { group = "com.mikepenz", name = "aboutlibraries-compose", version.ref = "about-libraries-gradle-plugin" }
|
about-libraries = { group = "com.mikepenz", name = "aboutlibraries-compose", version.ref = "about-libraries-gradle-plugin" }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user