Development discussion of WireGuard
 help / color / mirror / Atom feed
From: Stepan Rabotkin <epicstyt@gmail.com>
To: wireguard@lists.zx2c4.com
Cc: Stepan Rabotkin <epicstyt@gmail.com>
Subject: [PATCH] feat: add search to app list
Date: Wed, 15 Jun 2022 19:07:05 +0300	[thread overview]
Message-ID: <20220615160705.898-1-epicstyt@gmail.com> (raw)

---
 .../android/fragment/AppListDialogFragment.kt | 39 +++++++++++++------
 .../res/layout/app_list_dialog_fragment.xml   | 15 +++++++
 ui/src/main/res/values/strings.xml            |  1 +
 3 files changed, 44 insertions(+), 11 deletions(-)

diff --git a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
index 1a40a1c..e9b4cf6 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
@@ -11,6 +11,7 @@ import android.widget.Button
 import android.widget.Toast
 import androidx.appcompat.app.AlertDialog
 import androidx.core.os.bundleOf
+import androidx.core.widget.doAfterTextChanged
 import androidx.databinding.Observable
 import androidx.fragment.app.DialogFragment
 import androidx.fragment.app.setFragmentResult
@@ -28,7 +29,8 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 class AppListDialogFragment : DialogFragment() {
-    private val appData = ObservableKeyedArrayList<String, ApplicationData>()
+    private val appList: MutableList<ApplicationData> = ArrayList()
+    private val appListFiltered = ObservableKeyedArrayList<String, ApplicationData>()
     private var currentlySelectedApps = emptyList<String>()
     private var initiallyExcluded = false
     private var button: Button? = null
@@ -39,14 +41,13 @@ class AppListDialogFragment : DialogFragment() {
         val pm = activity.packageManager
         lifecycleScope.launch(Dispatchers.Default) {
             try {
-                val applicationData: MutableList<ApplicationData> = ArrayList()
                 withContext(Dispatchers.IO) {
                     val packageInfos = pm.getPackagesHoldingPermissions(arrayOf(Manifest.permission.INTERNET), 0)
                     packageInfos.forEach {
                         val packageName = it.packageName
                         val appInfo = it.applicationInfo
                         val appData = ApplicationData(appInfo.loadIcon(pm), appInfo.loadLabel(pm).toString(), packageName, currentlySelectedApps.contains(packageName))
-                        applicationData.add(appData)
+                        appList.add(appData)
                         appData.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
                             override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
                                 if (propertyId == BR.selected)
@@ -55,10 +56,10 @@ class AppListDialogFragment : DialogFragment() {
                         })
                     }
                 }
-                applicationData.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
+                appList.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
                 withContext(Dispatchers.Main.immediate) {
-                    appData.clear()
-                    appData.addAll(applicationData)
+                    appListFiltered.clear()
+                    appListFiltered.addAll(appList)
                 }
             } catch (e: Throwable) {
                 withContext(Dispatchers.Main.immediate) {
@@ -78,7 +79,7 @@ class AppListDialogFragment : DialogFragment() {
     }
 
     private fun setButtonText() {
-        val numSelected = appData.count { it.isSelected }
+        val numSelected = appList.count { it.isSelected }
         button?.text = if (numSelected == 0)
             getString(R.string.use_all_applications)
         else when (tabs?.selectedTabPosition) {
@@ -106,15 +107,19 @@ class AppListDialogFragment : DialogFragment() {
         alertDialogBuilder.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
         alertDialogBuilder.setNeutralButton(R.string.toggle_all) { _, _ -> }
         binding.fragment = this
-        binding.appData = appData
+        binding.appData = appListFiltered
         loadData()
+        binding.appSearchText.doAfterTextChanged {
+            appListFiltered.clear()
+            appListFiltered.addAll(filter(it.toString()))
+        }
         val dialog = alertDialogBuilder.create()
         dialog.setOnShowListener {
             button = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
             setButtonText()
             dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { _ ->
-                val selectAll = appData.none { it.isSelected }
-                appData.forEach {
+                val selectAll = appList.none { it.isSelected }
+                appList.forEach {
                     it.isSelected = selectAll
                 }
             }
@@ -122,9 +127,21 @@ class AppListDialogFragment : DialogFragment() {
         return dialog
     }
 
+    private fun filter(s: String): MutableList<ApplicationData> {
+        val resultData: MutableList<ApplicationData> = ArrayList()
+
+        for (app in appList) {
+            if (app.name.lowercase().contains(s)) {
+                resultData.add(app)
+            }
+        }
+
+        return resultData
+    }
+
     private fun setSelectionAndDismiss() {
         val selectedApps: MutableList<String> = ArrayList()
-        for (data in appData) {
+        for (data in appList) {
             if (data.isSelected) {
                 selectedApps.add(data.packageName)
             }
diff --git a/ui/src/main/res/layout/app_list_dialog_fragment.xml b/ui/src/main/res/layout/app_list_dialog_fragment.xml
index 4503de1..572996d 100644
--- a/ui/src/main/res/layout/app_list_dialog_fragment.xml
+++ b/ui/src/main/res/layout/app_list_dialog_fragment.xml
@@ -40,6 +40,21 @@
                 android:text="@string/include_in_tunnel" />
         </com.google.android.material.tabs.TabLayout>
 
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/app_search_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:padding="10dp">
+
+            <com.google.android.material.textfield.TextInputEditText
+                android:id="@+id/app_search_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/search"
+                android:imeOptions="actionDone" />
+
+        </com.google.android.material.textfield.TextInputLayout>
+
         <FrameLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent"
diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml
index 6c09019..80967ad 100644
--- a/ui/src/main/res/values/strings.xml
+++ b/ui/src/main/res/values/strings.xml
@@ -237,4 +237,5 @@
     <string name="biometric_prompt_private_key_title">Authenticate to view private key</string>
     <string name="biometric_auth_error">Authentication failure</string>
     <string name="biometric_auth_error_reason">Authentication failure: %s</string>
+    <string name="search">Search</string>
 </resources>
-- 
2.32.0.windows.2


             reply	other threads:[~2022-06-17 11:43 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-15 16:07 Stepan Rabotkin [this message]
2022-06-17 13:52 ` Jason A. Donenfeld
  -- strict thread matches above, loose matches on Subject: below --
2022-06-14 13:52 Stepan Rabotkin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220615160705.898-1-epicstyt@gmail.com \
    --to=epicstyt@gmail.com \
    --cc=wireguard@lists.zx2c4.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).