@@ -20,6 +20,8 @@ import androidx.compose.animation.core.tween
2020import androidx.compose.foundation.Image
2121import androidx.compose.foundation.background
2222import androidx.compose.foundation.clickable
23+ import androidx.compose.foundation.horizontalScroll
24+ import androidx.compose.foundation.rememberScrollState
2325import androidx.compose.foundation.gestures.FlingBehavior
2426import androidx.compose.foundation.gestures.ScrollableDefaults
2527import androidx.compose.foundation.isSystemInDarkTheme
@@ -111,6 +113,13 @@ data class ConfirmAction(
111113 val appName : String
112114)
113115
116+ data class AppFilters (
117+ val systemOnly : Boolean = false ,
118+ val userOnly : Boolean = false ,
119+ val disabledOnly : Boolean = false ,
120+ val uninstalledOnly : Boolean = false ,
121+ )
122+
114123private fun SafetyLevel.badgeColorScheme (): Pair <Color , Color > =
115124 when (this ) {
116125 SafetyLevel .SAFE -> Color (0xFF1B5E20 ) to Color (0xFFE8F5E9 )
@@ -142,6 +151,7 @@ fun DebloaterScreen(snackbarHostState: SnackbarHostState) {
142151 var allAppData by remember { mutableStateOf<List <AppData >>(emptyList()) }
143152 var query by rememberSaveable { mutableStateOf(" " ) }
144153 var active by rememberSaveable { mutableStateOf(false ) }
154+ var filters by rememberSaveable { mutableStateOf(AppFilters ()) }
145155 var isRefreshing by remember { mutableStateOf(false ) }
146156 var confirmAction by remember { mutableStateOf<ConfirmAction ?>(null ) }
147157 var riskyOverrideAction by remember { mutableStateOf<ConfirmAction ?>(null ) }
@@ -189,10 +199,23 @@ fun DebloaterScreen(snackbarHostState: SnackbarHostState) {
189199
190200 val filteredAppData by remember {
191201 derivedStateOf {
192- if (query.isBlank()) allAppData
193- else allAppData.filter {
194- it.appName.contains(query, ignoreCase = true ) ||
195- it.packageName.contains(query, ignoreCase = true )
202+ val trimmedQuery = query.trim()
203+
204+ allAppData.filter { appData ->
205+ val matchesQuery = trimmedQuery.isBlank() ||
206+ appData.appName.contains(trimmedQuery, ignoreCase = true ) ||
207+ appData.packageName.contains(trimmedQuery, ignoreCase = true )
208+
209+ val matchesSystemUser = when {
210+ filters.systemOnly -> appData.isSystem
211+ filters.userOnly -> ! appData.isSystem
212+ else -> true
213+ }
214+
215+ val matchesDisabled = ! filters.disabledOnly || appData.isDisabled
216+ val matchesUninstalled = ! filters.uninstalledOnly || ! appData.isInstalled
217+
218+ matchesQuery && matchesSystemUser && matchesDisabled && matchesUninstalled
196219 }
197220 }
198221 }
@@ -212,6 +235,8 @@ fun DebloaterScreen(snackbarHostState: SnackbarHostState) {
212235 onActiveChange = { active = it },
213236 suggestions = filteredAppData.take(10 ),
214237 onSuggestionClick = { query = it; active = false },
238+ filters = filters,
239+ onFiltersChange = { filters = it },
215240 suggestionListState = suggestionListState,
216241 smoothFlingBehavior = smoothFlingBehavior,
217242 currentScreen = currentScreen,
@@ -633,6 +658,8 @@ fun DebloaterTopBar(
633658 onActiveChange : (Boolean ) -> Unit ,
634659 suggestions : List <AppData >,
635660 onSuggestionClick : (String ) -> Unit ,
661+ filters : AppFilters ,
662+ onFiltersChange : (AppFilters ) -> Unit ,
636663 suggestionListState : LazyListState ,
637664 smoothFlingBehavior : FlingBehavior ,
638665 currentScreen : String ,
@@ -669,50 +696,91 @@ fun DebloaterTopBar(
669696 )
670697
671698 if (currentScreen == " apps" ) {
672- SearchBar (
673- query = query,
674- onQueryChange = onQueryChange,
675- onSearch = { onActiveChange(false ) },
676- active = active,
677- onActiveChange = onActiveChange,
678- placeholder = { Text (" Search apps" ) },
679- leadingIcon = {
680- if (active) {
681- IconButton (onClick = {
682- onQueryChange(" " )
683- onActiveChange(false )
684- }) {
685- Icon (Icons .Default .ArrowBack , null )
699+ Column {
700+ SearchBar (
701+ query = query,
702+ onQueryChange = onQueryChange,
703+ onSearch = { onActiveChange(false ) },
704+ active = active,
705+ onActiveChange = onActiveChange,
706+ placeholder = { Text (" Search apps" ) },
707+ leadingIcon = {
708+ if (active) {
709+ IconButton (onClick = {
710+ onQueryChange(" " )
711+ onActiveChange(false )
712+ }) {
713+ Icon (Icons .Default .ArrowBack , null )
714+ }
715+ } else {
716+ Icon (Icons .Default .Search , null )
686717 }
687- } else {
688- Icon (Icons .Default .Search , null )
689- }
690- },
691- trailingIcon = {
692- if (query.isNotBlank()) {
693- IconButton (onClick = { onQueryChange(" " ) }) {
694- Icon (Icons .Default .Close , null )
718+ },
719+ trailingIcon = {
720+ if (query.isNotBlank()) {
721+ IconButton (onClick = { onQueryChange(" " ) }) {
722+ Icon (Icons .Default .Close , null )
723+ }
695724 }
696- }
697- },
698- modifier = Modifier
699- .fillMaxWidth()
700- .padding(horizontal = 16 .dp, vertical = 8 .dp)
701- ) {
702- LazyColumn (
703- state = suggestionListState,
704- flingBehavior = smoothFlingBehavior
725+ },
726+ modifier = Modifier
727+ .fillMaxWidth()
728+ .padding(horizontal = 16 .dp, vertical = 8 .dp)
705729 ) {
706- items(suggestions, key = { it.packageName }) { appData ->
707- ListItem (
708- headlineContent = { Text (appData.appName) },
709- supportingContent = { Text (appData.packageName) },
710- modifier = Modifier .clickable {
711- onSuggestionClick(appData.appName)
712- }
713- )
730+ LazyColumn (
731+ state = suggestionListState,
732+ flingBehavior = smoothFlingBehavior
733+ ) {
734+ items(suggestions, key = { it.packageName }) { appData ->
735+ ListItem (
736+ headlineContent = { Text (appData.appName) },
737+ supportingContent = { Text (appData.packageName) },
738+ modifier = Modifier .clickable {
739+ onSuggestionClick(appData.appName)
740+ }
741+ )
742+ }
714743 }
715744 }
745+
746+ Row (
747+ modifier = Modifier
748+ .fillMaxWidth()
749+ .horizontalScroll(rememberScrollState())
750+ .padding(horizontal = 16 .dp, vertical = 4 .dp),
751+ horizontalArrangement = Arrangement .spacedBy(8 .dp)
752+ ) {
753+ FilterChip (
754+ selected = filters.systemOnly,
755+ onClick = {
756+ val enabled = ! filters.systemOnly
757+ onFiltersChange(filters.copy(systemOnly = enabled, userOnly = if (enabled) false else filters.userOnly))
758+ },
759+ label = { Text (" System" ) }
760+ )
761+ FilterChip (
762+ selected = filters.userOnly,
763+ onClick = {
764+ val enabled = ! filters.userOnly
765+ onFiltersChange(filters.copy(userOnly = enabled, systemOnly = if (enabled) false else filters.systemOnly))
766+ },
767+ label = { Text (" User" ) }
768+ )
769+ FilterChip (
770+ selected = filters.disabledOnly,
771+ onClick = {
772+ onFiltersChange(filters.copy(disabledOnly = ! filters.disabledOnly))
773+ },
774+ label = { Text (" Disabled" ) }
775+ )
776+ FilterChip (
777+ selected = filters.uninstalledOnly,
778+ onClick = {
779+ onFiltersChange(filters.copy(uninstalledOnly = ! filters.uninstalledOnly))
780+ },
781+ label = { Text (" Uninstalled" ) }
782+ )
783+ }
716784 }
717785 }
718786 }
0 commit comments