Development discussion of WireGuard
 help / color / mirror / Atom feed
* [PATCH] feat: add search to app list
@ 2022-06-14 13:52 Stepan Rabotkin
  0 siblings, 0 replies; 3+ messages in thread
From: Stepan Rabotkin @ 2022-06-14 13:52 UTC (permalink / raw)
  To: wireguard; +Cc: Stepan Rabotkin

---
 .../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


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] feat: add search to app list
  2022-06-15 16:07 Stepan Rabotkin
@ 2022-06-17 13:52 ` Jason A. Donenfeld
  0 siblings, 0 replies; 3+ messages in thread
From: Jason A. Donenfeld @ 2022-06-17 13:52 UTC (permalink / raw)
  To: Stepan Rabotkin; +Cc: WireGuard mailing list

Please resubmit this with a proper commit subject, commit message, and
signed-off-by line.

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH] feat: add search to app list
@ 2022-06-15 16:07 Stepan Rabotkin
  2022-06-17 13:52 ` Jason A. Donenfeld
  0 siblings, 1 reply; 3+ messages in thread
From: Stepan Rabotkin @ 2022-06-15 16:07 UTC (permalink / raw)
  To: wireguard; +Cc: Stepan Rabotkin

---
 .../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


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2022-06-17 13:52 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-06-14 13:52 [PATCH] feat: add search to app list Stepan Rabotkin
2022-06-15 16:07 Stepan Rabotkin
2022-06-17 13:52 ` Jason A. Donenfeld

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).