diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts index ce583ac..ea6de3b 100644 --- a/manager/build.gradle.kts +++ b/manager/build.gradle.kts @@ -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${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") } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt b/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt index 96728aa..2d9e117 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt @@ -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 ) } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/component/SearchBar.kt b/manager/src/main/java/org/lsposed/lspatch/ui/component/SearchBar.kt index 20a992e..91ee333 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/component/SearchBar.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/component/SearchBar.kt @@ -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) diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/BottomBarDestination.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/BottomBarDestination.kt new file mode 100644 index 0000000..4fdc54c --- /dev/null +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/BottomBarDestination.kt @@ -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); +} diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/HomeScreen.kt similarity index 97% rename from manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt rename to manager/src/main/java/org/lsposed/lspatch/ui/page/HomeScreen.kt index a84262c..e2bba09 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/HomeScreen.kt @@ -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 diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/RepoPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/LogsScreen.kt similarity index 80% rename from manager/src/main/java/org/lsposed/lspatch/ui/page/RepoPage.kt rename to manager/src/main/java/org/lsposed/lspatch/ui/page/LogsScreen.kt index 7be838e..3204c3b 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/RepoPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/LogsScreen.kt @@ -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)) } ) } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManageScreen.kt similarity index 81% rename from manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt rename to manager/src/main/java/org/lsposed/lspatch/ui/page/ManageScreen.kt index 942d5ed..b335901 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManageScreen.kt @@ -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 +) { 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)) } ) } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchScreen.kt similarity index 81% rename from manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt rename to manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchScreen.kt index 1bc4244..d934649 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchScreen.kt @@ -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, + from: String +) { val viewModel = viewModel() val snackbarHost = LocalSnackbarHost.current - val navController = LocalNavController.current - val lifecycleOwner = LocalLifecycleOwner.current - val isCancelled by entry.observeState("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").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()).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() - 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() 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)) diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt deleted file mode 100644 index 1760191..0000000 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt +++ /dev/null @@ -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 = 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}}" }) - } - } -} diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/LogsPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/RepoScreen.kt similarity index 80% rename from manager/src/main/java/org/lsposed/lspatch/ui/page/LogsPage.kt rename to manager/src/main/java/org/lsposed/lspatch/ui/page/RepoScreen.kt index 901e769..67c55a0 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/LogsPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/RepoScreen.kt @@ -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)) } ) } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsScreen.kt similarity index 72% rename from manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt rename to manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsScreen.kt index deb7b3a..ffe74f5 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsScreen.kt @@ -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) : SelectAppsResult() +} + @OptIn(ExperimentalMaterial3Api::class) +@Destination @Composable -fun SelectAppsPage(multiSelect: Boolean) { +fun SelectAppsScreen( + navigator: ResultBackNavigator, + multiSelect: Boolean, + initialSelected: ArrayList? = null +) { val viewModel = viewModel() - 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() - 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() - val selected by navController.previousBackStackEntry!!.observeState>("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 ) diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsScreen.kt similarity index 98% rename from manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt rename to manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsScreen.kt index b405bc8..b190ea9 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsScreen.kt @@ -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)) } ) } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/manage/AppManagePage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/manage/AppManagePage.kt index 1c0255b..6ce7b05 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/manage/AppManagePage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/manage/AppManagePage.kt @@ -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 +) { val viewModel = viewModel() 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("isCancelled") - LaunchedEffect(isCancelled) { - if (isCancelled == false) { - val selected = navController.currentBackStackEntry!! - .savedStateHandle.getLiveData>("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().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 } ) { diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/util/CompositionProvider.kt b/manager/src/main/java/org/lsposed/lspatch/ui/util/CompositionProvider.kt index 9502419..480aa6d 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/util/CompositionProvider.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/util/CompositionProvider.kt @@ -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 { - error("CompositionLocal LocalNavController not present") -} val LocalSnackbarHost = compositionLocalOf { error("CompositionLocal LocalSnackbarController not present") diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/util/Navigation.kt b/manager/src/main/java/org/lsposed/lspatch/ui/util/Navigation.kt deleted file mode 100644 index 861d8d5..0000000 --- a/manager/src/main/java/org/lsposed/lspatch/ui/util/Navigation.kt +++ /dev/null @@ -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 NavBackStackEntry.setState(key: String, value: T?) { - savedStateHandle.getLiveData(key).value = value -} - -@Composable -fun 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 - } -} diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt index f3e944a..0dfee95 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/NewPatchViewModel.kt @@ -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() + var embeddedModules = emptyList() 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), diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppsViewModel.kt b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppsViewModel.kt index c50ca99..cbe0842 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppsViewModel.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppsViewModel.kt @@ -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()) private set + val multiSelected = mutableStateListOf() + fun filterAppList(refresh: Boolean, filter: (AppInfo) -> Boolean) { viewModelScope.launch { if (LSPPackageManager.appList.isEmpty() || refresh) { diff --git a/manager/src/main/java/org/lsposed/lspatch/util/LSPPackageManager.kt b/manager/src/main/java/org/lsposed/lspatch/util/LSPPackageManager.kt index f53bd14..eda68c8 100644 --- a/manager/src/main/java/org/lsposed/lspatch/util/LSPPackageManager.kt +++ b/manager/src/main/java/org/lsposed/lspatch/util/LSPPackageManager.kt @@ -103,7 +103,7 @@ object LSPPackageManager { } } var result: Intent? = null - suspendCoroutine { 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 { cont -> + suspendCoroutine { cont -> val countDownLatch = CountDownLatch(1) val adapter = IntentSenderHelper.IIntentSenderAdaptor { intent -> result = intent diff --git a/manager/src/main/res/values/strings.xml b/manager/src/main/res/values/strings.xml index 0fb13b0..a397039 100644 --- a/manager/src/main/res/values/strings.xml +++ b/manager/src/main/res/values/strings.xml @@ -9,10 +9,10 @@ Modules Shizuku service available Shizuku service not connected - Repo - Logs + Repo + Logs - + Some functions unavailable API Version LSPatch Version @@ -25,8 +25,8 @@ LSPatch is a free non-root Xposed framework based on LSPosed core. Join our %2$s channel]]> - - Manage + + Manage Loading No patched apps yet Update loader @@ -39,8 +39,8 @@ Uninstall successfully No modules yet - - New Patch + + New Patch Select storage directory Select a directory to store the patched apks Error when setting storage directory @@ -66,11 +66,11 @@ Install successfully Install failed - - Select Apps + + Select Apps - - Settings + + Settings Signature keystore Built-in Custom