feat: 支持手動重載列表

- 當卸載後重新整理程式清單
- 可手動下拉刷新應用列表與模組列表
This commit is contained in:
NkBe 2025-12-07 23:35:59 +08:00
parent 5a90ed436e
commit 5c8509e4f5
No known key found for this signature in database
GPG Key ID: 525137026FF031DF
4 changed files with 274 additions and 226 deletions

View File

@ -30,6 +30,8 @@ import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
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.result.NavResult
import com.ramcosta.composedestinations.result.ResultRecipient
@ -68,21 +70,9 @@ fun AppManageBody(
val snackbarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope()
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 {
var scopeApp by rememberSaveable { mutableStateOf("") }
var afterCheckManager by remember { mutableStateOf<(() -> Unit)?>(null) }
resultRecipient.onNavResult {
if (it is NavResult.Value) {
scope.launch {
@ -133,8 +123,26 @@ fun AppManageBody(
}
}
}
LazyColumn(Modifier.fillMaxHeight()) {
// 下拉刷新
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 }
@ -239,6 +247,7 @@ fun AppManageBody(
if (result.resultCode == Activity.RESULT_OK) {
scope.launch {
snackbarHost.showSnackbar(uninstallSuccessfully)
viewModel.dispatch(AppManageViewModel.ViewAction.Refresh)
}
}
}
@ -257,6 +266,7 @@ fun AppManageBody(
}
}
}
}
}
@Composable

View File

@ -22,6 +22,8 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
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.AppItem
import org.lsposed.npatch.R
@ -32,6 +34,12 @@ import nkbe.util.NPackageManager
fun ModuleManageBody() {
val context = LocalContext.current
val viewModel = viewModel<ModuleManageViewModel>()
// 下拉刷新
SwipeRefresh(
state = rememberSwipeRefreshState(viewModel.isRefreshing),
onRefresh = { viewModel.refresh() },
modifier = Modifier.fillMaxSize()
) {
if (viewModel.appList.isEmpty()) {
Box(Modifier.fillMaxSize()) {
Text(
@ -45,7 +53,7 @@ fun ModuleManageBody() {
)
}
} else {
LazyColumn(Modifier.fillMaxHeight()) {
LazyColumn(Modifier.fillMaxSize()) {
items(
items = viewModel.appList,
key = { it.first.app.packageName }
@ -105,4 +113,5 @@ fun ModuleManageBody() {
}
}
}
}
}

View File

@ -42,7 +42,7 @@ class AppManageViewModel : ViewModel() {
object ClearUpdateLoaderResult : ViewAction()
data class PerformOptimize(val appInfo: AppInfo) : 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.PerformOptimize -> performOptimize(action.appInfo)
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
}
}
}
}
}

View File

@ -3,7 +3,13 @@ package org.lsposed.npatch.ui.viewmodel.manage
import android.util.Log
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nkbe.util.NPackageManager
class ModuleManageViewModel : ViewModel() {
@ -12,6 +18,9 @@ class ModuleManageViewModel : ViewModel() {
private const val TAG = "ModuleManageViewModel"
}
var isRefreshing by mutableStateOf(false)
private set
class XposedInfo(
val api: Int,
val description: String,
@ -30,4 +39,15 @@ class ModuleManageViewModel : ViewModel() {
Log.d(TAG, "Loaded ${it.size} Xposed modules")
}
}
fun refresh() {
if (isRefreshing) return
viewModelScope.launch {
isRefreshing = true
withContext(Dispatchers.IO) {
NPackageManager.fetchAppList()
}
isRefreshing = false
}
}
}