AppListDialogFragment: support both inclusion and exclusion
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
@@ -5,14 +5,17 @@
|
||||
package com.wireguard.android.fragment
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.databinding.Observable
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.BR
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.AppListDialogFragmentBinding
|
||||
import com.wireguard.android.databinding.ObservableKeyedArrayList
|
||||
@@ -21,7 +24,10 @@ import com.wireguard.android.util.ErrorMessages
|
||||
|
||||
class AppListDialogFragment : DialogFragment() {
|
||||
private val appData: ObservableKeyedArrayList<String, ApplicationData> = ObservableKeyedArrayList()
|
||||
private var currentlyExcludedApps = emptyList<String>()
|
||||
private var currentlySelectedApps = emptyList<String>()
|
||||
private var initiallyExcluded: Boolean = false
|
||||
private var button: Button? = null
|
||||
private var tabs: TabLayout? = null
|
||||
|
||||
private fun loadData() {
|
||||
val activity = activity ?: return
|
||||
@@ -33,7 +39,14 @@ class AppListDialogFragment : DialogFragment() {
|
||||
val applicationData: MutableList<ApplicationData> = ArrayList()
|
||||
resolveInfos.forEach {
|
||||
val packageName = it.activityInfo.packageName
|
||||
applicationData.add(ApplicationData(it.loadIcon(pm), it.loadLabel(pm).toString(), packageName, currentlyExcludedApps.contains(packageName)))
|
||||
val appData = ApplicationData(it.loadIcon(pm), it.loadLabel(pm).toString(), packageName, currentlySelectedApps.contains(packageName))
|
||||
applicationData.add(appData)
|
||||
appData.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
|
||||
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
|
||||
if (propertyId == BR.selected)
|
||||
setButtonText()
|
||||
}
|
||||
})
|
||||
}
|
||||
applicationData.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
applicationData
|
||||
@@ -52,17 +65,34 @@ class AppListDialogFragment : DialogFragment() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val excludedApps = requireArguments().getStringArrayList(KEY_EXCLUDED_APPS)
|
||||
currentlyExcludedApps = (excludedApps ?: emptyList())
|
||||
currentlySelectedApps = (arguments?.getStringArrayList(KEY_SELECTED_APPS) ?: emptyList())
|
||||
initiallyExcluded = arguments?.getBoolean(KEY_IS_EXCLUDED) ?: true
|
||||
}
|
||||
|
||||
private fun setButtonText() {
|
||||
val numSelected = appData.count { it.isSelected }
|
||||
button?.text = if (numSelected == 0)
|
||||
getString(R.string.use_all_applications)
|
||||
else when (tabs?.selectedTabPosition) {
|
||||
0 -> resources.getQuantityString(R.plurals.exclude_n_applications, numSelected, numSelected)
|
||||
1 -> resources.getQuantityString(R.plurals.include_n_applications, numSelected, numSelected)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val alertDialogBuilder = AlertDialog.Builder(requireActivity())
|
||||
alertDialogBuilder.setTitle(R.string.excluded_applications)
|
||||
val binding = AppListDialogFragmentBinding.inflate(requireActivity().layoutInflater, null, false)
|
||||
binding.executePendingBindings()
|
||||
alertDialogBuilder.setView(binding.root)
|
||||
alertDialogBuilder.setPositiveButton(R.string.set_exclusions) { _, _ -> setExclusionsAndDismiss() }
|
||||
tabs = binding.tabs
|
||||
tabs!!.selectTab(binding.tabs.getTabAt(if (initiallyExcluded) 0 else 1))
|
||||
tabs!!.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) = Unit
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) = setButtonText()
|
||||
})
|
||||
alertDialogBuilder.setPositiveButton(" ") { _, _ -> setSelectionAndDismiss() }
|
||||
alertDialogBuilder.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
alertDialogBuilder.setNeutralButton(R.string.toggle_all) { _, _ -> }
|
||||
binding.fragment = this
|
||||
@@ -70,39 +100,40 @@ class AppListDialogFragment : DialogFragment() {
|
||||
loadData()
|
||||
val dialog = alertDialogBuilder.create()
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener {
|
||||
val selectedItems = appData
|
||||
.filter { it.isExcludedFromTunnel }
|
||||
|
||||
val excludeAll = selectedItems.isEmpty()
|
||||
button = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
setButtonText()
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { _ ->
|
||||
val selectAll = appData.none { it.isSelected }
|
||||
appData.forEach {
|
||||
it.isExcludedFromTunnel = excludeAll
|
||||
it.isSelected = selectAll
|
||||
}
|
||||
}
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
private fun setExclusionsAndDismiss() {
|
||||
val excludedApps: MutableList<String> = ArrayList()
|
||||
private fun setSelectionAndDismiss() {
|
||||
val selectedApps: MutableList<String> = ArrayList()
|
||||
for (data in appData) {
|
||||
if (data.isExcludedFromTunnel) {
|
||||
excludedApps.add(data.packageName)
|
||||
if (data.isSelected) {
|
||||
selectedApps.add(data.packageName)
|
||||
}
|
||||
}
|
||||
(targetFragment as AppExclusionListener?)!!.onExcludedAppsSelected(excludedApps)
|
||||
(targetFragment as AppSelectionListener?)!!.onSelectedAppsSelected(selectedApps, tabs?.selectedTabPosition == 0)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
interface AppExclusionListener {
|
||||
fun onExcludedAppsSelected(excludedApps: List<String>)
|
||||
interface AppSelectionListener {
|
||||
fun onSelectedAppsSelected(selectedApps: List<String>, isExcluded: Boolean)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_EXCLUDED_APPS = "excludedApps"
|
||||
fun <T> newInstance(excludedApps: ArrayList<String?>?, target: T): AppListDialogFragment where T : Fragment?, T : AppExclusionListener? {
|
||||
private const val KEY_SELECTED_APPS = "selected_apps"
|
||||
private const val KEY_IS_EXCLUDED = "is_excluded"
|
||||
fun <T> newInstance(selectedApps: ArrayList<String?>?, isExcluded: Boolean, target: T): AppListDialogFragment where T : Fragment?, T : AppSelectionListener? {
|
||||
val extras = Bundle()
|
||||
extras.putStringArrayList(KEY_EXCLUDED_APPS, excludedApps)
|
||||
extras.putStringArrayList(KEY_SELECTED_APPS, selectedApps)
|
||||
extras.putBoolean(KEY_IS_EXCLUDED, isExcluded)
|
||||
val fragment = AppListDialogFragment()
|
||||
fragment.setTargetFragment(target, 0)
|
||||
fragment.arguments = extras
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelEditorFragmentBinding
|
||||
import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener
|
||||
import com.wireguard.android.fragment.AppListDialogFragment.AppSelectionListener
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.BiometricAuthenticator
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
@@ -35,7 +35,7 @@ import com.wireguard.config.Config
|
||||
/**
|
||||
* Fragment for editing a WireGuard configuration.
|
||||
*/
|
||||
class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
|
||||
class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
|
||||
private var haveShownKeys = false
|
||||
private var binding: TunnelEditorFragmentBinding? = null
|
||||
private var tunnel: ObservableTunnel? = null
|
||||
@@ -88,11 +88,20 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onExcludedAppsSelected(excludedApps: List<String>) {
|
||||
requireNotNull(binding) { "Tried to set excluded apps while no view was loaded" }
|
||||
binding!!.config!!.`interface`.excludedApplications.apply {
|
||||
clear()
|
||||
addAll(excludedApps)
|
||||
override fun onSelectedAppsSelected(selectedApps: List<String>, isExcluded: Boolean) {
|
||||
requireNotNull(binding) { "Tried to set excluded/included apps while no view was loaded" }
|
||||
if (isExcluded) {
|
||||
binding!!.config!!.`interface`.includedApplications.clear()
|
||||
binding!!.config!!.`interface`.excludedApplications.apply {
|
||||
clear()
|
||||
addAll(selectedApps)
|
||||
}
|
||||
} else {
|
||||
binding!!.config!!.`interface`.excludedApplications.clear()
|
||||
binding!!.config!!.`interface`.includedApplications.apply {
|
||||
clear()
|
||||
addAll(selectedApps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,10 +159,16 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun onRequestSetExcludedApplications(view: View?) {
|
||||
fun onRequestSetExcludedIncludedApplications(view: View?) {
|
||||
if (binding != null) {
|
||||
val excludedApps = ArrayList(binding!!.config!!.`interface`.excludedApplications)
|
||||
val fragment = AppListDialogFragment.newInstance(excludedApps, this)
|
||||
var isExcluded = true
|
||||
var selectedApps = ArrayList(binding!!.config!!.`interface`.excludedApplications)
|
||||
if (selectedApps.isEmpty()) {
|
||||
selectedApps = ArrayList(binding!!.config!!.`interface`.includedApplications)
|
||||
if (selectedApps.isNotEmpty())
|
||||
isExcluded = false
|
||||
}
|
||||
val fragment = AppListDialogFragment.newInstance(selectedApps, isExcluded, this)
|
||||
fragment.show(parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user