Support module scope
This commit is contained in:
parent
66804c425d
commit
290989b0e6
|
|
@ -7,6 +7,7 @@ val coreVerName: String by rootProject.extra
|
|||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("com.google.devtools.ksp")
|
||||
id("dev.rikka.tools.refine")
|
||||
id("kotlin-parcelize")
|
||||
kotlin("android")
|
||||
|
|
@ -73,6 +74,8 @@ dependencies {
|
|||
implementation(projects.share.android)
|
||||
implementation(projects.share.java)
|
||||
|
||||
val roomVersion = "2.4.2"
|
||||
annotationProcessor("androidx.room:room-compiler:$roomVersion")
|
||||
compileOnly("dev.rikka.hidden:stub:2.3.1")
|
||||
implementation("dev.rikka.hidden:compat:2.3.1")
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
|
|
@ -85,6 +88,8 @@ dependencies {
|
|||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-rc01")
|
||||
implementation("androidx.navigation:navigation-compose:2.5.0-rc01")
|
||||
implementation("androidx.preference:preference:1.2.0")
|
||||
implementation("androidx.room:room-ktx:$roomVersion")
|
||||
implementation("androidx.room:room-runtime:$roomVersion")
|
||||
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.9-beta")
|
||||
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.9-beta")
|
||||
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.9-beta")
|
||||
|
|
@ -93,4 +98,5 @@ dependencies {
|
|||
implementation("dev.rikka.shizuku:api:12.1.0")
|
||||
implementation("dev.rikka.shizuku:provider:12.1.0")
|
||||
implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
|
||||
ksp("androidx.room:room-compiler:$roomVersion")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import android.content.Context
|
|||
import android.content.SharedPreferences
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.ShizukuApi
|
||||
import java.io.File
|
||||
|
||||
|
|
@ -29,5 +31,6 @@ class LSPApplication : Application() {
|
|||
tmpApkDir.mkdirs()
|
||||
prefs = lspApp.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
ShizukuApi.init()
|
||||
globalScope.launch { LSPPackageManager.fetchAppList() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
package org.lsposed.lspatch.config
|
||||
|
||||
import androidx.room.Room
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.lsposed.lspatch.database.LSPDatabase
|
||||
import org.lsposed.lspatch.database.entity.Module
|
||||
import org.lsposed.lspatch.database.entity.Scope
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.lspatch.util.ModuleLoader
|
||||
|
||||
object ConfigManager {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val dispatcher = Dispatchers.Default.limitedParallelism(1)
|
||||
|
||||
private val db: LSPDatabase = Room.databaseBuilder(
|
||||
lspApp, LSPDatabase::class.java, "modules_config.db"
|
||||
).build()
|
||||
|
||||
private val moduleDao = db.moduleDao()
|
||||
private val scopeDao = db.scopeDao()
|
||||
|
||||
private val loadedModules = mutableMapOf<Module, org.lsposed.lspd.models.Module>()
|
||||
|
||||
suspend fun updateModules(newModules: Map<String, String>) =
|
||||
withContext(dispatcher) {
|
||||
for (module in moduleDao.getAll()) {
|
||||
val apkPath = newModules[module.pkgName]
|
||||
if (apkPath == null) {
|
||||
moduleDao.delete(module)
|
||||
loadedModules.remove(module)
|
||||
} else if (module.apkPath != apkPath) {
|
||||
module.apkPath = apkPath
|
||||
loadedModules.remove(module)
|
||||
}
|
||||
}
|
||||
for ((pkgName, apkPath) in newModules) {
|
||||
moduleDao.insert(Module(pkgName, apkPath))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun activateModule(pkgName: String, module: Module) =
|
||||
withContext(dispatcher) {
|
||||
scopeDao.insert(Scope(appPkgName = pkgName, modulePkgName = module.pkgName))
|
||||
}
|
||||
|
||||
suspend fun deactivateModule(pkgName: String, module: Module) =
|
||||
withContext(dispatcher) {
|
||||
scopeDao.delete(Scope(appPkgName = pkgName, modulePkgName = module.pkgName))
|
||||
}
|
||||
|
||||
suspend fun getModulesForApp(pkgName: String): List<Module> =
|
||||
withContext(dispatcher) {
|
||||
return@withContext scopeDao.getModulesForApp(pkgName)
|
||||
}
|
||||
|
||||
suspend fun getModuleFilesForApp(pkgName: String): List<org.lsposed.lspd.models.Module> =
|
||||
withContext(dispatcher) {
|
||||
val modules = scopeDao.getModulesForApp(pkgName)
|
||||
return@withContext modules.map {
|
||||
loadedModules.getOrPut(it) {
|
||||
org.lsposed.lspd.models.Module().apply {
|
||||
packageName = it.pkgName
|
||||
apkPath = it.apkPath
|
||||
file = ModuleLoader.loadModule(it.apkPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package org.lsposed.lspatch.database
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import org.lsposed.lspatch.database.dao.ModuleDao
|
||||
import org.lsposed.lspatch.database.dao.ScopeDao
|
||||
|
||||
import org.lsposed.lspatch.database.entity.Module
|
||||
import org.lsposed.lspatch.database.entity.Scope
|
||||
|
||||
@Database(entities = [Module::class, Scope::class], version = 1)
|
||||
abstract class LSPDatabase : RoomDatabase() {
|
||||
abstract fun moduleDao(): ModuleDao
|
||||
abstract fun scopeDao(): ScopeDao
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package org.lsposed.lspatch.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import org.lsposed.lspatch.database.entity.Module
|
||||
|
||||
@Dao
|
||||
interface ModuleDao {
|
||||
|
||||
@Query("SELECT * FROM module")
|
||||
suspend fun getAll(): List<Module>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insert(module: Module)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(module: Module)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package org.lsposed.lspatch.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import org.lsposed.lspatch.database.entity.Module
|
||||
import org.lsposed.lspatch.database.entity.Scope
|
||||
|
||||
@Dao
|
||||
interface ScopeDao {
|
||||
|
||||
@Query("SELECT * FROM module INNER JOIN scope ON module.pkgName = scope.modulePkgName WHERE scope.appPkgName = :appPkgName")
|
||||
suspend fun getModulesForApp(appPkgName: String): List<Module>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(scope: Scope)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(scope: Scope)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package org.lsposed.lspatch.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity
|
||||
data class Module(
|
||||
@PrimaryKey val pkgName: String,
|
||||
var apkPath: String
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package org.lsposed.lspatch.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
|
||||
@Entity(
|
||||
primaryKeys = ["appPkgName", "modulePkgName"],
|
||||
foreignKeys = [ForeignKey(entity = Module::class, parentColumns = ["pkgName"], childColumns = ["modulePkgName"], onDelete = ForeignKey.CASCADE)]
|
||||
)
|
||||
data class Scope(
|
||||
val appPkgName: String,
|
||||
val modulePkgName: String
|
||||
)
|
||||
|
|
@ -1,19 +1,31 @@
|
|||
package org.lsposed.lspatch.manager
|
||||
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.lsposed.lspatch.config.ConfigManager
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.lspd.models.Module
|
||||
import org.lsposed.lspd.service.ILSPApplicationService
|
||||
|
||||
object ManagerService : ILSPApplicationService.Stub() {
|
||||
|
||||
private const val TAG = "ManagerService"
|
||||
|
||||
override fun requestModuleBinder(name: String): IBinder {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getModulesList(): List<Module> {
|
||||
return ModuleProvider.allModules
|
||||
val app = lspApp.packageManager.getNameForUid(Binder.getCallingUid())
|
||||
val list = app?.let {
|
||||
runBlocking { ConfigManager.getModuleFilesForApp(it) }
|
||||
}.orEmpty()
|
||||
Log.d(TAG, "$app calls getModulesList: $list")
|
||||
return list
|
||||
}
|
||||
|
||||
override fun getPrefsPath(packageName: String): String {
|
||||
|
|
|
|||
|
|
@ -2,55 +2,31 @@ package org.lsposed.lspatch.manager
|
|||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.pm.PackageManager
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import org.lsposed.lspatch.TAG
|
||||
import org.lsposed.lspatch.util.ModuleLoader
|
||||
import org.lsposed.lspd.models.Module
|
||||
import org.lsposed.lspatch.lspApp
|
||||
|
||||
class ModuleProvider : ContentProvider() {
|
||||
companion object {
|
||||
lateinit var allModules: List<Module>
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun call(method: String, arg: String?, extras: Bundle?): Bundle {
|
||||
val app = context!!.packageManager.getNameForUid(Binder.getCallingUid())
|
||||
Log.d(TAG, "$app calls binder")
|
||||
when (method) {
|
||||
"getBinder" -> {
|
||||
loadAllModules()
|
||||
return Bundle().apply {
|
||||
val app = lspApp.packageManager.getNameForUid(Binder.getCallingUid())
|
||||
Log.d(TAG, "$app requests ModuleProvider")
|
||||
return when (method) {
|
||||
"getBinder" -> Bundle().apply {
|
||||
putBinder("binder", ManagerService.asBinder())
|
||||
}
|
||||
}
|
||||
else -> throw IllegalArgumentException("Invalid method name")
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadAllModules() {
|
||||
val list = mutableListOf<Module>()
|
||||
for (pkg in context!!.packageManager.getInstalledPackages(PackageManager.GET_META_DATA)) {
|
||||
val app = pkg.applicationInfo ?: continue
|
||||
if (app.metaData != null && app.metaData.containsKey("xposedminversion")) {
|
||||
Module().apply {
|
||||
apkPath = app.publicSourceDir
|
||||
packageName = app.packageName
|
||||
file = ModuleLoader.loadModule(apkPath)
|
||||
}.also { list.add(it) }
|
||||
Log.d(TAG, "send module ${app.packageName}")
|
||||
}
|
||||
}
|
||||
allModules = list
|
||||
}
|
||||
|
||||
override fun query(p0: Uri, p1: Array<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? {
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import android.graphics.drawable.GradientDrawable
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowForwardIos
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -27,8 +29,11 @@ fun AppItem(
|
|||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
checked: Boolean? = null,
|
||||
rightIcon: (@Composable () -> Unit)? = null,
|
||||
additionalContent: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
if (checked != null && rightIcon != null)
|
||||
throw IllegalArgumentException("`checked` and `rightIcon` should not be both set")
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -67,6 +72,9 @@ fun AppItem(
|
|||
modifier = Modifier.padding(start = 20.dp)
|
||||
)
|
||||
}
|
||||
if (rightIcon != null) {
|
||||
rightIcon()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +90,8 @@ private fun AppItemPreview() {
|
|||
icon = shape,
|
||||
label = "Sample App",
|
||||
packageName = "org.lsposed.sample",
|
||||
onClick = {}
|
||||
onClick = {},
|
||||
rightIcon = { Icon(Icons.Filled.ArrowForwardIos, null) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ fun HomePage() {
|
|||
ShizukuCard()
|
||||
InfoCard()
|
||||
SupportCard()
|
||||
Spacer(Modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -84,11 +85,6 @@ private fun ShizukuCard() {
|
|||
}
|
||||
|
||||
ElevatedCard(
|
||||
modifier = Modifier.clickable {
|
||||
if (ShizukuApi.isBinderAvalable && !ShizukuApi.isPermissionGranted) {
|
||||
Shizuku.requestPermission(114514)
|
||||
}
|
||||
},
|
||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
||||
if (ShizukuApi.isPermissionGranted) MaterialTheme.colorScheme.secondaryContainer
|
||||
else MaterialTheme.colorScheme.errorContainer
|
||||
|
|
@ -97,6 +93,11 @@ private fun ShizukuCard() {
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (ShizukuApi.isBinderAvalable && !ShizukuApi.isPermissionGranted) {
|
||||
Shizuku.requestPermission(114514)
|
||||
}
|
||||
}
|
||||
.padding(24.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -13,8 +13,11 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowForwardIos
|
||||
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
|
||||
|
|
@ -29,20 +32,24 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.TAG
|
||||
import org.lsposed.lspatch.config.ConfigManager
|
||||
import org.lsposed.lspatch.database.entity.Module
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.lspatch.ui.component.AppItem
|
||||
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||
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.ManageViewModel
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||
import java.io.IOException
|
||||
|
||||
private const val TAG = "ManagePage"
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ManagePage() {
|
||||
|
|
@ -198,14 +205,8 @@ private fun Fab() {
|
|||
@Composable
|
||||
private fun Body() {
|
||||
val viewModel = viewModel<ManageViewModel>()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (LSPPackageManager.appList.isEmpty()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
LSPPackageManager.fetchAppList()
|
||||
}
|
||||
}
|
||||
}
|
||||
val navController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
if (viewModel.appList.isEmpty()) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
|
|
@ -220,18 +221,53 @@ private fun Body() {
|
|||
)
|
||||
}
|
||||
} else {
|
||||
var scopeApp by rememberSaveable { mutableStateOf("") }
|
||||
val isCancelled by navController.currentBackStackEntry!!.observeState<Boolean>("isCancelled")
|
||||
LaunchedEffect(isCancelled) {
|
||||
if (isCancelled == false) {
|
||||
val selected = navController.currentBackStackEntry!!
|
||||
.savedStateHandle.getLiveData<SnapshotStateList<AppInfo>>("selected").value!!.toSet()
|
||||
Log.d(TAG, "Clear module list for $scopeApp")
|
||||
ConfigManager.getModulesForApp(scopeApp).forEach {
|
||||
ConfigManager.deactivateModule(scopeApp, it)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
LazyColumn {
|
||||
items(
|
||||
items = viewModel.appList,
|
||||
key = { it.first.app.packageName }
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
AppItem(
|
||||
modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)),
|
||||
icon = LSPPackageManager.getIcon(it.first),
|
||||
label = it.first.label,
|
||||
packageName = it.first.app.packageName,
|
||||
onClick = {}
|
||||
) {
|
||||
onClick = {
|
||||
scope.launch {
|
||||
scopeApp = it.first.app.packageName
|
||||
val activated = ConfigManager.getModulesForApp(scopeApp).map { it.pkgName }.toSet()
|
||||
navController.currentBackStackEntry!!.setState(
|
||||
"selected",
|
||||
SnapshotStateList<AppInfo>().apply {
|
||||
LSPPackageManager.appList.filterTo(this) { activated.contains(it.app.packageName) }
|
||||
}
|
||||
)
|
||||
navController.navigate(PageList.SelectApps.name + "?multiSelect=true")
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
// expanded = true
|
||||
},
|
||||
rightIcon = { if (it.second.useManager) Icon(Icons.Filled.ArrowForwardIos, null) },
|
||||
additionalContent = {
|
||||
val text = buildAnnotatedString {
|
||||
val (text, color) =
|
||||
if (it.second.useManager) stringResource(R.string.patch_local) to MaterialTheme.colorScheme.secondary
|
||||
|
|
@ -247,6 +283,11 @@ private fun Body() {
|
|||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
/* TODO */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import org.lsposed.lspatch.ui.component.settings.SettingsItem
|
|||
import org.lsposed.lspatch.ui.util.*
|
||||
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
|
||||
|
|
@ -71,7 +72,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
|||
runBlocking {
|
||||
LSPPackageManager.getAppInfoFromApks(apks)
|
||||
.onSuccess {
|
||||
viewModel.configurePatch(it)
|
||||
viewModel.dispatch(ViewAction.ConfigurePatch(it))
|
||||
}
|
||||
.onFailure {
|
||||
lspApp.globalScope.launch { snackbarHost.showSnackbar(it.message ?: "Unknown error") }
|
||||
|
|
@ -86,7 +87,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
|||
"storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive"))
|
||||
"applist" -> {
|
||||
entry.savedStateHandle.getLiveData<AppInfo>("appInfo").observe(lifecycleOwner) {
|
||||
viewModel.configurePatch(it)
|
||||
viewModel.dispatch(ViewAction.ConfigurePatch(it))
|
||||
}
|
||||
navController.navigate(PageList.SelectApps.name + "?multiSelect=false")
|
||||
}
|
||||
|
|
@ -111,7 +112,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
|||
) { innerPadding ->
|
||||
if (viewModel.patchState == PatchState.CONFIGURING) {
|
||||
LaunchedEffect(Unit) {
|
||||
entry.savedStateHandle.getLiveData<SnapshotStateList<AppInfo>>("selected", SnapshotStateList()).observe(lifecycleOwner) {
|
||||
entry.savedStateHandle.getLiveData("selected", SnapshotStateList<AppInfo>()).observe(lifecycleOwner) {
|
||||
viewModel.embeddedModules = it
|
||||
}
|
||||
}
|
||||
|
|
@ -142,7 +143,7 @@ private fun ConfiguringFab() {
|
|||
ExtendedFloatingActionButton(
|
||||
text = { Text(stringResource(R.string.patch_start)) },
|
||||
icon = { Icon(Icons.Outlined.AutoFixHigh, null) },
|
||||
onClick = { viewModel.submitPatch() }
|
||||
onClick = { viewModel.dispatch(ViewAction.SubmitPatch) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +274,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.logs.isEmpty()) {
|
||||
viewModel.launchPatch()
|
||||
viewModel.dispatch(ViewAction.LaunchPatch)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -288,9 +289,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
.animateContentSize(spring(stiffness = Spring.StiffnessLow))
|
||||
) {
|
||||
ShimmerAnimation(enabled = viewModel.patchState == PatchState.PATCHING) {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace)
|
||||
) {
|
||||
ProvideTextStyle(MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace)) {
|
||||
val scrollState = rememberLazyListState()
|
||||
LazyColumn(
|
||||
state = scrollState,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ fun SelectAppsPage(multiSelect: Boolean) {
|
|||
val filter: (AppInfo) -> Boolean = {
|
||||
val packageLowerCase = searchPackage.toLowerCase(Locale.current)
|
||||
val contains = it.label.toLowerCase(Locale.current).contains(packageLowerCase) || it.app.packageName.contains(packageLowerCase)
|
||||
if (multiSelect) contains && it.app.metaData?.get("xposedminversion") != null
|
||||
if (multiSelect) contains && it.isXposedModule
|
||||
else contains && it.app.flags and ApplicationInfo.FLAG_SYSTEM == 0
|
||||
}
|
||||
|
||||
|
|
@ -94,9 +94,13 @@ fun SelectAppsPage(multiSelect: Boolean) {
|
|||
@Composable
|
||||
private fun MultiSelectFab() {
|
||||
val navController = LocalNavController.current
|
||||
FloatingActionButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(Icons.Outlined.Done, stringResource(R.string.add))
|
||||
}
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
navController.previousBackStackEntry!!.setState("isCancelled", false)
|
||||
navController.popBackStack()
|
||||
},
|
||||
content = { Icon(Icons.Outlined.Done, stringResource(R.string.add)) }
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
|
|||
|
|
@ -77,7 +77,10 @@ private fun KeyStore() {
|
|||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.settings_keystore_custom)) },
|
||||
onClick = { showDialog = true }
|
||||
onClick = {
|
||||
dropDownExpanded = false
|
||||
showDialog = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -160,7 +163,10 @@ private fun KeyStore() {
|
|||
)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
LaunchedEffect(interactionSource) {
|
||||
interactionSource.interactions.collect { interaction ->
|
||||
|
|
@ -178,9 +184,7 @@ private fun KeyStore() {
|
|||
else -> null
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 8.dp),
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
text = wrongText ?: stringResource(R.string.settings_keystore_desc),
|
||||
color = if (wrongText != null) MaterialTheme.colorScheme.error else Color.Unspecified
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ class NewPatchViewModel : ViewModel() {
|
|||
SELECTING, CONFIGURING, PATCHING, FINISHED, ERROR
|
||||
}
|
||||
|
||||
sealed class ViewAction {
|
||||
data class ConfigurePatch(val app: AppInfo) : ViewAction()
|
||||
object SubmitPatch : ViewAction()
|
||||
object LaunchPatch : ViewAction()
|
||||
}
|
||||
|
||||
var patchState by mutableStateOf(PatchState.SELECTING)
|
||||
private set
|
||||
|
||||
|
|
@ -31,10 +37,10 @@ class NewPatchViewModel : ViewModel() {
|
|||
var overrideVersionCode by mutableStateOf(false)
|
||||
val sign = mutableStateListOf(false, true)
|
||||
var sigBypassLevel by mutableStateOf(2)
|
||||
var embeddedModules = SnapshotStateList<AppInfo>()
|
||||
|
||||
lateinit var patchApp: AppInfo
|
||||
private set
|
||||
lateinit var embeddedModules: SnapshotStateList<AppInfo>
|
||||
lateinit var patchOptions: Patcher.Options
|
||||
private set
|
||||
|
||||
|
|
@ -58,13 +64,21 @@ class NewPatchViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun configurePatch(app: AppInfo) {
|
||||
fun dispatch(action: ViewAction) {
|
||||
when (action) {
|
||||
is ViewAction.ConfigurePatch -> configurePatch(action.app)
|
||||
is ViewAction.SubmitPatch -> submitPatch()
|
||||
is ViewAction.LaunchPatch -> launchPatch()
|
||||
}
|
||||
}
|
||||
|
||||
private fun configurePatch(app: AppInfo) {
|
||||
Log.d(TAG, "Configuring patch for ${app.app.packageName}")
|
||||
patchApp = app
|
||||
patchState = PatchState.CONFIGURING
|
||||
}
|
||||
|
||||
fun submitPatch() {
|
||||
private fun submitPatch() {
|
||||
Log.d(TAG, "Submit patch")
|
||||
if (useManager) embeddedModules.clear()
|
||||
patchOptions = Patcher.Options(
|
||||
|
|
@ -80,7 +94,7 @@ class NewPatchViewModel : ViewModel() {
|
|||
patchState = PatchState.PATCHING
|
||||
}
|
||||
|
||||
fun launchPatch() {
|
||||
private fun launchPatch() {
|
||||
logger.i("Launch patch")
|
||||
viewModelScope.launch {
|
||||
patchState = try {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.withContext
|
|||
import kotlinx.parcelize.Parcelize
|
||||
import org.lsposed.lspatch.Constants.PATCH_FILE_SUFFIX
|
||||
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
||||
import org.lsposed.lspatch.config.ConfigManager
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.patch.util.ManifestParser
|
||||
import java.io.File
|
||||
|
|
@ -35,7 +36,10 @@ object LSPPackageManager {
|
|||
private const val TAG = "LSPPackageManager"
|
||||
|
||||
@Parcelize
|
||||
class AppInfo(val app: ApplicationInfo, val label: String) : Parcelable
|
||||
class AppInfo(val app: ApplicationInfo, val label: String) : Parcelable {
|
||||
val isXposedModule: Boolean
|
||||
get() = app.metaData?.get("xposedminversion") != null
|
||||
}
|
||||
|
||||
const val STATUS_USER_CANCELLED = -2
|
||||
|
||||
|
|
@ -53,7 +57,11 @@ object LSPPackageManager {
|
|||
collection.add(AppInfo(it, label.toString()))
|
||||
appIcon[it.packageName] = pm.getApplicationIcon(it)
|
||||
}
|
||||
collection.sortWith(compareBy(Collator.getInstance(Locale.getDefault())) { it.label })
|
||||
collection.sortWith(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))
|
||||
val modules = buildMap {
|
||||
collection.forEach { if (it.isXposedModule) put(it.app.packageName, it.app.sourceDir) }
|
||||
}
|
||||
ConfigManager.updateModules(modules)
|
||||
appList = collection
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Stub!"
|
||||
android:textSize="50sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -10,6 +10,7 @@ pluginManagement {
|
|||
plugins {
|
||||
id("com.android.library") version agpVersion
|
||||
id("com.android.application") version agpVersion
|
||||
id("com.google.devtools.ksp") version "1.6.21-1.0.5"
|
||||
id("dev.rikka.tools.refine") version "3.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue