From d593107e04227ec87c85772f983ceae009c1352e Mon Sep 17 00:00:00 2001 From: Nullptr <52071314+Dr-TSNG@users.noreply.github.com> Date: Sun, 13 Feb 2022 23:43:52 +0800 Subject: [PATCH] Minimum availability --- manager/build.gradle.kts | 1 + .../lspatch/ui/activity/MainActivity.kt | 2 +- .../lsposed/lspatch/ui/component/AppItem.kt | 25 +++-- .../lspatch/ui/component/settings/Slot.kt | 17 +-- .../org/lsposed/lspatch/ui/page/HomePage.kt | 8 +- .../ui/page/{PatchesPage.kt => ManagePage.kt} | 10 +- .../lsposed/lspatch/ui/page/NewPatchPage.kt | 18 +-- .../org/lsposed/lspatch/ui/page/PageList.kt | 27 ++--- .../lsposed/lspatch/ui/page/SelectAppsPage.kt | 104 +++++++++++++++--- .../org/lsposed/lspatch/ui/util/Navigation.kt | 9 ++ .../ui/viewmodel/SelectAppViewModel.kt | 65 ++++++----- manager/src/main/res/values/strings.xml | 10 +- 12 files changed, 201 insertions(+), 95 deletions(-) rename manager/src/main/java/org/lsposed/lspatch/ui/page/{PatchesPage.kt => ManagePage.kt} (79%) diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts index 31e3dc3..4ac69c2 100644 --- a/manager/build.gradle.kts +++ b/manager/build.gradle.kts @@ -13,6 +13,7 @@ val composeVersion = "1.2.0-alpha03" plugins { id("com.android.application") + id("kotlin-parcelize") kotlin("android") } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt b/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt index de0978d..46af9ad 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/activity/MainActivity.kt @@ -81,7 +81,7 @@ private fun MainNavHost(navController: NavHostController, modifier: Modifier) { @Composable private fun MainNavigationBar(page: PageList, onClick: (PageList) -> Unit) { NavigationBar(tonalElevation = 8.dp) { - arrayOf(PageList.Home, PageList.Patches, PageList.Repo, PageList.Settings).forEach { + arrayOf(PageList.Home, PageList.Manage, PageList.Repo, PageList.Settings).forEach { NavigationBarItem( selected = page == it, onClick = { onClick(it) }, diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/component/AppItem.kt b/manager/src/main/java/org/lsposed/lspatch/ui/component/AppItem.kt index f3b5384..3d02118 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/component/AppItem.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/component/AppItem.kt @@ -5,9 +5,7 @@ import android.graphics.drawable.GradientDrawable import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -18,8 +16,7 @@ import androidx.compose.ui.unit.dp import com.google.accompanist.drawablepainter.rememberDrawablePainter import org.lsposed.lspatch.ui.theme.LSPTheme - -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun AppItem( modifier: Modifier = Modifier, @@ -28,7 +25,8 @@ fun AppItem( packageName: String, additionalInfo: (@Composable () -> Unit)? = null, onClick: () -> Unit, - onLongClick: (() -> Unit)? = null + onLongClick: (() -> Unit)? = null, + checked: Boolean? = null ) { Column( modifier = modifier @@ -39,19 +37,28 @@ fun AppItem( ) .padding(20.dp) ) { - - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp), + verticalAlignment = Alignment.CenterVertically + ) { Icon( painter = rememberDrawablePainter(icon), contentDescription = label, modifier = Modifier.size(32.dp), tint = Color.Unspecified ) - Column(Modifier.padding(start = 20.dp)) { + Column(Modifier.weight(1f)) { Text(text = label, style = MaterialTheme.typography.bodyMedium) Text(text = packageName, style = MaterialTheme.typography.bodySmall) additionalInfo?.invoke() } + if (checked != null) { + Checkbox( + checked = checked, + onCheckedChange = { onClick() }, + modifier = Modifier.padding(start = 20.dp) + ) + } } } } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/component/settings/Slot.kt b/manager/src/main/java/org/lsposed/lspatch/ui/component/settings/Slot.kt index 7b4fea1..619fc54 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/component/settings/Slot.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/component/settings/Slot.kt @@ -23,14 +23,15 @@ fun SettingsSlot( extraContent: (@Composable ColumnScope.() -> Unit)? = null, action: (@Composable RowScope.() -> Unit)?, ) { + val enabledModifier = + if (enabled) Modifier + .alpha(1f) + .clickable(onClick = onClick) + else Modifier.alpha(0.5f) Row( modifier = modifier - .fillMaxWidth() then ( - if (enabled) Modifier - .alpha(1f) - .clickable(onClick = onClick) - else Modifier.alpha(0.5f) - ) + .fillMaxWidth() + .then(enabledModifier) .padding(horizontal = 16.dp, vertical = 8.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically @@ -54,7 +55,9 @@ fun SettingsSlot( Text( text = desc, style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.alpha(0.75f).padding(top = 4.dp) + modifier = Modifier + .alpha(0.75f) + .padding(top = 4.dp) ) } extraContent?.invoke(this) diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt index 3f8e25e..29fb646 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/HomePage.kt @@ -1,6 +1,10 @@ package org.lsposed.lspatch.ui.page +import androidx.compose.material3.SmallTopAppBar +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import org.lsposed.lspatch.R @Composable fun HomePage() { @@ -9,5 +13,7 @@ fun HomePage() { @Composable fun HomeTopBar() { - + SmallTopAppBar( + title = { Text(stringResource(R.string.app_name)) } + ) } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/PatchesPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt similarity index 79% rename from manager/src/main/java/org/lsposed/lspatch/ui/page/PatchesPage.kt rename to manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt index e7e43f4..d49b45d 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/PatchesPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt @@ -12,21 +12,21 @@ import org.lsposed.lspatch.R import org.lsposed.lspatch.ui.util.LocalNavController @Composable -fun PatchesTopBar() { +fun ManageTopBar() { SmallTopAppBar( - title = { Text(PageList.Patches.title) } + title = { Text(PageList.Manage.title) } ) } @Composable -fun PatchesFab() { +fun ManageFab() { val navController = LocalNavController.current FloatingActionButton(onClick = { navController.navigate(PageList.NewPatch.name) }) { - Icon(Icons.Filled.Add, stringResource(R.string.patches_add)) + Icon(Icons.Filled.Add, stringResource(R.string.add)) } } @Composable -fun PatchesPage() { +fun ManagePage() { } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt index b18c4ef..3d31b6c 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt @@ -20,8 +20,8 @@ import androidx.compose.material.icons.outlined.BugReport import androidx.compose.material.icons.outlined.WorkOutline import androidx.compose.material3.* import androidx.compose.runtime.* -import androidx.compose.runtime.livedata.observeAsState 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.draw.clip @@ -40,6 +40,7 @@ import org.lsposed.lspatch.ui.component.settings.SettingsItem import org.lsposed.lspatch.ui.util.LocalNavController import org.lsposed.lspatch.ui.util.isScrolledToEnd import org.lsposed.lspatch.ui.util.lastItemIndex +import org.lsposed.lspatch.ui.util.observeState import org.lsposed.lspatch.ui.viewmodel.AppInfo import org.lsposed.patch.util.Logger @@ -68,8 +69,7 @@ fun NewPatchFab() { fun NewPatchPage() { val viewModel = viewModel() val navController = LocalNavController.current - val patchApp by navController.currentBackStackEntry!!.savedStateHandle - .getLiveData("appInfo").observeAsState() + val patchApp by navController.currentBackStackEntry!!.observeState("appInfo") if (viewModel.patchState == PatchState.SELECTING && patchApp != null) viewModel.patchState = PatchState.CONFIGURING Log.d(TAG, "NewPatchPage: ${viewModel.patchState}") @@ -83,6 +83,7 @@ fun NewPatchPage() { @Composable private fun PatchOptionsPage(patchApp: AppInfo) { val viewModel = viewModel() + val navController = LocalNavController.current var useManager by rememberSaveable { mutableStateOf(true) } var debuggable by rememberSaveable { mutableStateOf(false) } var v1 by rememberSaveable { mutableStateOf(false) } @@ -90,9 +91,12 @@ private fun PatchOptionsPage(patchApp: AppInfo) { var v3 by rememberSaveable { mutableStateOf(true) } val sigBypassLevel by rememberSaveable { mutableStateOf(2) } var overrideVersionCode by rememberSaveable { mutableStateOf(false) } + val embeddedModules = navController.currentBackStackEntry!! + .savedStateHandle.getLiveData>("selected", SnapshotStateList()) if (viewModel.patchState == PatchState.SUBMITTING) LaunchedEffect(patchApp) { val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path + if (useManager) embeddedModules.value?.clear() viewModel.patchOptions = Patcher.Options( apkPaths = arrayOf(patchApp.app.sourceDir), // TODO: Split Apk outputPath = downloadDir, @@ -102,7 +106,7 @@ private fun PatchOptionsPage(patchApp: AppInfo) { useManager = useManager, overrideVersionCode = overrideVersionCode, verbose = true, - embeddedModules = emptyList() // TODO: Embed modules + embeddedModules = embeddedModules.value?.map { it.app.sourceDir } ?: emptyList() // TODO: Split Apk ) viewModel.patchState = PatchState.PATCHING } @@ -145,7 +149,7 @@ private fun PatchOptionsPage(patchApp: AppInfo) { desc = stringResource(R.string.patch_portable_desc), extraContent = { TextButton( - onClick = { /* TODO */ } + onClick = { navController.navigate(PageList.SelectApps.name + "/true") } ) { Text(text = stringResource(R.string.patch_embed_modules), style = MaterialTheme.typography.bodyLarge) } @@ -229,14 +233,14 @@ private fun DoPatchPage(patcherOptions: Patcher.Options) { val patching by remember { derivedStateOf { viewModel.patchState == PatchState.PATCHING } } ShimmerAnimation(enabled = patching) { CompositionLocalProvider( - LocalTextStyle provides MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace) + LocalTextStyle provides MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace) ) { val scrollState = rememberLazyListState() LazyColumn( state = scrollState, modifier = Modifier .fillMaxWidth() - .heightIn(max = 300.dp) + .heightIn(max = 320.dp) .clip(RoundedCornerShape(32.dp)) .background(brush) .padding(horizontal = 24.dp, vertical = 18.dp) diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt index 91f9fcd..ea48a7a 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt @@ -1,14 +1,8 @@ package org.lsposed.lspatch.ui.page import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.GetApp -import androidx.compose.material.icons.filled.Healing -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material.icons.outlined.GetApp -import androidx.compose.material.icons.outlined.Healing -import androidx.compose.material.icons.outlined.Home -import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.* import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource @@ -32,11 +26,11 @@ enum class PageList( topBar = { HomeTopBar() }, body = { HomePage() } ), - Patches( - iconSelected = Icons.Filled.Healing, - iconNotSelected = Icons.Outlined.Healing, - topBar = { PatchesTopBar() }, - fab = { PatchesFab() }, + Manage( + iconSelected = Icons.Filled.Dashboard, + iconNotSelected = Icons.Outlined.Dashboard, + topBar = { ManageTopBar() }, + fab = { ManageFab() }, body = {} ), Repo( @@ -60,17 +54,18 @@ enum class PageList( arguments = listOf( navArgument("multiSelect") { type = NavType.BoolType } ), - topBar = {}, + topBar = { SelectAppsTopBar() }, + fab = { SelectAppsFab() }, body = { SelectAppsPage(this) } ); val title: String @Composable get() = when (this) { Home -> stringResource(R.string.app_name) - Patches -> stringResource(R.string.page_patches) + Manage -> stringResource(R.string.page_manage) Repo -> stringResource(R.string.page_repo) Settings -> stringResource(R.string.page_settings) NewPatch -> stringResource(R.string.page_new_patch) - SelectApps -> stringResource(R.string.page_select_app) + SelectApps -> stringResource(R.string.page_select_apps) } } diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt index 8857109..53a3530 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/SelectAppsPage.kt @@ -1,60 +1,90 @@ package org.lsposed.lspatch.ui.page +import android.content.pm.ApplicationInfo import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Done +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.SmallTopAppBar +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavBackStackEntry import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState +import org.lsposed.lspatch.R import org.lsposed.lspatch.ui.component.AppItem import org.lsposed.lspatch.ui.util.LocalNavController +import org.lsposed.lspatch.ui.util.observeState +import org.lsposed.lspatch.ui.util.setState import org.lsposed.lspatch.ui.viewmodel.AppInfo -import org.lsposed.lspatch.ui.viewmodel.SelectAppViewModel +import org.lsposed.lspatch.ui.viewmodel.SelectAppsViewModel + +@Composable +fun SelectAppsTopBar() { + SmallTopAppBar( + title = { Text(stringResource(R.string.page_select_apps)) } + ) +} + +@Composable +fun SelectAppsFab() { + val navController = LocalNavController.current + val viewModel = viewModel() + val multiSelect = navController.currentBackStackEntry?.arguments?.get("multiSelect") as? Boolean + ?: throw IllegalArgumentException("multiSelect is null") + + if (multiSelect) { + FloatingActionButton(onClick = { viewModel.done = true }) { + Icon(Icons.Outlined.Done, stringResource(R.string.add)) + } + } +} @Composable fun SelectAppsPage(entry: NavBackStackEntry) { val multiSelect = entry.arguments?.get("multiSelect") as? Boolean ?: throw IllegalArgumentException("multiSelect is null") if (multiSelect) { - TODO("MultiSelect") + MultiSelect(filter = { it.app.metaData?.get("xposedminversion") != null }) } else { - SelectSingle() + SingleSelect() } } @Composable -private fun SelectSingle( - filter: (AppInfo) -> Boolean = { true } +private fun SingleSelect( + filter: (AppInfo) -> Boolean = { it.app.flags and ApplicationInfo.FLAG_SYSTEM == 0 } ) { val context = LocalContext.current val navController = LocalNavController.current - val viewModel = viewModel() - val isRefreshing by viewModel.isRefreshing.collectAsState() - if (SelectAppViewModel.appList.isEmpty()) - LaunchedEffect(viewModel) { - viewModel.loadAppList(context) - } + val viewModel = viewModel() + LaunchedEffect(viewModel) { + viewModel.filterAppList(context, false, filter) + } SwipeRefresh( - state = rememberSwipeRefreshState(isRefreshing), - onRefresh = { viewModel.loadAppList(context) }, + state = rememberSwipeRefreshState(viewModel.isRefreshing), + onRefresh = { viewModel.filterAppList(context, true, filter) }, modifier = Modifier.fillMaxSize() ) { LazyColumn { - items(SelectAppViewModel.appList) { + items(viewModel.filteredList) { AppItem( - icon = it.icon, + icon = viewModel.getIcon(it), label = it.label, packageName = it.app.packageName, onClick = { - navController.previousBackStackEntry!!.savedStateHandle.getLiveData("appInfo").value = it + navController.previousBackStackEntry!!.setState("appInfo", it) navController.popBackStack() } ) @@ -62,3 +92,43 @@ private fun SelectSingle( } } } + +@Composable +private fun MultiSelect( + filter: (AppInfo) -> Boolean = { true } +) { + val context = LocalContext.current + val navController = LocalNavController.current + val viewModel = viewModel() + val selected by navController.previousBackStackEntry!!.observeState>("selected") + + LaunchedEffect(viewModel) { + viewModel.filterAppList(context, false, filter) + } + if (viewModel.done) { + LaunchedEffect(viewModel) { + navController.popBackStack() + } + } + + SwipeRefresh( + state = rememberSwipeRefreshState(viewModel.isRefreshing), + onRefresh = { viewModel.filterAppList(context, true, filter) }, + modifier = Modifier.fillMaxSize() + ) { + LazyColumn { + items(viewModel.filteredList) { + val checked = selected!!.contains(it) + AppItem( + icon = viewModel.getIcon(it), + label = it.label, + packageName = it.app.packageName, + onClick = { + if (checked) selected!!.remove(it) else selected!!.add(it) + }, + checked = checked + ) + } + } + } +} diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/util/Navigation.kt b/manager/src/main/java/org/lsposed/lspatch/ui/util/Navigation.kt index 8b811ad..c301a8d 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/util/Navigation.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/util/Navigation.kt @@ -2,6 +2,8 @@ package org.lsposed.lspatch.ui.util import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.livedata.observeAsState +import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController @@ -17,6 +19,13 @@ val NavController.currentRoute: String? val NavController.startRoute: String? get() = graph.findStartDestination().route +fun NavBackStackEntry.setState(key: String, value: T?) { + savedStateHandle.getLiveData(key).value = value +} + +@Composable +fun NavBackStackEntry.observeState(key: String, initial: T? = null) = savedStateHandle.getLiveData(key, initial).observeAsState() + @Composable fun NavController.isAtStartRoute(): Boolean = currentRoute == startRoute diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppViewModel.kt b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppViewModel.kt index e14d787..fa32bb3 100644 --- a/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppViewModel.kt +++ b/manager/src/main/java/org/lsposed/lspatch/ui/viewmodel/SelectAppViewModel.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable +import android.os.Parcelable import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -11,46 +12,54 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.parcelize.Parcelize import org.lsposed.lspatch.TAG -class AppInfo(val app: ApplicationInfo, val icon: Drawable, val label: String) +@Parcelize +class AppInfo(val app: ApplicationInfo, val label: String) : Parcelable -class SelectAppViewModel : ViewModel() { +private var appList = listOf() +private val appIcon = mutableMapOf() + +class SelectAppsViewModel : ViewModel() { init { - Log.d(TAG, "SelectAppViewModel ${toString().substringAfterLast('@')} construct") + Log.d(TAG, "SelectAppsViewModel ${toString().substringAfterLast('@')} construct") } - companion object { - var appList by mutableStateOf(listOf()) - private set - } + var done by mutableStateOf(false) - private val _isRefreshing = MutableStateFlow(false) - val isRefreshing: StateFlow - get() = _isRefreshing.asStateFlow() + var isRefreshing by mutableStateOf(false) + private set - fun loadAppList(context: Context) { - viewModelScope.launch { - Log.d(TAG, "Start refresh apps") - _isRefreshing.emit(true) - val pm = context.packageManager - val collection = mutableListOf() - withContext(Dispatchers.IO) { - pm.getInstalledApplications(PackageManager.GET_META_DATA).forEach { - val icon = pm.getApplicationIcon(it) - val label = pm.getApplicationLabel(it) - collection.add(AppInfo(it, icon, label.toString())) - } + var filteredList by mutableStateOf(listOf()) + private set + + private suspend fun refreshAppList(context: Context) { + Log.d(TAG, "Start refresh apps") + isRefreshing = true + val pm = context.packageManager + val collection = mutableListOf() + withContext(Dispatchers.IO) { + pm.getInstalledApplications(PackageManager.GET_META_DATA).forEach { + val label = pm.getApplicationLabel(it) + appIcon[it.packageName] = pm.getApplicationIcon(it) + collection.add(AppInfo(it, label.toString())) } - appList = collection - _isRefreshing.emit(false) - Log.d(TAG, "Refreshed ${appList.size} apps") + } + appList = collection + isRefreshing = false + Log.d(TAG, "Refreshed ${appList.size} apps") + } + + fun filterAppList(context: Context, refresh: Boolean, filter: (AppInfo) -> Boolean) { + viewModelScope.launch { + if (appList.isEmpty() || refresh) refreshAppList(context) + filteredList = appList.filter(filter) } } + + fun getIcon(appInfo: AppInfo) = appIcon[appInfo.app.packageName]!! } diff --git a/manager/src/main/res/values/strings.xml b/manager/src/main/res/values/strings.xml index 9c4e659..2e07c69 100644 --- a/manager/src/main/res/values/strings.xml +++ b/manager/src/main/res/values/strings.xml @@ -1,13 +1,12 @@ LSPatch + Add Repo Settings - Select App - - Patches - Add + + Manage New Patch @@ -28,4 +27,7 @@ Start Patch Return Install + + + Select Apps