--- .../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
--- .../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
Please resubmit this with a proper commit subject, commit message, and signed-off-by line.