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.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,21 +70,9 @@ fun AppManageBody(
val snackbarHost = LocalSnackbarHost.current val snackbarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope() 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 scopeApp by rememberSaveable { mutableStateOf("") }
var afterCheckManager by remember { mutableStateOf<(() -> Unit)?>(null) } var afterCheckManager by remember { mutableStateOf<(() -> Unit)?>(null) }
resultRecipient.onNavResult { resultRecipient.onNavResult {
if (it is NavResult.Value) { if (it is NavResult.Value) {
scope.launch { 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(
items = viewModel.appList, items = viewModel.appList,
key = { it.first.app.packageName } key = { it.first.app.packageName }
@ -239,6 +247,7 @@ fun AppManageBody(
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
scope.launch { scope.launch {
snackbarHost.showSnackbar(uninstallSuccessfully) snackbarHost.showSnackbar(uninstallSuccessfully)
viewModel.dispatch(AppManageViewModel.ViewAction.Refresh)
} }
} }
} }
@ -258,6 +267,7 @@ fun AppManageBody(
} }
} }
} }
}
@Composable @Composable
fun AppManageFab(navigator: DestinationsNavigator) { fun AppManageFab(navigator: DestinationsNavigator) {

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.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,6 +34,12 @@ import nkbe.util.NPackageManager
fun ModuleManageBody() { fun ModuleManageBody() {
val context = LocalContext.current val context = LocalContext.current
val viewModel = viewModel<ModuleManageViewModel>() val viewModel = viewModel<ModuleManageViewModel>()
// 下拉刷新
SwipeRefresh(
state = rememberSwipeRefreshState(viewModel.isRefreshing),
onRefresh = { viewModel.refresh() },
modifier = Modifier.fillMaxSize()
) {
if (viewModel.appList.isEmpty()) { if (viewModel.appList.isEmpty()) {
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
Text( Text(
@ -45,7 +53,7 @@ fun ModuleManageBody() {
) )
} }
} else { } else {
LazyColumn(Modifier.fillMaxHeight()) { LazyColumn(Modifier.fillMaxSize()) {
items( items(
items = viewModel.appList, items = viewModel.appList,
key = { it.first.app.packageName } key = { it.first.app.packageName }
@ -106,3 +114,4 @@ fun ModuleManageBody() {
} }
} }
} }
}

View File

@ -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
}
}
} }
} }
} }

View File

@ -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
}
}
} }