Skip to content

Commit d964a5a

Browse files
committed
feat: Add status filter (All/Running/Stopped/Frozen/Restricted) for app list
1 parent 2ea73b2 commit d964a5a

File tree

4 files changed

+112
-37
lines changed

4 files changed

+112
-37
lines changed

app/src/main/java/com/appcontrolx/ui/AppListFragment.kt

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,16 @@ class AppListFragment : Fragment() {
4949
private var showSystemApps = false
5050
private var executionMode: ExecutionMode = ExecutionMode.None
5151
private var currentSearchQuery: String = ""
52+
private var currentStatusFilter: StatusFilter = StatusFilter.ALL
5253

5354
// App cache - persists until package change detected
5455
private var cachedUserApps: List<AppInfo>? = null
5556
private var cachedSystemApps: List<AppInfo>? = null
5657

58+
enum class StatusFilter {
59+
ALL, RUNNING, STOPPED, FROZEN, RESTRICTED
60+
}
61+
5762
// Package change receiver
5863
private val packageReceiver = object : BroadcastReceiver() {
5964
override fun onReceive(context: Context?, intent: Intent?) {
@@ -89,6 +94,7 @@ class AppListFragment : Fragment() {
8994
setupSwipeRefresh()
9095
setupSearch()
9196
setupChips()
97+
setupStatusFilter()
9298
setupSelectionBar()
9399
setupSelectAll()
94100
registerPackageReceiver()
@@ -119,11 +125,21 @@ class AppListFragment : Fragment() {
119125
val cachedApps = if (showSystemApps) cachedSystemApps else cachedUserApps
120126
if (cachedApps == null) return
121127

122-
val filtered = if (currentSearchQuery.isBlank()) {
123-
cachedApps
124-
} else {
128+
var filtered = cachedApps
129+
130+
// Apply status filter
131+
filtered = when (currentStatusFilter) {
132+
StatusFilter.ALL -> filtered
133+
StatusFilter.RUNNING -> filtered.filter { it.isRunning }
134+
StatusFilter.STOPPED -> filtered.filter { it.isStopped }
135+
StatusFilter.FROZEN -> filtered.filter { !it.isEnabled }
136+
StatusFilter.RESTRICTED -> filtered.filter { it.isBackgroundRestricted }
137+
}
138+
139+
// Apply search filter
140+
if (currentSearchQuery.isNotBlank()) {
125141
val query = currentSearchQuery.lowercase()
126-
cachedApps.filter { app ->
142+
filtered = filtered.filter { app ->
127143
app.appName.lowercase().contains(query) ||
128144
app.packageName.lowercase().contains(query)
129145
}
@@ -195,19 +211,50 @@ class AppListFragment : Fragment() {
195211
b.chipUserApps.isChecked = true
196212

197213
b.chipUserApps.setOnClickListener {
198-
showSystemApps = false
199-
b.chipUserApps.isChecked = true
200-
b.chipSystemApps.isChecked = false
201-
adapter.deselectAll()
202-
loadApps()
214+
if (showSystemApps) {
215+
showSystemApps = false
216+
b.chipUserApps.isChecked = true
217+
b.chipSystemApps.isChecked = false
218+
adapter.deselectAll()
219+
loadApps()
220+
}
203221
}
204222

205223
b.chipSystemApps.setOnClickListener {
206-
showSystemApps = true
207-
b.chipSystemApps.isChecked = true
208-
b.chipUserApps.isChecked = false
209-
adapter.deselectAll()
210-
loadApps()
224+
if (!showSystemApps) {
225+
showSystemApps = true
226+
b.chipSystemApps.isChecked = true
227+
b.chipUserApps.isChecked = false
228+
adapter.deselectAll()
229+
loadApps()
230+
}
231+
}
232+
}
233+
234+
private fun setupStatusFilter() {
235+
val b = binding ?: return
236+
237+
b.chipStatusFilter.setOnClickListener {
238+
val filterNames = arrayOf(
239+
getString(R.string.filter_all),
240+
getString(R.string.filter_running),
241+
getString(R.string.filter_stopped),
242+
getString(R.string.filter_frozen),
243+
getString(R.string.filter_restricted)
244+
)
245+
val filters = StatusFilter.values()
246+
val currentIndex = filters.indexOf(currentStatusFilter)
247+
248+
MaterialAlertDialogBuilder(requireContext())
249+
.setTitle(R.string.filter_status_title)
250+
.setSingleChoiceItems(filterNames, currentIndex) { dialog, which ->
251+
currentStatusFilter = filters[which]
252+
b.chipStatusFilter.text = filterNames[which]
253+
filterApps()
254+
dialog.dismiss()
255+
}
256+
.setNegativeButton(android.R.string.cancel, null)
257+
.show()
211258
}
212259
}
213260

app/src/main/res/layout/fragment_app_list.xml

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -126,41 +126,53 @@
126126
android:layout_height="wrap_content"
127127
android:orientation="horizontal"
128128
android:gravity="center_vertical"
129-
android:paddingHorizontal="20dp"
129+
android:paddingHorizontal="16dp"
130130
android:paddingVertical="8dp"
131131
android:background="?attr/colorSurface">
132132

133-
<com.google.android.material.chip.ChipGroup
134-
android:layout_width="0dp"
133+
<!-- App Type Chips -->
134+
<com.google.android.material.chip.Chip
135+
android:id="@+id/chipUserApps"
136+
android:layout_width="wrap_content"
135137
android:layout_height="wrap_content"
136-
android:layout_weight="1"
137-
app:singleSelection="true"
138-
app:singleLine="true"
139-
app:chipSpacingHorizontal="8dp">
138+
android:text="@string/chip_user_apps"
139+
android:checkable="true"
140+
style="@style/Widget.Material3.Chip.Filter" />
140141

141-
<com.google.android.material.chip.Chip
142-
android:id="@+id/chipUserApps"
143-
android:layout_width="wrap_content"
144-
android:layout_height="wrap_content"
145-
android:text="@string/chip_user_apps"
146-
android:checkable="true"
147-
style="@style/Widget.Material3.Chip.Filter" />
142+
<com.google.android.material.chip.Chip
143+
android:id="@+id/chipSystemApps"
144+
android:layout_width="wrap_content"
145+
android:layout_height="wrap_content"
146+
android:layout_marginStart="4dp"
147+
android:text="@string/chip_system_apps"
148+
android:checkable="true"
149+
style="@style/Widget.Material3.Chip.Filter" />
148150

149-
<com.google.android.material.chip.Chip
150-
android:id="@+id/chipSystemApps"
151-
android:layout_width="wrap_content"
152-
android:layout_height="wrap_content"
153-
android:text="@string/chip_system_apps"
154-
android:checkable="true"
155-
style="@style/Widget.Material3.Chip.Filter" />
156-
</com.google.android.material.chip.ChipGroup>
151+
<!-- Status Filter Chip -->
152+
<com.google.android.material.chip.Chip
153+
android:id="@+id/chipStatusFilter"
154+
android:layout_width="wrap_content"
155+
android:layout_height="wrap_content"
156+
android:layout_marginStart="4dp"
157+
android:text="@string/filter_all"
158+
app:chipIcon="@drawable/ic_expand_more"
159+
app:chipIconVisible="true"
160+
app:closeIconVisible="false"
161+
style="@style/Widget.Material3.Chip.Assist" />
162+
163+
<View
164+
android:layout_width="0dp"
165+
android:layout_height="0dp"
166+
android:layout_weight="1" />
157167

158168
<com.google.android.material.button.MaterialButton
159169
android:id="@+id/btnSelectAll"
160170
android:layout_width="wrap_content"
161171
android:layout_height="36dp"
162172
android:text="@string/btn_select_all"
163-
android:textSize="12sp"
173+
android:textSize="11sp"
174+
android:minWidth="0dp"
175+
android:paddingHorizontal="8dp"
164176
style="@style/Widget.Material3.Button.TextButton" />
165177
</LinearLayout>
166178

app/src/main/res/values-id/strings.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@
131131
<string name="badge_stopped">BERHENTI</string>
132132
<string name="badge_restricted">DIBATASI</string>
133133

134+
<!-- Status Filter -->
135+
<string name="filter_all">Semua</string>
136+
<string name="filter_running">Berjalan</string>
137+
<string name="filter_stopped">Berhenti</string>
138+
<string name="filter_frozen">Dibekukan</string>
139+
<string name="filter_restricted">Dibatasi</string>
140+
<string name="filter_status_title">Filter Status</string>
141+
134142
<!-- Log -->
135143
<string name="log_status_success">Berhasil</string>
136144
<string name="log_status_failed">Gagal</string>

app/src/main/res/values/strings.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,14 @@
296296
<string name="badge_restricted">BG RESTRICTED</string>
297297
<string name="badge_exported">EXPORTED</string>
298298

299+
<!-- Status Filter -->
300+
<string name="filter_all">All</string>
301+
<string name="filter_running">Running</string>
302+
<string name="filter_stopped">Stopped</string>
303+
<string name="filter_frozen">Frozen</string>
304+
<string name="filter_restricted">Restricted</string>
305+
<string name="filter_status_title">Filter by Status</string>
306+
299307
<!-- Errors -->
300308
<string name="error_root_not_available">Root access not available. Please grant root permission.</string>
301309
<string name="error_shizuku_not_available">Shizuku not available. Please start Shizuku app first.</string>

0 commit comments

Comments
 (0)