mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-03-13 08:41:57 +08:00
feat: improve AppSelectorScreen caching/perf (#2961)
Co-authored-by: planshim <100317079+planshim@users.noreply.github.com>
This commit is contained in:
@@ -24,9 +24,9 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -68,20 +68,11 @@ fun AppSelectorScreen(
|
||||
uri?.let(vm::handleStorageResult)
|
||||
}
|
||||
|
||||
val suggestedVersions by vm.suggestedAppVersions.collectAsStateWithLifecycle(emptyMap())
|
||||
val suggestedVersions by vm.suggestedAppVersions.collectAsStateWithLifecycle()
|
||||
|
||||
var filterText by rememberSaveable { mutableStateOf("") }
|
||||
var search by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val appList by vm.appList.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
val filteredAppList = remember(appList, filterText) {
|
||||
appList.filter { app ->
|
||||
(vm.loadLabel(app.packageInfo)).contains(
|
||||
filterText,
|
||||
true
|
||||
) or app.packageName.contains(filterText, true)
|
||||
}
|
||||
}
|
||||
val appList by vm.apps.collectAsStateWithLifecycle()
|
||||
val appsListFiltered by vm.filteredApps.collectAsStateWithLifecycle()
|
||||
|
||||
vm.nonSuggestedVersionDialogSubject?.let {
|
||||
NonSuggestedVersionDialog(
|
||||
@@ -90,17 +81,20 @@ fun AppSelectorScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (search)
|
||||
if (search) {
|
||||
val filterText by vm.filterText.collectAsState()
|
||||
|
||||
SearchView(
|
||||
query = filterText,
|
||||
onQueryChange = { filterText = it },
|
||||
onQueryChange = vm::setFilterText,
|
||||
onActiveChange = { search = it },
|
||||
placeholder = { Text(stringResource(R.string.search_apps)) }
|
||||
) {
|
||||
if (appList.isNotEmpty() && filterText.isNotEmpty()) {
|
||||
val appsFiltered = appsListFiltered
|
||||
if (!appsFiltered.isNullOrEmpty() && filterText.isNotEmpty()) {
|
||||
LazyColumnWithScrollbar(modifier = Modifier.fillMaxSize()) {
|
||||
items(
|
||||
items = filteredAppList,
|
||||
items = appsFiltered,
|
||||
key = { it.packageName }
|
||||
) { app ->
|
||||
ListItem(
|
||||
@@ -109,9 +103,9 @@ fun AppSelectorScreen(
|
||||
},
|
||||
leadingContent = {
|
||||
AppIcon(
|
||||
app.packageInfo,
|
||||
null,
|
||||
Modifier.size(36.dp)
|
||||
packageInfo = app.packageInfo,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(36.dp)
|
||||
)
|
||||
},
|
||||
headlineContent = { AppLabel(app.packageInfo) },
|
||||
@@ -152,6 +146,7 @@ fun AppSelectorScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
@@ -198,16 +193,33 @@ fun AppSelectorScreen(
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
if (appList.isNotEmpty()) {
|
||||
val apps = appList
|
||||
if (apps == null) {
|
||||
item(key = "LOADING") {
|
||||
Box(
|
||||
modifier = Modifier.fillParentMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
LoadingIndicator()
|
||||
}
|
||||
}
|
||||
} else if (apps.isNotEmpty()) {
|
||||
items(
|
||||
items = appList,
|
||||
key = { it.packageName }
|
||||
items = apps,
|
||||
key = { "APP-" + it.packageName },
|
||||
contentType = { "APP" },
|
||||
) { app ->
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
onSelect(app.packageName)
|
||||
},
|
||||
leadingContent = { AppIcon(app.packageInfo, null, Modifier.size(36.dp)) },
|
||||
leadingContent = {
|
||||
AppIcon(
|
||||
packageInfo = app.packageInfo,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(36.dp)
|
||||
)
|
||||
},
|
||||
headlineContent = {
|
||||
AppLabel(
|
||||
app.packageInfo,
|
||||
@@ -231,15 +243,6 @@ fun AppSelectorScreen(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillParentMaxSize(), contentAlignment = Alignment.Center
|
||||
) {
|
||||
LoadingIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,12 @@ import app.revanced.manager.util.PM
|
||||
import app.revanced.manager.util.toast
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
@@ -40,16 +44,51 @@ class AppSelectorViewModel(
|
||||
"input.apk"
|
||||
).also(File::delete)
|
||||
}
|
||||
val appList = pm.appList
|
||||
|
||||
val filterTextFlow = MutableStateFlow("")
|
||||
val filterText: StateFlow<String> = filterTextFlow
|
||||
|
||||
val apps = pm.appList.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
val filteredApps = filterText
|
||||
.combine(apps) { filter, apps ->
|
||||
if (apps == null || filter.isBlank()) {
|
||||
apps
|
||||
} else {
|
||||
apps.filter { app ->
|
||||
app.packageName.contains(filter, true) ||
|
||||
loadLabel(app.packageInfo).contains(filter)
|
||||
}
|
||||
}
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(
|
||||
stopTimeoutMillis = 0,
|
||||
replayExpirationMillis = 10_000,
|
||||
),
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
private val storageSelectionChannel = Channel<SelectedApp.Local>()
|
||||
val storageSelectionFlow = storageSelectionChannel.receiveAsFlow()
|
||||
|
||||
val suggestedAppVersions = patchBundleRepository.suggestedVersions.flowOn(Dispatchers.Default)
|
||||
val suggestedAppVersions = patchBundleRepository.suggestedVersions.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = emptyMap(),
|
||||
)
|
||||
|
||||
var nonSuggestedVersionDialogSubject by mutableStateOf<SelectedApp.Local?>(null)
|
||||
private set
|
||||
|
||||
fun setFilterText(filter: String) {
|
||||
filterTextFlow.value = filter
|
||||
}
|
||||
|
||||
fun loadLabel(app: PackageInfo?) = with(pm) { app?.label() ?: "Not installed" }
|
||||
|
||||
fun dismissNonSuggestedVersionDialog() {
|
||||
|
||||
Reference in New Issue
Block a user