Fix UI bugs

This commit is contained in:
Nullptr 2022-05-11 13:32:57 +08:00
parent a9b27b1427
commit d7b3b4cb6b
5 changed files with 75 additions and 78 deletions

View File

@ -10,6 +10,7 @@ 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.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.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@ -18,7 +19,7 @@ import org.lsposed.lspatch.ui.page.PageList
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.LocalNavController
import org.lsposed.lspatch.ui.util.LocalSnackbarHost import org.lsposed.lspatch.ui.util.LocalSnackbarHost
import org.lsposed.lspatch.ui.util.currentRoute import org.lsposed.lspatch.ui.util.navigateWithState
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -27,7 +28,6 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
val navController = rememberAnimatedNavController() val navController = rememberAnimatedNavController()
val currentRoute = navController.currentRoute
var mainPage by rememberSaveable { mutableStateOf(PageList.Home) } var mainPage by rememberSaveable { mutableStateOf(PageList.Home) }
LSPTheme { LSPTheme {
@ -39,12 +39,9 @@ class MainActivity : ComponentActivity() {
Scaffold( Scaffold(
bottomBar = { bottomBar = {
MainNavigationBar(mainPage) { MainNavigationBar(mainPage) {
if (mainPage == it) return@MainNavigationBar
mainPage = it mainPage = it
navController.navigate(it.name) { navController.navigateWithState(it.name)
currentRoute?.let { route ->
popUpTo(route) { inclusive = true }
}
}
} }
}, },
snackbarHost = { SnackbarHost(snackbarHostState) } snackbarHost = { SnackbarHost(snackbarHostState) }
@ -64,12 +61,8 @@ private fun MainNavHost(navController: NavHostController, modifier: Modifier) {
startDestination = PageList.Home.name, startDestination = PageList.Home.name,
modifier = modifier modifier = modifier
) { ) {
PageList.values().forEach { page -> for (page in PageList.values()) {
val sb = StringBuilder(page.name) composable(route = page.route, arguments = page.arguments, content = page.body)
if (page.arguments.isNotEmpty()) {
sb.append(page.arguments.joinToString(",", "?") { "${it.name}={${it.name}}" })
}
composable(route = sb.toString(), arguments = page.arguments, content = page.body)
} }
} }
} }

View File

@ -23,12 +23,10 @@ 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.saveable.rememberSaveable
import androidx.compose.runtime.snapshots.SnapshotStateList 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.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner 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
@ -38,7 +36,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.lsposed.lspatch.Patcher
import org.lsposed.lspatch.R import org.lsposed.lspatch.R
import org.lsposed.lspatch.lspApp import org.lsposed.lspatch.lspApp
import org.lsposed.lspatch.ui.component.SelectionColumn import org.lsposed.lspatch.ui.component.SelectionColumn
@ -51,7 +48,6 @@ import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.PatchState
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 org.lsposed.patch.util.Logger
import java.io.File import java.io.File
private const val TAG = "NewPatchPage" private const val TAG = "NewPatchPage"
@ -64,12 +60,6 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
val navController = LocalNavController.current val navController = LocalNavController.current
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
val isCancelled by entry.observeState<Boolean>("isCancelled") val isCancelled by entry.observeState<Boolean>("isCancelled")
LaunchedEffect(Unit) {
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
entry.savedStateHandle.getLiveData<AppInfo>("appInfo").observe(lifecycleOwner) {
viewModel.configurePatch(it)
}
}
Log.d(TAG, "PatchState: ${viewModel.patchState}") Log.d(TAG, "PatchState: ${viewModel.patchState}")
if (viewModel.patchState == PatchState.SELECTING) { if (viewModel.patchState == PatchState.SELECTING) {
@ -90,10 +80,16 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
} }
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
if (isCancelled == true) navController.popBackStack() if (isCancelled == true) navController.popBackStack()
else when (from) { else when (from) {
"storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive")) "storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive"))
"applist" -> navController.navigate(PageList.SelectApps.name + "?multiSelect=false") "applist" -> {
entry.savedStateHandle.getLiveData<AppInfo>("appInfo").observe(lifecycleOwner) {
viewModel.configurePatch(it)
}
navController.navigate(PageList.SelectApps.name + "?multiSelect=false")
}
} }
} }
} else { } else {
@ -268,44 +264,16 @@ private fun PatchOptionsBody(modifier: Modifier) {
} }
} }
private class PatchLogger(private val logs: MutableList<Pair<Int, String>>) : Logger() {
override fun d(msg: String) {
if (verbose) {
Log.d(TAG, msg)
logs += Log.DEBUG to msg
}
}
override fun i(msg: String) {
Log.i(TAG, msg)
logs += Log.INFO to msg
}
override fun e(msg: String) {
Log.e(TAG, msg)
logs += Log.ERROR to msg
}
}
@Composable @Composable
private fun DoPatchBody(modifier: Modifier) { private fun DoPatchBody(modifier: Modifier) {
val viewModel = viewModel<NewPatchViewModel>() val viewModel = viewModel<NewPatchViewModel>()
val snackbarHost = LocalSnackbarHost.current val snackbarHost = LocalSnackbarHost.current
val navController = LocalNavController.current val navController = LocalNavController.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val logs = remember { mutableStateListOf<Pair<Int, String>>() }
val logger = remember { PatchLogger(logs) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
try { if (viewModel.logs.isEmpty()) {
Patcher.patch(logger, viewModel.patchOptions) viewModel.launchPatch()
viewModel.finishPatch()
} catch (t: Throwable) {
logger.e(t.message.orEmpty())
logger.e(t.stackTraceToString())
viewModel.failPatch()
} finally {
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
} }
} }
@ -333,7 +301,7 @@ private fun DoPatchBody(modifier: Modifier) {
.background(brush) .background(brush)
.padding(horizontal = 24.dp, vertical = 18.dp) .padding(horizontal = 24.dp, vertical = 18.dp)
) { ) {
items(logs) { items(viewModel.logs) {
when (it.first) { when (it.first) {
Log.DEBUG -> Text(text = it.second) Log.DEBUG -> Text(text = it.second)
Log.INFO -> Text(text = it.second) Log.INFO -> Text(text = it.second)
@ -357,7 +325,7 @@ private fun DoPatchBody(modifier: Modifier) {
val installSuccessfully = stringResource(R.string.patch_install_successfully) val installSuccessfully = stringResource(R.string.patch_install_successfully)
val installFailed = stringResource(R.string.patch_install_failed) val installFailed = stringResource(R.string.patch_install_failed)
val copyError = stringResource(R.string.patch_copy_error) val copyError = stringResource(R.string.patch_copy_error)
var installing by rememberSaveable { mutableStateOf(false) } var installing by remember { mutableStateOf(false) }
if (installing) InstallDialog(viewModel.patchApp) { status, message -> if (installing) InstallDialog(viewModel.patchApp) { status, message ->
scope.launch { scope.launch {
LSPPackageManager.fetchAppList() LSPPackageManager.fetchAppList()
@ -408,7 +376,7 @@ private fun DoPatchBody(modifier: Modifier) {
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
onClick = { onClick = {
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(ClipData.newPlainText("LSPatch", logs.joinToString { it.second + "\n" })) cm.setPrimaryClip(ClipData.newPlainText("LSPatch", viewModel.logs.joinToString { it.second + "\n" }))
}, },
content = { Text(stringResource(R.string.patch_copy_error)) } content = { Text(stringResource(R.string.patch_copy_error)) }
) )

View File

@ -66,4 +66,11 @@ enum class PageList(
NewPatch -> stringResource(R.string.page_new_patch) NewPatch -> stringResource(R.string.page_new_patch)
SelectApps -> stringResource(R.string.page_select_apps) SelectApps -> stringResource(R.string.page_select_apps)
} }
val route = buildString {
append(name)
if (arguments.isNotEmpty()) {
append(arguments.joinToString(",", "?") { "${it.name}={${it.name}}" })
}
}
} }

View File

@ -5,27 +5,18 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.currentBackStackEntryAsState
val NavController.currentRoute: String?
@Composable get() = currentBackStackEntryAsState().value?.destination?.route
val NavController.startRoute: String?
get() = graph.findStartDestination().route
fun <T> NavBackStackEntry.setState(key: String, value: T?) { fun <T> NavBackStackEntry.setState(key: String, value: T?) {
savedStateHandle.getLiveData<T>(key).value = value savedStateHandle.getLiveData<T>(key).value = value
} }
@Composable @Composable
fun <T> NavBackStackEntry.observeState(key: String, initial: T? = null) = savedStateHandle.getLiveData(key, initial).observeAsState() fun <T> NavBackStackEntry.observeState(key: String, initial: T? = null) =
savedStateHandle.getLiveData(key, initial).observeAsState()
@Composable fun NavController.navigateWithState(route: String) {
fun NavController.isAtStartRoute(): Boolean = currentRoute == startRoute navigate(route) {
popUpTo(graph.findStartDestination().id) {
fun NavController.navigateWithState(route: String?) {
navigate(route.toString()) {
popUpTo(startRoute.toString()) {
saveState = true saveState = true
} }
launchSingleTop = true launchSingleTop = true

View File

@ -1,13 +1,21 @@
package org.lsposed.lspatch.ui.viewmodel package org.lsposed.lspatch.ui.viewmodel
import android.util.Log
import androidx.compose.runtime.getValue 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.compose.runtime.snapshots.SnapshotStateList
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.lsposed.lspatch.Patcher import org.lsposed.lspatch.Patcher
import org.lsposed.lspatch.lspApp
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
import org.lsposed.patch.util.Logger
import java.io.File
private const val TAG = "NewPatchViewModel"
class NewPatchViewModel : ViewModel() { class NewPatchViewModel : ViewModel() {
@ -21,7 +29,7 @@ class NewPatchViewModel : ViewModel() {
var useManager by mutableStateOf(true) var useManager by mutableStateOf(true)
var debuggable by mutableStateOf(false) var debuggable by mutableStateOf(false)
var overrideVersionCode by mutableStateOf(false) var overrideVersionCode by mutableStateOf(false)
var sign = mutableStateListOf(false, true) val sign = mutableStateListOf(false, true)
var sigBypassLevel by mutableStateOf(2) var sigBypassLevel by mutableStateOf(2)
lateinit var patchApp: AppInfo lateinit var patchApp: AppInfo
@ -30,12 +38,34 @@ class NewPatchViewModel : ViewModel() {
lateinit var patchOptions: Patcher.Options lateinit var patchOptions: Patcher.Options
private set private set
val logs = mutableStateListOf<Pair<Int, String>>()
private val logger = object : Logger() {
override fun d(msg: String) {
if (verbose) {
Log.d(TAG, msg)
logs += Log.DEBUG to msg
}
}
override fun i(msg: String) {
Log.i(TAG, msg)
logs += Log.INFO to msg
}
override fun e(msg: String) {
Log.e(TAG, msg)
logs += Log.ERROR to msg
}
}
fun configurePatch(app: AppInfo) { fun configurePatch(app: AppInfo) {
Log.d(TAG, "Configuring patch for ${app.app.packageName}")
patchApp = app patchApp = app
patchState = PatchState.CONFIGURING patchState = PatchState.CONFIGURING
} }
fun submitPatch() { fun submitPatch() {
Log.d(TAG, "Submit patch")
if (useManager) embeddedModules.clear() if (useManager) embeddedModules.clear()
patchOptions = Patcher.Options( patchOptions = Patcher.Options(
apkPaths = listOf(patchApp.app.sourceDir) + (patchApp.app.splitSourceDirs ?: emptyArray()), apkPaths = listOf(patchApp.app.sourceDir) + (patchApp.app.splitSourceDirs ?: emptyArray()),
@ -50,11 +80,19 @@ class NewPatchViewModel : ViewModel() {
patchState = PatchState.PATCHING patchState = PatchState.PATCHING
} }
fun finishPatch() { fun launchPatch() {
patchState = PatchState.FINISHED logger.i("Launch patch")
viewModelScope.launch {
patchState = try {
Patcher.patch(logger, patchOptions)
PatchState.FINISHED
} catch (t: Throwable) {
logger.e(t.message.orEmpty())
logger.e(t.stackTraceToString())
PatchState.ERROR
} finally {
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
}
} }
fun failPatch() {
patchState = PatchState.ERROR
} }
} }