@@ -5,82 +5,80 @@ import android.content.pm.ApplicationInfo
55import android.content.pm.PackageInfo
66import android.content.pm.PackageManager
77import android.os.Bundle
8- import android.view.MenuItem
98import android.view.View
10- import android.widget.PopupMenu
9+ import android.view.WindowManager
10+ import android.widget.CheckBox
11+ import android.widget.ImageView
12+ import android.widget.TextView
1113import androidx.activity.OnBackPressedCallback
14+ import androidx.appcompat.app.AlertDialog
1215import androidx.appcompat.app.AppCompatActivity
13- import androidx.appcompat.view.ContextThemeWrapper
1416import androidx.core.widget.doAfterTextChanged
1517import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
1618import kotlinx.coroutines.*
1719import tech.httptoolkit.android.databinding.AppsListBinding
18- import java.util.*
19- import kotlin.collections.ArrayList
2020
2121// Used to both to send and return the current list of selected apps
2222const val UNSELECTED_APPS_EXTRA = " tech.httptoolkit.android.UNSELECTED_APPS_EXTRA"
2323
2424class ApplicationListActivity : AppCompatActivity (), SwipeRefreshLayout.OnRefreshListener,
25- CoroutineScope by MainScope (), PopupMenu . OnMenuItemClickListener , View .OnClickListener {
25+ CoroutineScope by MainScope (), View .OnClickListener {
2626
2727 private lateinit var binding: AppsListBinding
28-
29- private val allApps = ArrayList <PackageInfo >()
30- private val filteredApps = ArrayList <PackageInfo >()
31-
28+ private val allApps = mutableListOf<PackageInfo >()
29+ private val filteredApps = mutableListOf<PackageInfo >()
3230 private lateinit var blockedPackages: MutableSet <String >
33-
3431 private var showSystem = false
3532 private var showEnabledOnly = false
3633 private var textFilter = " "
3734
3835 override fun onCreate (savedInstanceState : Bundle ? ) {
3936 super .onCreate(savedInstanceState)
4037
41- blockedPackages = intent.getStringArrayExtra(UNSELECTED_APPS_EXTRA )!! .toHashSet()
42-
38+ blockedPackages = intent.getStringArrayExtra(UNSELECTED_APPS_EXTRA )? .toHashSet()
39+ ? : mutableSetOf ()
4340 binding = AppsListBinding .inflate(layoutInflater)
4441 setContentView(binding.root)
4542
46- binding.appsListRecyclerView.adapter =
47- ApplicationListAdapter (
43+ setupViews()
44+ setupBackNavigation()
45+ onRefresh()
46+ }
47+
48+ private fun setupViews () {
49+ binding.apply {
50+ appsListRecyclerView.adapter = ApplicationListAdapter (
4851 filteredApps,
4952 ::isAppEnabled,
5053 ::setAppEnabled
5154 )
52- binding. appsListSwipeRefreshLayout.setOnRefreshListener(this )
53- binding. appsListMoreMenu.setOnClickListener(this )
54-
55- binding.appsListFilterEditText.doAfterTextChanged {
56- textFilter = it.toString ()
57- applyFilters()
55+ appsListSwipeRefreshLayout.setOnRefreshListener(this @ApplicationListActivity )
56+ appsListMoreMenu.setOnClickListener(this @ApplicationListActivity )
57+ appsListFilterEditText.doAfterTextChanged {
58+ textFilter = it.toString()
59+ applyFilters ()
60+ }
5861 }
62+ }
5963
64+ private fun setupBackNavigation () {
6065 onBackPressedDispatcher.addCallback(this , object : OnBackPressedCallback (true ) {
6166 override fun handleOnBackPressed () {
62- setResult(RESULT_OK , Intent ().putExtra(
63- UNSELECTED_APPS_EXTRA ,
64- blockedPackages.toTypedArray()
65- ))
67+ setResult(
68+ RESULT_OK ,
69+ Intent ().putExtra( UNSELECTED_APPS_EXTRA , blockedPackages.toTypedArray() )
70+ )
6671 finish()
6772 }
6873 })
69-
70- onRefresh()
7174 }
7275
7376 override fun onRefresh () {
74- launch(Dispatchers .Main ) {
75- if (binding.appsListSwipeRefreshLayout.isRefreshing.not ()) {
76- binding.appsListSwipeRefreshLayout.isRefreshing = true
77- }
78-
79- val apps = loadAllApps()
77+ launch {
78+ binding.appsListSwipeRefreshLayout.isRefreshing = true
8079 allApps.clear()
81- allApps.addAll(apps )
80+ allApps.addAll(loadAllApps() )
8281 applyFilters()
83-
8482 binding.appsListSwipeRefreshLayout.isRefreshing = false
8583 }
8684 }
@@ -94,88 +92,95 @@ class ApplicationListActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefres
9492 private fun matchesFilters (app : PackageInfo ): Boolean {
9593 val appInfo = app.applicationInfo ? : return false
9694 val appLabel = AppLabelCache .getAppLabel(packageManager, appInfo)
97- val isSystemApp = appInfo.flags and ApplicationInfo .FLAG_SYSTEM == 1
95+ val isSystemApp = ( appInfo.flags and ApplicationInfo .FLAG_SYSTEM ) != 0
9896
99- return (textFilter.isEmpty() || appLabel.contains(textFilter, true )) && // Filter by name
100- (showSystem || ! isSystemApp) && // Show system apps, if that's active
101- (! showEnabledOnly || isAppEnabled(app)) && // Only show enabled apps, if that's active
102- app.packageName != packageName // Never show ourselves
97+ return (textFilter.isEmpty() || appLabel.contains(textFilter, true )) &&
98+ (showSystem || ! isSystemApp) &&
99+ (! showEnabledOnly || isAppEnabled(app)) &&
100+ app.packageName != packageName
103101 }
104102
105- private fun isAppEnabled (app : PackageInfo ): Boolean {
106- return ! blockedPackages.contains(app.packageName)
107- }
103+ private fun isAppEnabled (app : PackageInfo ): Boolean = app.packageName !in blockedPackages
108104
109105 private fun setAppEnabled (app : PackageInfo , isEnabled : Boolean ) {
110- val wasChanged = if (! isEnabled) {
111- blockedPackages.add(app.packageName)
112- } else {
113- blockedPackages.remove(app.packageName)
114- }
106+ val modified = if (isEnabled) blockedPackages.remove(app.packageName)
107+ else blockedPackages.add(app.packageName)
115108
116- if (wasChanged && showEnabledOnly) applyFilters()
109+ if (modified && showEnabledOnly) applyFilters()
117110 }
118111
119112 private suspend fun loadAllApps (): List <PackageInfo > =
120113 withContext(Dispatchers .IO ) {
121- return @withContext packageManager.getInstalledPackages(PackageManager .GET_META_DATA )
122- .filter { pkg ->
123- pkg.applicationInfo != null
124- }.sortedBy { pkg ->
125- AppLabelCache .getAppLabel(packageManager, pkg.applicationInfo!! ).toUpperCase(
126- Locale .getDefault()
127- )
128- }
114+ packageManager.getInstalledPackages(PackageManager .GET_META_DATA )
115+ .filter { it.applicationInfo != null }
116+ .sortedBy { AppLabelCache .getAppLabel(packageManager, it.applicationInfo!! ).lowercase() }
129117 }
130118
131- override fun onMenuItemClick (item : MenuItem ? ): Boolean {
132- return when (item?.itemId) {
133- R .id.action_show_system -> {
134- showSystem = showSystem.not ()
135- applyFilters()
136- true
137- }
138- R .id.action_show_enabled -> {
139- showEnabledOnly = showEnabledOnly.not ()
119+ private fun toggleAllApps () {
120+ if (blockedPackages.isEmpty()) {
121+ blockedPackages.addAll(allApps.map { it.packageName })
122+ } else {
123+ blockedPackages.clear()
124+ }
125+ binding.appsListRecyclerView.adapter?.notifyDataSetChanged()
126+ }
127+
128+ private fun showOptionsDialog () {
129+ val dialogView = layoutInflater.inflate(R .layout.dialog_options, null )
130+ val dialog = AlertDialog .Builder (this )
131+ .setView(dialogView)
132+ .create()
133+
134+ // Set rounded corners for the dialog window
135+ dialog.window?.setBackgroundDrawableResource(android.R .color.transparent)
136+
137+ // Access views
138+ val toggleAll = dialogView.findViewById<TextView >(R .id.option_toggle_all)
139+ val checkboxShowEnabled = dialogView.findViewById<CheckBox >(R .id.checkbox_show_enabled)
140+ val checkboxShowSystem = dialogView.findViewById<CheckBox >(R .id.checkbox_show_system)
141+ val cancelButton = dialogView.findViewById<ImageView >(R .id.close_button)
142+
143+ checkboxShowEnabled.isChecked = showEnabledOnly
144+ checkboxShowSystem.isChecked = showSystem
145+
146+ toggleAll.text = getString(
147+ if (blockedPackages.isEmpty()) R .string.disable_all_apps else R .string.enable_all_apps
148+ )
149+
150+ toggleAll.setOnClickListener {
151+ toggleAllApps()
152+ if (showEnabledOnly) {
140153 applyFilters()
141- true
142- }
143- R .id.action_toggle_all -> {
144- if (blockedPackages.isEmpty()) {
145- // If everything is enabled, disable everything
146- blockedPackages.addAll(allApps.map { app -> app.packageName })
147- } else {
148- // Otherwise, re-enable everything
149- blockedPackages.removeAll(allApps.map { app -> app.packageName })
150- }
151-
152- if (showEnabledOnly) {
153- applyFilters()
154- } else {
155- binding.appsListRecyclerView.adapter?.notifyDataSetChanged()
156- }
157- true
154+ } else {
155+ binding.appsListRecyclerView.adapter?.notifyDataSetChanged()
158156 }
159- else -> false
157+ dialog.dismiss()
160158 }
159+
160+ checkboxShowEnabled.setOnCheckedChangeListener { _, isChecked ->
161+ showEnabledOnly = isChecked
162+ applyFilters()
163+ dialog.dismiss()
164+ }
165+
166+ checkboxShowSystem.setOnCheckedChangeListener { _, isChecked ->
167+ showSystem = isChecked
168+ applyFilters()
169+ dialog.dismiss()
170+ }
171+
172+ cancelButton.setOnClickListener {
173+ dialog.dismiss()
174+ }
175+
176+ // Show the dialog
177+ dialog.show()
161178 }
162179
163- override fun onClick (v : View ? ) {
164- when (v?.id) {
165- R .id.apps_list_more_menu -> {
166- PopupMenu (ContextThemeWrapper (this , R .style.PopupMenu ), binding.appsListMoreMenu).apply {
167- this .inflate(R .menu.menu_app_list)
168- this .menu.findItem(R .id.action_show_system).isChecked = showSystem
169- this .menu.findItem(R .id.action_show_enabled).isChecked = showEnabledOnly
170- this .menu.findItem(R .id.action_toggle_all).title = getString(
171- if (blockedPackages.isEmpty())
172- R .string.disable_all
173- else
174- R .string.enable_all
175- )
176- this .setOnMenuItemClickListener(this @ApplicationListActivity)
177- }.show()
178- }
180+
181+ override fun onClick (view : View ? ) {
182+ if (view?.id == R .id.apps_list_more_menu) {
183+ showOptionsDialog()
179184 }
180185 }
181- }
186+ }
0 commit comments