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