Type safe navigation
This commit is contained in:
parent
21c43250f1
commit
b76ea503dd
|
|
@ -49,6 +49,12 @@ afterEvaluate {
|
|||
val variantLowered = variant.name.toLowerCase()
|
||||
val variantCapped = variant.name.capitalize()
|
||||
|
||||
kotlin.sourceSets {
|
||||
getByName(variant.name) {
|
||||
kotlin.srcDir("build/generated/ksp/${variant.name}/kotlin")
|
||||
}
|
||||
}
|
||||
|
||||
task<Copy>("copy${variantCapped}Assets") {
|
||||
dependsOn(":appstub:copy$variantCapped")
|
||||
dependsOn(":patch-loader:copy$variantCapped")
|
||||
|
|
@ -75,6 +81,7 @@ dependencies {
|
|||
implementation(projects.share.java)
|
||||
|
||||
val roomVersion = "2.4.2"
|
||||
val composeDestinationsVersion = "1.6.13-beta"
|
||||
annotationProcessor("androidx.room:room-compiler:$roomVersion")
|
||||
compileOnly("dev.rikka.hidden:stub:2.3.1")
|
||||
implementation("dev.rikka.hidden:compat:2.3.1")
|
||||
|
|
@ -82,7 +89,6 @@ dependencies {
|
|||
implementation("androidx.activity:activity-compose:1.6.0-alpha05")
|
||||
implementation("androidx.compose.material:material-icons-extended:1.3.0-alpha01")
|
||||
implementation("androidx.compose.material3:material3:1.0.0-alpha14")
|
||||
implementation("androidx.compose.runtime:runtime-livedata:1.3.0-alpha01")
|
||||
implementation("androidx.compose.ui:ui:1.3.0-alpha01")
|
||||
implementation("androidx.compose.ui:ui-tooling:1.3.0-alpha01")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0")
|
||||
|
|
@ -98,6 +104,8 @@ dependencies {
|
|||
implementation("com.google.code.gson:gson:2.9.0")
|
||||
implementation("dev.rikka.shizuku:api:12.1.0")
|
||||
implementation("dev.rikka.shizuku:provider:12.1.0")
|
||||
implementation("io.github.raamcosta.compose-destinations:core:$composeDestinationsVersion")
|
||||
implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
|
||||
ksp("androidx.room:room-compiler:$roomVersion")
|
||||
ksp("io.github.raamcosta.compose-destinations:ksp:$composeDestinationsVersion")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,16 +9,19 @@ import androidx.compose.material3.*
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import org.lsposed.lspatch.ui.page.PageList
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import org.lsposed.lspatch.ui.page.BottomBarDestination
|
||||
import org.lsposed.lspatch.ui.page.NavGraphs
|
||||
import org.lsposed.lspatch.ui.page.appCurrentDestinationAsState
|
||||
import org.lsposed.lspatch.ui.page.destinations.Destination
|
||||
import org.lsposed.lspatch.ui.page.startAppDestination
|
||||
import org.lsposed.lspatch.ui.theme.LSPTheme
|
||||
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
|
||||
import org.lsposed.lspatch.ui.util.navigateWithState
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
|
|
@ -27,25 +30,18 @@ class MainActivity : ComponentActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
val navController = rememberAnimatedNavController()
|
||||
var mainPage by rememberSaveable { mutableStateOf(PageList.Home) }
|
||||
|
||||
LSPTheme {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
CompositionLocalProvider(
|
||||
LocalNavController provides navController,
|
||||
LocalSnackbarHost provides snackbarHostState
|
||||
) {
|
||||
CompositionLocalProvider(LocalSnackbarHost provides snackbarHostState) {
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
MainNavigationBar(mainPage) {
|
||||
if (mainPage == it) return@MainNavigationBar
|
||||
mainPage = it
|
||||
navController.navigateWithState(it.name)
|
||||
}
|
||||
},
|
||||
bottomBar = { BottomBar(navController) },
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||
) { innerPadding ->
|
||||
MainNavHost(navController, Modifier.padding(innerPadding))
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
navGraph = NavGraphs.root,
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,30 +50,34 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun MainNavHost(navController: NavHostController, modifier: Modifier) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = PageList.Home.name,
|
||||
modifier = modifier
|
||||
) {
|
||||
for (page in PageList.values()) {
|
||||
composable(route = page.route, arguments = page.arguments, content = page.body)
|
||||
}
|
||||
private fun BottomBar(navController: NavHostController) {
|
||||
val currentDestination: Destination = navController.appCurrentDestinationAsState().value
|
||||
?: NavGraphs.root.startAppDestination
|
||||
var topDestination by rememberSaveable { mutableStateOf(currentDestination.route) }
|
||||
LaunchedEffect(currentDestination) {
|
||||
val queue = navController.backQueue
|
||||
if (queue.size == 2) topDestination = queue[1].destination.route!!
|
||||
else if (queue.size > 2) topDestination = queue[2].destination.route!!
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainNavigationBar(page: PageList, onClick: (PageList) -> Unit) {
|
||||
NavigationBar(tonalElevation = 8.dp) {
|
||||
arrayOf(PageList.Repo, PageList.Manage, PageList.Home, PageList.Logs, PageList.Settings).forEach {
|
||||
BottomBarDestination.values().forEach { destination ->
|
||||
NavigationBarItem(
|
||||
selected = page == it,
|
||||
onClick = { onClick(it) },
|
||||
icon = {
|
||||
if (page == it) Icon(it.iconSelected!!, it.title)
|
||||
else Icon(it.iconNotSelected!!, it.title)
|
||||
selected = topDestination == destination.direction.route,
|
||||
onClick = {
|
||||
navController.navigate(destination.direction.route) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
label = { Text(it.title) },
|
||||
icon = {
|
||||
if (topDestination == destination.direction.route) Icon(destination.iconSelected, stringResource(destination.label))
|
||||
else Icon(destination.iconNotSelected, stringResource(destination.label))
|
||||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
alwaysShowLabel = false
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import androidx.compose.ui.text.input.ImeAction
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
|
||||
private const val TAG = "SearchBar"
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package org.lsposed.lspatch.ui.page
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.ui.page.destinations.*
|
||||
|
||||
enum class BottomBarDestination(
|
||||
val direction: DirectionDestinationSpec,
|
||||
@StringRes val label: Int,
|
||||
val iconSelected: ImageVector,
|
||||
val iconNotSelected: ImageVector
|
||||
) {
|
||||
Repo(RepoScreenDestination, R.string.screen_repo, Icons.Filled.GetApp, Icons.Outlined.GetApp),
|
||||
Manage(ManageScreenDestination, R.string.screen_manage, Icons.Filled.Dashboard, Icons.Outlined.Dashboard),
|
||||
Home(HomeScreenDestination, R.string.app_name, Icons.Filled.Home, Icons.Outlined.Home),
|
||||
Logs(LogsScreenDestination, R.string.screen_logs, Icons.Filled.Assignment, Icons.Outlined.Assignment),
|
||||
Settings(SettingsScreenDestination, R.string.screen_settings, Icons.Filled.Settings, Icons.Outlined.Settings);
|
||||
}
|
||||
|
|
@ -25,6 +25,8 @@ import androidx.compose.ui.text.font.FontFamily
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import kotlinx.coroutines.launch
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.share.LSPConfig
|
||||
|
|
@ -34,8 +36,10 @@ import org.lsposed.lspatch.util.ShizukuApi
|
|||
import rikka.shizuku.Shizuku
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RootNavGraph(start = true)
|
||||
@Destination
|
||||
@Composable
|
||||
fun HomePage() {
|
||||
fun HomeScreen() {
|
||||
Scaffold(topBar = { TopBar() }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -8,11 +8,14 @@ import androidx.compose.material3.SmallTopAppBar
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun RepoPage() {
|
||||
fun LogsScreen() {
|
||||
Scaffold(topBar = { TopBar() }) { innerPadding ->
|
||||
Text(
|
||||
modifier = Modifier
|
||||
|
|
@ -27,6 +30,6 @@ fun RepoPage() {
|
|||
@Composable
|
||||
private fun TopBar() {
|
||||
SmallTopAppBar(
|
||||
title = { Text(PageList.Repo.title) }
|
||||
title = { Text(stringResource(BottomBarDestination.Logs.label)) }
|
||||
)
|
||||
}
|
||||
|
|
@ -13,20 +13,28 @@ import androidx.compose.ui.unit.dp
|
|||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.result.ResultRecipient
|
||||
import kotlinx.coroutines.launch
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.ui.page.destinations.SelectAppsScreenDestination
|
||||
import org.lsposed.lspatch.ui.page.manage.AppManageBody
|
||||
import org.lsposed.lspatch.ui.page.manage.AppManageFab
|
||||
import org.lsposed.lspatch.ui.page.manage.ModuleManageBody
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun ManagePage() {
|
||||
fun ManageScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
resultRecipient: ResultRecipient<SelectAppsScreenDestination, SelectAppsResult>
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val pagerState = rememberPagerState()
|
||||
Scaffold(
|
||||
topBar = { TopBar() },
|
||||
floatingActionButton = { if (pagerState.currentPage == 0) AppManageFab() }
|
||||
floatingActionButton = { if (pagerState.currentPage == 0) AppManageFab(navigator) }
|
||||
) { innerPadding ->
|
||||
Box(Modifier.padding(innerPadding)) {
|
||||
Column {
|
||||
|
|
@ -58,7 +66,7 @@ fun ManagePage() {
|
|||
|
||||
HorizontalPager(count = 2, state = pagerState) { page ->
|
||||
when (page) {
|
||||
0 -> AppManageBody()
|
||||
0 -> AppManageBody(navigator, resultRecipient)
|
||||
1 -> ModuleManageBody()
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +78,6 @@ fun ManagePage() {
|
|||
@Composable
|
||||
private fun TopBar() {
|
||||
SmallTopAppBar(
|
||||
title = { Text(PageList.Manage.title) }
|
||||
title = { Text(stringResource(BottomBarDestination.Manage.label)) }
|
||||
)
|
||||
}
|
||||
|
|
@ -23,17 +23,18 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.result.NavResult
|
||||
import com.ramcosta.composedestinations.result.ResultRecipient
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.lsposed.lspatch.R
|
||||
|
|
@ -42,83 +43,100 @@ import org.lsposed.lspatch.ui.component.SelectionColumn
|
|||
import org.lsposed.lspatch.ui.component.ShimmerAnimation
|
||||
import org.lsposed.lspatch.ui.component.settings.SettingsCheckBox
|
||||
import org.lsposed.lspatch.ui.component.settings.SettingsItem
|
||||
import org.lsposed.lspatch.ui.util.*
|
||||
import org.lsposed.lspatch.ui.page.destinations.SelectAppsScreenDestination
|
||||
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
|
||||
import org.lsposed.lspatch.ui.util.isScrolledToEnd
|
||||
import org.lsposed.lspatch.ui.util.lastItemIndex
|
||||
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel
|
||||
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.PatchState
|
||||
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.ViewAction
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||
import org.lsposed.lspatch.util.ShizukuApi
|
||||
import java.io.File
|
||||
|
||||
private const val TAG = "NewPatchPage"
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
||||
fun NewPatchScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
resultRecipient: ResultRecipient<SelectAppsScreenDestination, SelectAppsResult>,
|
||||
from: String
|
||||
) {
|
||||
val viewModel = viewModel<NewPatchViewModel>()
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val navController = LocalNavController.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val isCancelled by entry.observeState<Boolean>("isCancelled")
|
||||
val storageLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { apks ->
|
||||
if (apks.isEmpty()) {
|
||||
navigator.navigateUp()
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
runBlocking {
|
||||
LSPPackageManager.getAppInfoFromApks(apks)
|
||||
.onSuccess {
|
||||
viewModel.dispatch(ViewAction.ConfigurePatch(it))
|
||||
}
|
||||
.onFailure {
|
||||
lspApp.globalScope.launch { snackbarHost.showSnackbar(it.message ?: "Unknown error") }
|
||||
navigator.navigateUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "PatchState: ${viewModel.patchState}")
|
||||
if (viewModel.patchState == PatchState.SELECTING) {
|
||||
val storageLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { apks ->
|
||||
if (apks.isEmpty()) {
|
||||
navController.popBackStack()
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
runBlocking {
|
||||
LSPPackageManager.getAppInfoFromApks(apks)
|
||||
.onSuccess {
|
||||
viewModel.dispatch(ViewAction.ConfigurePatch(it))
|
||||
}
|
||||
.onFailure {
|
||||
lspApp.globalScope.launch { snackbarHost.showSnackbar(it.message ?: "Unknown error") }
|
||||
navController.popBackStack()
|
||||
}
|
||||
when (viewModel.patchState) {
|
||||
PatchState.INIT -> {
|
||||
LaunchedEffect(Unit) {
|
||||
LSPPackageManager.cleanTmpApkDir()
|
||||
when (from) {
|
||||
"storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive"))
|
||||
"applist" -> navigator.navigate(SelectAppsScreenDestination(false))
|
||||
}
|
||||
viewModel.dispatch(ViewAction.DoneInit)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
LSPPackageManager.cleanTmpApkDir()
|
||||
if (isCancelled == true) navController.popBackStack()
|
||||
else when (from) {
|
||||
"storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive"))
|
||||
"applist" -> {
|
||||
entry.savedStateHandle.getLiveData<AppInfo>("appInfo").observe(lifecycleOwner) {
|
||||
viewModel.dispatch(ViewAction.ConfigurePatch(it))
|
||||
PatchState.SELECTING -> {
|
||||
resultRecipient.onNavResult {
|
||||
Log.d(TAG, "onNavResult: $it")
|
||||
when (it) {
|
||||
is NavResult.Canceled -> navigator.navigateUp()
|
||||
is NavResult.Value -> {
|
||||
val result = it.value as SelectAppsResult.SingleApp
|
||||
viewModel.dispatch(ViewAction.ConfigurePatch(result.selected))
|
||||
}
|
||||
navController.navigate(PageList.SelectApps.name + "?multiSelect=false")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
when (viewModel.patchState) {
|
||||
PatchState.CONFIGURING -> ConfiguringTopBar { navController.popBackStack() }
|
||||
PatchState.PATCHING,
|
||||
PatchState.FINISHED,
|
||||
PatchState.ERROR -> CenterAlignedTopAppBar(title = { Text(viewModel.patchApp.app.packageName) })
|
||||
else -> Unit
|
||||
else -> {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
when (viewModel.patchState) {
|
||||
PatchState.CONFIGURING -> ConfiguringTopBar { navigator.navigateUp() }
|
||||
PatchState.PATCHING,
|
||||
PatchState.FINISHED,
|
||||
PatchState.ERROR -> CenterAlignedTopAppBar(title = { Text(viewModel.patchApp.app.packageName) })
|
||||
else -> Unit
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (viewModel.patchState == PatchState.CONFIGURING) {
|
||||
ConfiguringFab()
|
||||
}
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
) { innerPadding ->
|
||||
if (viewModel.patchState == PatchState.CONFIGURING) {
|
||||
ConfiguringFab()
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
if (viewModel.patchState == PatchState.CONFIGURING) {
|
||||
LaunchedEffect(Unit) {
|
||||
entry.savedStateHandle.getLiveData("selected", SnapshotStateList<AppInfo>()).observe(lifecycleOwner) {
|
||||
viewModel.embeddedModules = it
|
||||
PatchOptionsBody(Modifier.padding(innerPadding)) {
|
||||
navigator.navigate(SelectAppsScreenDestination(true, viewModel.embeddedModules.mapTo(ArrayList()) { it.app.packageName }))
|
||||
}
|
||||
resultRecipient.onNavResult {
|
||||
if (it is NavResult.Value) {
|
||||
val result = it.value as SelectAppsResult.MultipleApps
|
||||
viewModel.embeddedModules = result.selected
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DoPatchBody(Modifier.padding(innerPadding), navigator)
|
||||
}
|
||||
PatchOptionsBody(Modifier.padding(innerPadding))
|
||||
} else {
|
||||
DoPatchBody(Modifier.padding(innerPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -127,7 +145,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
|||
@Composable
|
||||
private fun ConfiguringTopBar(onBackClick: () -> Unit) {
|
||||
SmallTopAppBar(
|
||||
title = { Text(stringResource(R.string.page_new_patch)) },
|
||||
title = { Text(stringResource(R.string.screen_new_patch)) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBackClick,
|
||||
|
|
@ -157,9 +175,8 @@ private fun sigBypassLvStr(level: Int) = when (level) {
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun PatchOptionsBody(modifier: Modifier) {
|
||||
private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
|
||||
val viewModel = viewModel<NewPatchViewModel>()
|
||||
val navController = LocalNavController.current
|
||||
|
||||
Column(modifier.verticalScroll(rememberScrollState())) {
|
||||
Text(
|
||||
|
|
@ -195,7 +212,7 @@ private fun PatchOptionsBody(modifier: Modifier) {
|
|||
desc = stringResource(R.string.patch_portable_desc),
|
||||
extraContent = {
|
||||
TextButton(
|
||||
onClick = { navController.navigate(PageList.SelectApps.name + "?multiSelect=true") },
|
||||
onClick = onAddEmbed,
|
||||
content = { Text(text = stringResource(R.string.patch_embed_modules), style = MaterialTheme.typography.bodyLarge) }
|
||||
)
|
||||
}
|
||||
|
|
@ -266,10 +283,9 @@ private fun PatchOptionsBody(modifier: Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun DoPatchBody(modifier: Modifier) {
|
||||
private fun DoPatchBody(modifier: Modifier, navigator: DestinationsNavigator) {
|
||||
val viewModel = viewModel<NewPatchViewModel>()
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val navController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
@ -331,7 +347,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
installing = false
|
||||
if (status == PackageInstaller.STATUS_SUCCESS) {
|
||||
lspApp.globalScope.launch { snackbarHost.showSnackbar(installSuccessfully) }
|
||||
navController.popBackStack()
|
||||
navigator.navigateUp()
|
||||
} else if (status != LSPPackageManager.STATUS_USER_CANCELLED) {
|
||||
val result = snackbarHost.showSnackbar(installFailed, copyError)
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
|
|
@ -344,7 +360,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
Row(Modifier.padding(top = 12.dp)) {
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = { navController.popBackStack() },
|
||||
onClick = { navigator.navigateUp() },
|
||||
content = { Text(stringResource(R.string.patch_return)) }
|
||||
)
|
||||
Spacer(Modifier.weight(0.2f))
|
||||
|
|
@ -367,7 +383,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
Row(Modifier.padding(top = 12.dp)) {
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = { navController.popBackStack() },
|
||||
onClick = { navigator.navigateUp() },
|
||||
content = { Text(stringResource(R.string.patch_return)) }
|
||||
)
|
||||
Spacer(Modifier.weight(0.2f))
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
package org.lsposed.lspatch.ui.page
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.NamedNavArgument
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import org.lsposed.lspatch.R
|
||||
|
||||
enum class PageList(
|
||||
val iconSelected: ImageVector? = null,
|
||||
val iconNotSelected: ImageVector? = null,
|
||||
val arguments: List<NamedNavArgument> = emptyList(),
|
||||
val body: @Composable NavBackStackEntry.() -> Unit
|
||||
) {
|
||||
Repo(
|
||||
iconSelected = Icons.Filled.GetApp,
|
||||
iconNotSelected = Icons.Outlined.GetApp,
|
||||
body = { RepoPage() }
|
||||
),
|
||||
Manage(
|
||||
iconSelected = Icons.Filled.Dashboard,
|
||||
iconNotSelected = Icons.Outlined.Dashboard,
|
||||
body = { ManagePage() }
|
||||
),
|
||||
Home(
|
||||
iconSelected = Icons.Filled.Home,
|
||||
iconNotSelected = Icons.Outlined.Home,
|
||||
body = { HomePage() }
|
||||
),
|
||||
Logs(
|
||||
iconSelected = Icons.Filled.Assignment,
|
||||
iconNotSelected = Icons.Outlined.Assignment,
|
||||
body = { LogsPage() }
|
||||
),
|
||||
Settings(
|
||||
iconSelected = Icons.Filled.Settings,
|
||||
iconNotSelected = Icons.Outlined.Settings,
|
||||
body = { SettingsPage() }
|
||||
),
|
||||
NewPatch(
|
||||
arguments = listOf(
|
||||
navArgument("from") { type = NavType.StringType }
|
||||
),
|
||||
body = { NewPatchPage(arguments!!.getString("from")!!, this) }
|
||||
),
|
||||
SelectApps(
|
||||
arguments = listOf(
|
||||
navArgument("multiSelect") { type = NavType.BoolType }
|
||||
),
|
||||
body = { SelectAppsPage(arguments!!.getBoolean("multiSelect")) }
|
||||
);
|
||||
|
||||
val title: String
|
||||
@Composable get() = when (this) {
|
||||
Repo -> stringResource(R.string.page_repo)
|
||||
Manage -> stringResource(R.string.page_manage)
|
||||
Home -> stringResource(R.string.app_name)
|
||||
Logs -> stringResource(R.string.page_logs)
|
||||
Settings -> stringResource(R.string.page_settings)
|
||||
NewPatch -> stringResource(R.string.page_new_patch)
|
||||
SelectApps -> stringResource(R.string.page_select_apps)
|
||||
}
|
||||
|
||||
val route = buildString {
|
||||
append(name)
|
||||
if (arguments.isNotEmpty()) {
|
||||
append(arguments.joinToString(",", "?") { "${it.name}={${it.name}}" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,14 @@ import androidx.compose.material3.SmallTopAppBar
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun LogsPage() {
|
||||
fun RepoScreen() {
|
||||
Scaffold(topBar = { TopBar() }) { innerPadding ->
|
||||
Text(
|
||||
modifier = Modifier
|
||||
|
|
@ -27,6 +30,6 @@ fun LogsPage() {
|
|||
@Composable
|
||||
private fun TopBar() {
|
||||
SmallTopAppBar(
|
||||
title = { Text(PageList.Logs.title) }
|
||||
title = { Text(stringResource(BottomBarDestination.Repo.label)) }
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package org.lsposed.lspatch.ui.page
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Parcelable
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
|
|
@ -13,30 +14,38 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.outlined.Done
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.toLowerCase
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.ui.component.AppItem
|
||||
import org.lsposed.lspatch.ui.component.SearchAppBar
|
||||
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||
import org.lsposed.lspatch.ui.util.observeState
|
||||
import org.lsposed.lspatch.ui.util.setState
|
||||
import org.lsposed.lspatch.ui.viewmodel.SelectAppsViewModel
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||
|
||||
@Parcelize
|
||||
sealed class SelectAppsResult : Parcelable {
|
||||
data class SingleApp(val selected: AppInfo) : SelectAppsResult()
|
||||
data class MultipleApps(val selected: List<AppInfo>) : SelectAppsResult()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun SelectAppsPage(multiSelect: Boolean) {
|
||||
fun SelectAppsScreen(
|
||||
navigator: ResultBackNavigator<SelectAppsResult>,
|
||||
multiSelect: Boolean,
|
||||
initialSelected: ArrayList<String>? = null
|
||||
) {
|
||||
val viewModel = viewModel<SelectAppsViewModel>()
|
||||
val navController = LocalNavController.current
|
||||
|
||||
var searchPackage by remember { mutableStateOf("") }
|
||||
val filter: (AppInfo) -> Boolean = {
|
||||
|
|
@ -48,17 +57,20 @@ fun SelectAppsPage(multiSelect: Boolean) {
|
|||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.filterAppList(false, filter)
|
||||
initialSelected?.let {
|
||||
val tmp = initialSelected.toSet()
|
||||
viewModel.multiSelected.addAll(LSPPackageManager.appList.filter { tmp.contains(it.app.packageName) })
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
navController.previousBackStackEntry!!.setState("isCancelled", true)
|
||||
navController.popBackStack()
|
||||
navigator.navigateBack()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SearchAppBar(
|
||||
title = { Text(stringResource(R.string.page_select_apps)) },
|
||||
title = { Text(stringResource(R.string.screen_select_apps)) },
|
||||
searchText = searchPackage,
|
||||
onSearchTextChange = {
|
||||
searchPackage = it
|
||||
|
|
@ -69,13 +81,14 @@ fun SelectAppsPage(multiSelect: Boolean) {
|
|||
viewModel.filterAppList(false, filter)
|
||||
},
|
||||
onBackClick = {
|
||||
navController.previousBackStackEntry!!.setState("isCancelled", true)
|
||||
navController.popBackStack()
|
||||
navigator.navigateBack()
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (multiSelect) MultiSelectFab()
|
||||
if (multiSelect) MultiSelectFab {
|
||||
navigator.navigateBack(SelectAppsResult.MultipleApps(viewModel.multiSelected))
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
SwipeRefresh(
|
||||
|
|
@ -86,29 +99,25 @@ fun SelectAppsPage(multiSelect: Boolean) {
|
|||
.fillMaxSize()
|
||||
) {
|
||||
if (multiSelect) MultiSelect()
|
||||
else SingleSelect()
|
||||
else SingleSelect {
|
||||
navigator.navigateBack(SelectAppsResult.SingleApp(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MultiSelectFab() {
|
||||
val navController = LocalNavController.current
|
||||
private fun MultiSelectFab(onClick: () -> Unit) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
navController.previousBackStackEntry!!.setState("isCancelled", false)
|
||||
navController.popBackStack()
|
||||
},
|
||||
onClick = onClick,
|
||||
content = { Icon(Icons.Outlined.Done, stringResource(R.string.add)) }
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun SingleSelect() {
|
||||
val navController = LocalNavController.current
|
||||
private fun SingleSelect(onSelect: (AppInfo) -> Unit) {
|
||||
val viewModel = viewModel<SelectAppsViewModel>()
|
||||
|
||||
LazyColumn {
|
||||
items(
|
||||
items = viewModel.filteredList,
|
||||
|
|
@ -119,10 +128,7 @@ private fun SingleSelect() {
|
|||
icon = LSPPackageManager.getIcon(it),
|
||||
label = it.label,
|
||||
packageName = it.app.packageName,
|
||||
onClick = {
|
||||
navController.previousBackStackEntry!!.setState("appInfo", it)
|
||||
navController.popBackStack()
|
||||
}
|
||||
onClick = { onSelect(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -131,24 +137,21 @@ private fun SingleSelect() {
|
|||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun MultiSelect() {
|
||||
val navController = LocalNavController.current
|
||||
val viewModel = viewModel<SelectAppsViewModel>()
|
||||
val selected by navController.previousBackStackEntry!!.observeState<SnapshotStateList<AppInfo>>("selected")
|
||||
|
||||
LazyColumn {
|
||||
items(
|
||||
items = viewModel.filteredList,
|
||||
key = { it.app.packageName }
|
||||
) {
|
||||
val checked = selected!!.contains(it)
|
||||
val checked = viewModel.multiSelected.contains(it)
|
||||
AppItem(
|
||||
modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)),
|
||||
icon = LSPPackageManager.getIcon(it),
|
||||
label = it.label,
|
||||
packageName = it.app.packageName,
|
||||
onClick = {
|
||||
if (checked) selected!!.remove(it)
|
||||
else selected!!.add(it)
|
||||
if (checked) viewModel.multiSelected.remove(it)
|
||||
else viewModel.multiSelected.add(it)
|
||||
},
|
||||
checked = checked
|
||||
)
|
||||
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import kotlinx.coroutines.launch
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.config.MyKeyStore
|
||||
|
|
@ -31,8 +32,9 @@ import java.security.GeneralSecurityException
|
|||
import java.security.KeyStore
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination
|
||||
@Composable
|
||||
fun SettingsPage() {
|
||||
fun SettingsScreen() {
|
||||
Scaffold(
|
||||
topBar = { TopBar() }
|
||||
) { innerPadding ->
|
||||
|
|
@ -49,7 +51,7 @@ fun SettingsPage() {
|
|||
@Composable
|
||||
private fun TopBar() {
|
||||
SmallTopAppBar(
|
||||
title = { Text(stringResource(R.string.page_settings)) }
|
||||
title = { Text(stringResource(R.string.screen_settings)) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -17,7 +17,6 @@ import androidx.compose.material.icons.filled.Add
|
|||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
|
@ -32,6 +31,9 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.result.NavResult
|
||||
import com.ramcosta.composedestinations.result.ResultRecipient
|
||||
import kotlinx.coroutines.launch
|
||||
import org.lsposed.lspatch.Constants
|
||||
import org.lsposed.lspatch.R
|
||||
|
|
@ -41,11 +43,10 @@ import org.lsposed.lspatch.lspApp
|
|||
import org.lsposed.lspatch.share.LSPConfig
|
||||
import org.lsposed.lspatch.ui.component.AppItem
|
||||
import org.lsposed.lspatch.ui.component.LoadingDialog
|
||||
import org.lsposed.lspatch.ui.page.PageList
|
||||
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||
import org.lsposed.lspatch.ui.page.SelectAppsResult
|
||||
import org.lsposed.lspatch.ui.page.destinations.NewPatchScreenDestination
|
||||
import org.lsposed.lspatch.ui.page.destinations.SelectAppsScreenDestination
|
||||
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
|
||||
import org.lsposed.lspatch.ui.util.observeState
|
||||
import org.lsposed.lspatch.ui.util.setState
|
||||
import org.lsposed.lspatch.ui.viewmodel.manage.AppManageViewModel
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.ShizukuApi
|
||||
|
|
@ -54,10 +55,12 @@ import java.io.IOException
|
|||
private const val TAG = "AppManagePage"
|
||||
|
||||
@Composable
|
||||
fun AppManageBody() {
|
||||
fun AppManageBody(
|
||||
navigator: DestinationsNavigator,
|
||||
resultRecipient: ResultRecipient<SelectAppsScreenDestination, SelectAppsResult>
|
||||
) {
|
||||
val viewModel = viewModel<AppManageViewModel>()
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val navController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
if (viewModel.appList.isEmpty()) {
|
||||
|
|
@ -74,20 +77,18 @@ fun AppManageBody() {
|
|||
}
|
||||
} else {
|
||||
var scopeApp by rememberSaveable { mutableStateOf("") }
|
||||
val isCancelled by navController.currentBackStackEntry!!.observeState<Boolean>("isCancelled")
|
||||
LaunchedEffect(isCancelled) {
|
||||
if (isCancelled == false) {
|
||||
val selected = navController.currentBackStackEntry!!
|
||||
.savedStateHandle.getLiveData<SnapshotStateList<LSPPackageManager.AppInfo>>("selected").value!!.toSet()
|
||||
Log.d(TAG, "Clear module list for $scopeApp")
|
||||
ConfigManager.getModulesForApp(scopeApp).forEach {
|
||||
ConfigManager.deactivateModule(scopeApp, it)
|
||||
resultRecipient.onNavResult {
|
||||
if (it is NavResult.Value) {
|
||||
scope.launch {
|
||||
val result = it.value as SelectAppsResult.MultipleApps
|
||||
ConfigManager.getModulesForApp(scopeApp).forEach {
|
||||
ConfigManager.deactivateModule(scopeApp, it)
|
||||
}
|
||||
result.selected.forEach {
|
||||
Log.d(TAG, "Activate ${it.app.packageName} for $scopeApp")
|
||||
ConfigManager.activateModule(scopeApp, Module(it.app.packageName, it.app.sourceDir))
|
||||
}
|
||||
}
|
||||
selected.forEach {
|
||||
Log.d(TAG, "Activate ${it.app.packageName} for $scopeApp")
|
||||
ConfigManager.activateModule(scopeApp, Module(it.app.packageName, it.app.sourceDir))
|
||||
}
|
||||
navController.currentBackStackEntry!!.setState("isCancelled", null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,13 +166,10 @@ fun AppManageBody() {
|
|||
scope.launch {
|
||||
scopeApp = it.first.app.packageName
|
||||
val activated = ConfigManager.getModulesForApp(scopeApp).map { it.pkgName }.toSet()
|
||||
navController.currentBackStackEntry!!.setState(
|
||||
"selected",
|
||||
SnapshotStateList<LSPPackageManager.AppInfo>().apply {
|
||||
LSPPackageManager.appList.filterTo(this) { activated.contains(it.app.packageName) }
|
||||
}
|
||||
)
|
||||
navController.navigate(PageList.SelectApps.name + "?multiSelect=true")
|
||||
val initialSelected = LSPPackageManager.appList.mapNotNullTo(ArrayList()) {
|
||||
if (activated.contains(it.app.packageName)) it.app.packageName else null
|
||||
}
|
||||
navigator.navigate(SelectAppsScreenDestination(true, initialSelected))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -220,10 +218,9 @@ fun AppManageBody() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun AppManageFab() {
|
||||
fun AppManageFab(navigator: DestinationsNavigator) {
|
||||
val context = LocalContext.current
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val navController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var shouldSelectDirectory by remember { mutableStateOf(false) }
|
||||
var showNewPatchDialog by remember { mutableStateOf(false) }
|
||||
|
|
@ -286,7 +283,7 @@ fun AppManageFab() {
|
|||
title = {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.page_new_patch),
|
||||
text = stringResource(R.string.screen_new_patch),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
},
|
||||
|
|
@ -296,7 +293,7 @@ fun AppManageFab() {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.secondary),
|
||||
onClick = {
|
||||
navController.navigate(PageList.NewPatch.name + "?from=storage")
|
||||
navigator.navigate(NewPatchScreenDestination("storage"))
|
||||
showNewPatchDialog = false
|
||||
}
|
||||
) {
|
||||
|
|
@ -310,7 +307,7 @@ fun AppManageFab() {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.secondary),
|
||||
onClick = {
|
||||
navController.navigate(PageList.NewPatch.name + "?from=applist")
|
||||
navigator.navigate(NewPatchScreenDestination("applist"))
|
||||
showNewPatchDialog = false
|
||||
}
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,6 @@ package org.lsposed.lspatch.ui.util
|
|||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.navigation.NavHostController
|
||||
|
||||
val LocalNavController = compositionLocalOf<NavHostController> {
|
||||
error("CompositionLocal LocalNavController not present")
|
||||
}
|
||||
|
||||
val LocalSnackbarHost = compositionLocalOf<SnackbarHostState> {
|
||||
error("CompositionLocal LocalSnackbarController not present")
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
package org.lsposed.lspatch.ui.util
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
|
||||
fun <T> NavBackStackEntry.setState(key: String, value: T?) {
|
||||
savedStateHandle.getLiveData<T>(key).value = value
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> NavBackStackEntry.observeState(key: String, initial: T? = null) =
|
||||
savedStateHandle.getLiveData(key, initial).observeAsState()
|
||||
|
||||
fun NavController.navigateWithState(route: String) {
|
||||
navigate(route) {
|
||||
popUpTo(graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -22,16 +21,17 @@ class NewPatchViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
enum class PatchState {
|
||||
SELECTING, CONFIGURING, PATCHING, FINISHED, ERROR
|
||||
INIT, SELECTING, CONFIGURING, PATCHING, FINISHED, ERROR
|
||||
}
|
||||
|
||||
sealed class ViewAction {
|
||||
object DoneInit : ViewAction()
|
||||
data class ConfigurePatch(val app: AppInfo) : ViewAction()
|
||||
object SubmitPatch : ViewAction()
|
||||
object LaunchPatch : ViewAction()
|
||||
}
|
||||
|
||||
var patchState by mutableStateOf(PatchState.SELECTING)
|
||||
var patchState by mutableStateOf(PatchState.INIT)
|
||||
private set
|
||||
|
||||
var useManager by mutableStateOf(true)
|
||||
|
|
@ -39,7 +39,7 @@ class NewPatchViewModel : ViewModel() {
|
|||
var overrideVersionCode by mutableStateOf(false)
|
||||
val sign = mutableStateListOf(false, true)
|
||||
var sigBypassLevel by mutableStateOf(2)
|
||||
var embeddedModules = SnapshotStateList<AppInfo>()
|
||||
var embeddedModules = emptyList<AppInfo>()
|
||||
|
||||
lateinit var patchApp: AppInfo
|
||||
private set
|
||||
|
|
@ -68,12 +68,17 @@ class NewPatchViewModel : ViewModel() {
|
|||
|
||||
fun dispatch(action: ViewAction) {
|
||||
when (action) {
|
||||
is ViewAction.DoneInit -> doneInit()
|
||||
is ViewAction.ConfigurePatch -> configurePatch(action.app)
|
||||
is ViewAction.SubmitPatch -> submitPatch()
|
||||
is ViewAction.LaunchPatch -> launchPatch()
|
||||
}
|
||||
}
|
||||
|
||||
private fun doneInit() {
|
||||
patchState = PatchState.SELECTING
|
||||
}
|
||||
|
||||
private fun configurePatch(app: AppInfo) {
|
||||
Log.d(TAG, "Configuring patch for ${app.app.packageName}")
|
||||
patchApp = app
|
||||
|
|
@ -82,7 +87,7 @@ class NewPatchViewModel : ViewModel() {
|
|||
|
||||
private fun submitPatch() {
|
||||
Log.d(TAG, "Submit patch")
|
||||
if (useManager) embeddedModules.clear()
|
||||
if (useManager) embeddedModules = emptyList()
|
||||
patchOptions = Patcher.Options(
|
||||
verbose = true,
|
||||
config = PatchConfig(useManager, debuggable, overrideVersionCode, sign[0], sign[1], sigBypassLevel, null, null),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package org.lsposed.lspatch.ui.viewmodel
|
|||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
|
@ -26,6 +27,8 @@ class SelectAppsViewModel : ViewModel() {
|
|||
var filteredList by mutableStateOf(listOf<AppInfo>())
|
||||
private set
|
||||
|
||||
val multiSelected = mutableStateListOf<AppInfo>()
|
||||
|
||||
fun filterAppList(refresh: Boolean, filter: (AppInfo) -> Boolean) {
|
||||
viewModelScope.launch {
|
||||
if (LSPPackageManager.appList.isEmpty() || refresh) {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ object LSPPackageManager {
|
|||
}
|
||||
}
|
||||
var result: Intent? = null
|
||||
suspendCoroutine<Unit> { cont ->
|
||||
suspendCoroutine { cont ->
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
val adapter = IntentSenderHelper.IIntentSenderAdaptor { intent ->
|
||||
result = intent
|
||||
|
|
@ -133,7 +133,7 @@ object LSPPackageManager {
|
|||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
var result: Intent? = null
|
||||
suspendCoroutine<Unit> { cont ->
|
||||
suspendCoroutine { cont ->
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
val adapter = IntentSenderHelper.IIntentSenderAdaptor { intent ->
|
||||
result = intent
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@
|
|||
<string name="modules">Modules</string>
|
||||
<string name="shizuku_available">Shizuku service available</string>
|
||||
<string name="shizuku_unavailable">Shizuku service not connected</string>
|
||||
<string name="page_repo">Repo</string>
|
||||
<string name="page_logs">Logs</string>
|
||||
<string name="screen_repo">Repo</string>
|
||||
<string name="screen_logs">Logs</string>
|
||||
|
||||
<!-- Home Page -->
|
||||
<!-- Home Screen -->
|
||||
<string name="home_shizuku_warning">Some functions unavailable</string>
|
||||
<string name="home_api_version">API Version</string>
|
||||
<string name="home_lspatch_version">LSPatch Version</string>
|
||||
|
|
@ -25,8 +25,8 @@
|
|||
<string name="home_description">LSPatch is a free non-root Xposed framework based on LSPosed core.</string>
|
||||
<string name="home_view_source_code"><![CDATA[View source code at %1$s<br/>Join our %2$s channel]]></string>
|
||||
|
||||
<!-- Manage Page -->
|
||||
<string name="page_manage">Manage</string>
|
||||
<!-- Manage Screen -->
|
||||
<string name="screen_manage">Manage</string>
|
||||
<string name="manage_loading">Loading</string>
|
||||
<string name="manage_no_apps">No patched apps yet</string>
|
||||
<string name="manage_update_loader">Update loader</string>
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
<string name="manage_uninstall_successfully">Uninstall successfully</string>
|
||||
<string name="manage_no_modules">No modules yet</string>
|
||||
|
||||
<!-- New Patch Page -->
|
||||
<string name="page_new_patch">New Patch</string>
|
||||
<!-- New Patch Screen -->
|
||||
<string name="screen_new_patch">New Patch</string>
|
||||
<string name="patch_select_dir_title">Select storage directory</string>
|
||||
<string name="patch_select_dir_text">Select a directory to store the patched apks</string>
|
||||
<string name="patch_select_dir_error">Error when setting storage directory</string>
|
||||
|
|
@ -66,11 +66,11 @@
|
|||
<string name="patch_install_successfully">Install successfully</string>
|
||||
<string name="patch_install_failed">Install failed</string>
|
||||
|
||||
<!-- Select Apps Page -->
|
||||
<string name="page_select_apps">Select Apps</string>
|
||||
<!-- Select Apps Screen -->
|
||||
<string name="screen_select_apps">Select Apps</string>
|
||||
|
||||
<!-- Settings Page -->
|
||||
<string name="page_settings">Settings</string>
|
||||
<!-- Settings Screen -->
|
||||
<string name="screen_settings">Settings</string>
|
||||
<string name="settings_keystore">Signature keystore</string>
|
||||
<string name="settings_keystore_default">Built-in</string>
|
||||
<string name="settings_keystore_custom">Custom</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue