parent
5a90ed436e
commit
5c8509e4f5
|
|
@ -30,6 +30,8 @@ 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.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.ramcosta.composedestinations.result.NavResult
|
import com.ramcosta.composedestinations.result.NavResult
|
||||||
import com.ramcosta.composedestinations.result.ResultRecipient
|
import com.ramcosta.composedestinations.result.ResultRecipient
|
||||||
|
|
@ -68,191 +70,199 @@ fun AppManageBody(
|
||||||
val snackbarHost = LocalSnackbarHost.current
|
val snackbarHost = LocalSnackbarHost.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
if (viewModel.appList.isEmpty()) {
|
var scopeApp by rememberSaveable { mutableStateOf("") }
|
||||||
Box(Modifier.fillMaxSize()) {
|
var afterCheckManager by remember { mutableStateOf<(() -> Unit)?>(null) }
|
||||||
Text(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
resultRecipient.onNavResult {
|
||||||
text = run {
|
if (it is NavResult.Value) {
|
||||||
if (NPackageManager.appList.isEmpty()) stringResource(R.string.manage_loading)
|
scope.launch {
|
||||||
else stringResource(R.string.manage_no_apps)
|
val result = it.value as SelectAppsResult.MultipleApps
|
||||||
},
|
ConfigManager.getModulesForApp(scopeApp).forEach {
|
||||||
fontFamily = FontFamily.Serif,
|
ConfigManager.deactivateModule(scopeApp, it)
|
||||||
style = MaterialTheme.typography.headlineSmall
|
}
|
||||||
)
|
result.selected.forEach {
|
||||||
}
|
Log.d(TAG, "Activate ${it.app.packageName} for $scopeApp")
|
||||||
} else {
|
ConfigManager.activateModule(scopeApp, Module(it.app.packageName, it.app.sourceDir))
|
||||||
var scopeApp by rememberSaveable { mutableStateOf("") }
|
|
||||||
var afterCheckManager by remember { mutableStateOf<(() -> Unit)?>(null) }
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
when (viewModel.updateLoaderState) {
|
when (viewModel.updateLoaderState) {
|
||||||
is ProcessingState.Idle -> Unit
|
is ProcessingState.Idle -> Unit
|
||||||
is ProcessingState.Processing -> LoadingDialog()
|
is ProcessingState.Processing -> LoadingDialog()
|
||||||
is ProcessingState.Done -> {
|
is ProcessingState.Done -> {
|
||||||
val it = viewModel.updateLoaderState as ProcessingState.Done
|
val it = viewModel.updateLoaderState as ProcessingState.Done
|
||||||
val updateSuccessfully = stringResource(R.string.manage_update_loader_successfully)
|
val updateSuccessfully = stringResource(R.string.manage_update_loader_successfully)
|
||||||
val updateFailed = stringResource(R.string.manage_update_loader_failed)
|
val updateFailed = stringResource(R.string.manage_update_loader_failed)
|
||||||
val copyError = stringResource(R.string.copy_error)
|
val copyError = stringResource(R.string.copy_error)
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
it.result.onSuccess {
|
it.result.onSuccess {
|
||||||
snackbarHost.showSnackbar(updateSuccessfully)
|
snackbarHost.showSnackbar(updateSuccessfully)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
val result = snackbarHost.showSnackbar(updateFailed, copyError)
|
val result = snackbarHost.showSnackbar(updateFailed, copyError)
|
||||||
if (result == SnackbarResult.ActionPerformed) {
|
if (result == SnackbarResult.ActionPerformed) {
|
||||||
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
cm.setPrimaryClip(ClipData.newPlainText("NPatch", it.toString()))
|
cm.setPrimaryClip(ClipData.newPlainText("NPatch", it.toString()))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
viewModel.dispatch(AppManageViewModel.ViewAction.ClearUpdateLoaderResult)
|
|
||||||
}
|
}
|
||||||
|
viewModel.dispatch(AppManageViewModel.ViewAction.ClearUpdateLoaderResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when (viewModel.optimizeState) {
|
}
|
||||||
is ProcessingState.Idle -> Unit
|
when (viewModel.optimizeState) {
|
||||||
is ProcessingState.Processing -> LoadingDialog()
|
is ProcessingState.Idle -> Unit
|
||||||
is ProcessingState.Done -> {
|
is ProcessingState.Processing -> LoadingDialog()
|
||||||
val it = viewModel.optimizeState as ProcessingState.Done
|
is ProcessingState.Done -> {
|
||||||
val optimizeSucceed = stringResource(R.string.manage_optimize_successfully)
|
val it = viewModel.optimizeState as ProcessingState.Done
|
||||||
val optimizeFailed = stringResource(R.string.manage_optimize_failed)
|
val optimizeSucceed = stringResource(R.string.manage_optimize_successfully)
|
||||||
LaunchedEffect(Unit) {
|
val optimizeFailed = stringResource(R.string.manage_optimize_failed)
|
||||||
snackbarHost.showSnackbar(if (it.result) optimizeSucceed else optimizeFailed)
|
LaunchedEffect(Unit) {
|
||||||
viewModel.dispatch(AppManageViewModel.ViewAction.ClearOptimizeResult)
|
snackbarHost.showSnackbar(if (it.result) optimizeSucceed else optimizeFailed)
|
||||||
}
|
viewModel.dispatch(AppManageViewModel.ViewAction.ClearOptimizeResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 下拉刷新
|
||||||
|
SwipeRefresh(
|
||||||
|
state = rememberSwipeRefreshState(viewModel.isRefreshing),
|
||||||
|
onRefresh = { viewModel.dispatch(AppManageViewModel.ViewAction.Refresh) },
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
if (viewModel.appList.isEmpty()) {
|
||||||
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
text = run {
|
||||||
|
if (NPackageManager.appList.isEmpty()) stringResource(R.string.manage_loading)
|
||||||
|
else stringResource(R.string.manage_no_apps)
|
||||||
|
},
|
||||||
|
fontFamily = FontFamily.Serif,
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
|
items(
|
||||||
|
items = viewModel.appList,
|
||||||
|
key = { it.first.app.packageName }
|
||||||
|
) { (appInfo, patchConfig) ->
|
||||||
|
val isRolling = patchConfig.useManager && patchConfig.lspConfig.VERSION_CODE >= Constants.MIN_ROLLING_VERSION_CODE
|
||||||
|
val canUpdateLoader = !isRolling && (patchConfig.lspConfig.VERSION_CODE < LSPConfig.instance.VERSION_CODE || patchConfig.managerPackageName != BuildConfig.APPLICATION_ID)
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LazyColumn(Modifier.fillMaxHeight()) {
|
AnywhereDropdown(
|
||||||
items(
|
expanded = expanded,
|
||||||
items = viewModel.appList,
|
onDismissRequest = { expanded = false },
|
||||||
key = { it.first.app.packageName }
|
onClick = { expanded = true },
|
||||||
) { (appInfo, patchConfig) ->
|
onLongClick = { expanded = true },
|
||||||
val isRolling = patchConfig.useManager && patchConfig.lspConfig.VERSION_CODE >= Constants.MIN_ROLLING_VERSION_CODE
|
surface = {
|
||||||
val canUpdateLoader = !isRolling && (patchConfig.lspConfig.VERSION_CODE < LSPConfig.instance.VERSION_CODE || patchConfig.managerPackageName != BuildConfig.APPLICATION_ID)
|
AppItem(
|
||||||
var expanded by remember { mutableStateOf(false) }
|
icon = NPackageManager.getIcon(appInfo),
|
||||||
|
label = appInfo.label,
|
||||||
|
packageName = appInfo.app.packageName,
|
||||||
|
additionalContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
val patchText = if (patchConfig.useManager) {
|
||||||
|
stringResource(R.string.patch_local)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.patch_integrated)
|
||||||
|
}
|
||||||
|
val patchColor = if (patchConfig.useManager) {
|
||||||
|
MaterialTheme.colorScheme.secondary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.tertiary
|
||||||
|
}
|
||||||
|
val versionText = if (isRolling) {
|
||||||
|
stringResource(R.string.manage_rolling)
|
||||||
|
} else {
|
||||||
|
patchConfig.lspConfig.VERSION_CODE.toString()
|
||||||
|
}
|
||||||
|
|
||||||
AnywhereDropdown(
|
Text(
|
||||||
expanded = expanded,
|
text = "$patchText $versionText",
|
||||||
onDismissRequest = { expanded = false },
|
color = patchColor,
|
||||||
onClick = { expanded = true },
|
fontWeight = FontWeight.SemiBold,
|
||||||
onLongClick = { expanded = true },
|
fontFamily = FontFamily.Serif,
|
||||||
surface = {
|
style = MaterialTheme.typography.bodySmall
|
||||||
AppItem(
|
)
|
||||||
icon = NPackageManager.getIcon(appInfo),
|
if (canUpdateLoader) {
|
||||||
label = appInfo.label,
|
with(LocalDensity.current) {
|
||||||
packageName = appInfo.app.packageName,
|
val size = MaterialTheme.typography.bodySmall.fontSize * 1.2
|
||||||
additionalContent = {
|
Icon(Icons.Filled.KeyboardCapslock, null, Modifier.size(size.toDp()))
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
}
|
||||||
val patchText = if (patchConfig.useManager) {
|
|
||||||
stringResource(R.string.patch_local)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.patch_integrated)
|
|
||||||
}
|
|
||||||
val patchColor = if (patchConfig.useManager) {
|
|
||||||
MaterialTheme.colorScheme.secondary
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.tertiary
|
|
||||||
}
|
|
||||||
val versionText = if (isRolling) {
|
|
||||||
stringResource(R.string.manage_rolling)
|
|
||||||
} else {
|
|
||||||
patchConfig.lspConfig.VERSION_CODE.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "$patchText $versionText",
|
|
||||||
color = patchColor,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
fontFamily = FontFamily.Serif,
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
if (canUpdateLoader) {
|
|
||||||
with(LocalDensity.current) {
|
|
||||||
val size = MaterialTheme.typography.bodySmall.fontSize * 1.2
|
|
||||||
Icon(Icons.Filled.KeyboardCapslock, null, Modifier.size(size.toDp()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
}
|
) {
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = appInfo.label, color = MaterialTheme.colorScheme.primary) },
|
|
||||||
onClick = {}, enabled = false
|
|
||||||
)
|
|
||||||
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
|
|
||||||
if (canUpdateLoader || BuildConfig.DEBUG) {
|
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.manage_update_loader)) },
|
text = { Text(text = appInfo.label, color = MaterialTheme.colorScheme.primary) },
|
||||||
onClick = {
|
onClick = {}, enabled = false
|
||||||
expanded = false
|
|
||||||
scope.launch {
|
|
||||||
viewModel.dispatch(AppManageViewModel.ViewAction.UpdateLoader(appInfo, patchConfig))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
|
||||||
if (patchConfig.useManager) {
|
if (canUpdateLoader || BuildConfig.DEBUG) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.manage_module_scope)) },
|
text = { Text(stringResource(R.string.manage_update_loader)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
expanded = false
|
expanded = false
|
||||||
scope.launch {
|
scope.launch {
|
||||||
scopeApp = appInfo.app.packageName
|
viewModel.dispatch(AppManageViewModel.ViewAction.UpdateLoader(appInfo, patchConfig))
|
||||||
val activated = ConfigManager.getModulesForApp(scopeApp).map { it.pkgName }.toSet()
|
}
|
||||||
val initialSelected = NPackageManager.appList.mapNotNullTo(ArrayList()) {
|
}
|
||||||
if (activated.contains(it.app.packageName)) it.app.packageName else null
|
)
|
||||||
|
}
|
||||||
|
if (patchConfig.useManager) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.manage_module_scope)) },
|
||||||
|
onClick = {
|
||||||
|
expanded = false
|
||||||
|
scope.launch {
|
||||||
|
scopeApp = appInfo.app.packageName
|
||||||
|
val activated = ConfigManager.getModulesForApp(scopeApp).map { it.pkgName }.toSet()
|
||||||
|
val initialSelected = NPackageManager.appList.mapNotNullTo(ArrayList()) {
|
||||||
|
if (activated.contains(it.app.packageName)) it.app.packageName else null
|
||||||
|
}
|
||||||
|
navigator.navigate(SelectAppsScreenDestination(true, initialSelected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.manage_optimize)) },
|
||||||
|
onClick = {
|
||||||
|
expanded = false
|
||||||
|
scope.launch {
|
||||||
|
if (!ShizukuApi.isPermissionGranted) {
|
||||||
|
snackbarHost.showSnackbar(shizukuUnavailable)
|
||||||
|
} else {
|
||||||
|
viewModel.dispatch(AppManageViewModel.ViewAction.PerformOptimize(appInfo))
|
||||||
}
|
}
|
||||||
navigator.navigate(SelectAppsScreenDestination(true, initialSelected))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
val uninstallSuccessfully = stringResource(R.string.manage_uninstall_successfully)
|
||||||
DropdownMenuItem(
|
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
text = { Text(stringResource(R.string.manage_optimize)) },
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
onClick = {
|
scope.launch {
|
||||||
expanded = false
|
snackbarHost.showSnackbar(uninstallSuccessfully)
|
||||||
scope.launch {
|
viewModel.dispatch(AppManageViewModel.ViewAction.Refresh)
|
||||||
if (!ShizukuApi.isPermissionGranted) {
|
|
||||||
snackbarHost.showSnackbar(shizukuUnavailable)
|
|
||||||
} else {
|
|
||||||
viewModel.dispatch(AppManageViewModel.ViewAction.PerformOptimize(appInfo))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
DropdownMenuItem(
|
||||||
val uninstallSuccessfully = stringResource(R.string.manage_uninstall_successfully)
|
text = { Text(stringResource(R.string.uninstall)) },
|
||||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
onClick = {
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
expanded = false
|
||||||
scope.launch {
|
val intent = Intent(Intent.ACTION_DELETE).apply {
|
||||||
snackbarHost.showSnackbar(uninstallSuccessfully)
|
data = Uri.parse("package:${appInfo.app.packageName}")
|
||||||
|
putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
||||||
|
}
|
||||||
|
launcher.launch(intent)
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.uninstall)) },
|
|
||||||
onClick = {
|
|
||||||
expanded = false
|
|
||||||
val intent = Intent(Intent.ACTION_DELETE).apply {
|
|
||||||
data = Uri.parse("package:${appInfo.app.packageName}")
|
|
||||||
putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
|
||||||
}
|
|
||||||
launcher.launch(intent)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import org.lsposed.npatch.ui.component.AnywhereDropdown
|
import org.lsposed.npatch.ui.component.AnywhereDropdown
|
||||||
import org.lsposed.npatch.ui.component.AppItem
|
import org.lsposed.npatch.ui.component.AppItem
|
||||||
import org.lsposed.npatch.R
|
import org.lsposed.npatch.R
|
||||||
|
|
@ -32,75 +34,82 @@ import nkbe.util.NPackageManager
|
||||||
fun ModuleManageBody() {
|
fun ModuleManageBody() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val viewModel = viewModel<ModuleManageViewModel>()
|
val viewModel = viewModel<ModuleManageViewModel>()
|
||||||
if (viewModel.appList.isEmpty()) {
|
// 下拉刷新
|
||||||
Box(Modifier.fillMaxSize()) {
|
SwipeRefresh(
|
||||||
Text(
|
state = rememberSwipeRefreshState(viewModel.isRefreshing),
|
||||||
modifier = Modifier.align(Alignment.Center),
|
onRefresh = { viewModel.refresh() },
|
||||||
text = run {
|
modifier = Modifier.fillMaxSize()
|
||||||
if (NPackageManager.appList.isEmpty()) stringResource(R.string.manage_loading)
|
) {
|
||||||
else stringResource(R.string.manage_no_modules)
|
if (viewModel.appList.isEmpty()) {
|
||||||
},
|
Box(Modifier.fillMaxSize()) {
|
||||||
fontFamily = FontFamily.Serif,
|
Text(
|
||||||
style = MaterialTheme.typography.headlineSmall
|
modifier = Modifier.align(Alignment.Center),
|
||||||
)
|
text = run {
|
||||||
}
|
if (NPackageManager.appList.isEmpty()) stringResource(R.string.manage_loading)
|
||||||
} else {
|
else stringResource(R.string.manage_no_modules)
|
||||||
LazyColumn(Modifier.fillMaxHeight()) {
|
},
|
||||||
items(
|
fontFamily = FontFamily.Serif,
|
||||||
items = viewModel.appList,
|
style = MaterialTheme.typography.headlineSmall
|
||||||
key = { it.first.app.packageName }
|
)
|
||||||
) {
|
}
|
||||||
var expanded by remember { mutableStateOf(false) }
|
} else {
|
||||||
val settingsIntent = remember { NPackageManager.getSettingsIntent(it.first.app.packageName) }
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
AnywhereDropdown(
|
items(
|
||||||
expanded = expanded,
|
items = viewModel.appList,
|
||||||
onDismissRequest = { expanded = false },
|
key = { it.first.app.packageName }
|
||||||
onClick = { settingsIntent?.let { context.startActivity(it) } },
|
) {
|
||||||
onLongClick = { expanded = true },
|
var expanded by remember { mutableStateOf(false) }
|
||||||
surface = {
|
val settingsIntent = remember { NPackageManager.getSettingsIntent(it.first.app.packageName) }
|
||||||
AppItem(
|
AnywhereDropdown(
|
||||||
icon = NPackageManager.getIcon(it.first),
|
expanded = expanded,
|
||||||
label = it.first.label,
|
onDismissRequest = { expanded = false },
|
||||||
packageName = it.first.app.packageName,
|
onClick = { settingsIntent?.let { context.startActivity(it) } },
|
||||||
additionalContent = {
|
onLongClick = { expanded = true },
|
||||||
Text(
|
surface = {
|
||||||
text = it.second.description,
|
AppItem(
|
||||||
style = MaterialTheme.typography.bodySmall
|
icon = NPackageManager.getIcon(it.first),
|
||||||
)
|
label = it.first.label,
|
||||||
Text(
|
packageName = it.first.app.packageName,
|
||||||
text = buildAnnotatedString {
|
additionalContent = {
|
||||||
append(AnnotatedString("API", SpanStyle(color = MaterialTheme.colorScheme.secondary)))
|
Text(
|
||||||
append(" ")
|
text = it.second.description,
|
||||||
append(it.second.api.toString())
|
style = MaterialTheme.typography.bodySmall
|
||||||
},
|
)
|
||||||
fontWeight = FontWeight.SemiBold,
|
Text(
|
||||||
fontFamily = FontFamily.Serif,
|
text = buildAnnotatedString {
|
||||||
style = MaterialTheme.typography.bodySmall
|
append(AnnotatedString("API", SpanStyle(color = MaterialTheme.colorScheme.secondary)))
|
||||||
|
append(" ")
|
||||||
|
append(it.second.api.toString())
|
||||||
|
},
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontFamily = FontFamily.Serif,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = it.first.label, color = MaterialTheme.colorScheme.primary) },
|
||||||
|
onClick = {}, enabled = false
|
||||||
|
)
|
||||||
|
if (settingsIntent != null) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.manage_module_settings)) },
|
||||||
|
onClick = { context.startActivity(settingsIntent) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.manage_app_info)) },
|
||||||
|
onClick = {
|
||||||
|
val intent = Intent(
|
||||||
|
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||||
|
Uri.fromParts("package", it.first.app.packageName, null)
|
||||||
)
|
)
|
||||||
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = it.first.label, color = MaterialTheme.colorScheme.primary) },
|
|
||||||
onClick = {}, enabled = false
|
|
||||||
)
|
|
||||||
if (settingsIntent != null) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.manage_module_settings)) },
|
|
||||||
onClick = { context.startActivity(settingsIntent) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.manage_app_info)) },
|
|
||||||
onClick = {
|
|
||||||
val intent = Intent(
|
|
||||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
|
||||||
Uri.fromParts("package", it.first.app.packageName, null)
|
|
||||||
)
|
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class AppManageViewModel : ViewModel() {
|
||||||
object ClearUpdateLoaderResult : ViewAction()
|
object ClearUpdateLoaderResult : ViewAction()
|
||||||
data class PerformOptimize(val appInfo: AppInfo) : ViewAction()
|
data class PerformOptimize(val appInfo: AppInfo) : ViewAction()
|
||||||
object ClearOptimizeResult : ViewAction()
|
object ClearOptimizeResult : ViewAction()
|
||||||
object RefreshList : ViewAction()
|
object Refresh : ViewAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手動管理狀態,避免實時響應系統廣播導致列表跳動
|
// 手動管理狀態,避免實時響應系統廣播導致列表跳動
|
||||||
|
|
@ -98,7 +98,16 @@ class AppManageViewModel : ViewModel() {
|
||||||
is ViewAction.ClearUpdateLoaderResult -> updateLoaderState = ProcessingState.Idle
|
is ViewAction.ClearUpdateLoaderResult -> updateLoaderState = ProcessingState.Idle
|
||||||
is ViewAction.PerformOptimize -> performOptimize(action.appInfo)
|
is ViewAction.PerformOptimize -> performOptimize(action.appInfo)
|
||||||
is ViewAction.ClearOptimizeResult -> optimizeState = ProcessingState.Idle
|
is ViewAction.ClearOptimizeResult -> optimizeState = ProcessingState.Idle
|
||||||
is ViewAction.RefreshList -> loadData(silent = false)
|
is ViewAction.Refresh -> {
|
||||||
|
if (!isRefreshing) {
|
||||||
|
isRefreshing = true
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
NPackageManager.fetchAppList()
|
||||||
|
}
|
||||||
|
loadData(silent = true)
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,13 @@ package org.lsposed.npatch.ui.viewmodel.manage
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import nkbe.util.NPackageManager
|
import nkbe.util.NPackageManager
|
||||||
|
|
||||||
class ModuleManageViewModel : ViewModel() {
|
class ModuleManageViewModel : ViewModel() {
|
||||||
|
|
@ -12,6 +18,9 @@ class ModuleManageViewModel : ViewModel() {
|
||||||
private const val TAG = "ModuleManageViewModel"
|
private const val TAG = "ModuleManageViewModel"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isRefreshing by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
class XposedInfo(
|
class XposedInfo(
|
||||||
val api: Int,
|
val api: Int,
|
||||||
val description: String,
|
val description: String,
|
||||||
|
|
@ -30,4 +39,15 @@ class ModuleManageViewModel : ViewModel() {
|
||||||
Log.d(TAG, "Loaded ${it.size} Xposed modules")
|
Log.d(TAG, "Loaded ${it.size} Xposed modules")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
if (isRefreshing) return
|
||||||
|
viewModelScope.launch {
|
||||||
|
isRefreshing = true
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
NPackageManager.fetchAppList()
|
||||||
|
}
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue