Use new fragment result API
This commit is contained in:
@@ -7,8 +7,10 @@ import android.os.Bundle
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
import androidx.fragment.app.setFragmentResult
|
||||||
|
import androidx.fragment.app.setFragmentResultListener
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,33 +19,44 @@ import kotlinx.android.parcel.Parcelize
|
|||||||
abstract class AlertDialogFragment<Arg : Parcelable, Ret : Parcelable> :
|
abstract class AlertDialogFragment<Arg : Parcelable, Ret : Parcelable> :
|
||||||
AppCompatDialogFragment(), DialogInterface.OnClickListener {
|
AppCompatDialogFragment(), DialogInterface.OnClickListener {
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val KEY_RESULT = "result"
|
||||||
private const val KEY_ARG = "arg"
|
private const val KEY_ARG = "arg"
|
||||||
private const val KEY_RET = "ret"
|
private const val KEY_RET = "ret"
|
||||||
fun <T : Parcelable> getRet(data: Intent) = data.getParcelableExtra<T>(KEY_RET)!!
|
private const val KEY_WHICH = "which"
|
||||||
|
|
||||||
|
fun <Ret : Parcelable> setResultListener(fragment: Fragment, requestKey: String,
|
||||||
|
listener: (Int, Ret?) -> Unit) {
|
||||||
|
fragment.setFragmentResultListener(requestKey) { _, bundle ->
|
||||||
|
listener(bundle.getInt(KEY_WHICH, Activity.RESULT_CANCELED), bundle.getParcelable(KEY_RET))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline fun <reified T : AlertDialogFragment<*, Ret>, Ret : Parcelable> setResultListener(
|
||||||
|
fragment: Fragment, noinline listener: (Int, Ret?) -> Unit) =
|
||||||
|
setResultListener(fragment, T::class.java.name, listener)
|
||||||
}
|
}
|
||||||
protected abstract fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener)
|
protected abstract fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener)
|
||||||
|
|
||||||
|
private val resultKey get() = requireArguments().getString(KEY_RESULT)
|
||||||
protected val arg by lazy { requireArguments().getParcelable<Arg>(KEY_ARG)!! }
|
protected val arg by lazy { requireArguments().getParcelable<Arg>(KEY_ARG)!! }
|
||||||
protected open val ret: Ret? get() = null
|
protected open val ret: Ret? get() = null
|
||||||
fun withArg(arg: Arg) = apply { arguments = Bundle().apply { putParcelable(KEY_ARG, arg) } }
|
|
||||||
|
private fun args() = arguments ?: Bundle().also { arguments = it }
|
||||||
|
fun arg(arg: Arg) = args().putParcelable(KEY_ARG, arg)
|
||||||
|
fun key(resultKey: String = javaClass.name) = args().putString(KEY_RESULT, resultKey)
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog =
|
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog =
|
||||||
AlertDialog.Builder(requireContext()).also { it.prepare(this) }.create()
|
AlertDialog.Builder(requireContext()).also { it.prepare(this) }.create()
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||||
targetFragment?.onActivityResult(targetRequestCode, which, ret?.let {
|
setFragmentResult(resultKey ?: return, Bundle().apply {
|
||||||
Intent().replaceExtras(Bundle().apply { putParcelable(KEY_RET, it) })
|
putInt(KEY_WHICH, which)
|
||||||
|
putParcelable(KEY_RET, ret ?: return@apply)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDismiss(dialog: DialogInterface) {
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
super.onDismiss(dialog)
|
super.onDismiss(dialog)
|
||||||
targetFragment?.onActivityResult(targetRequestCode, Activity.RESULT_CANCELED, null)
|
setFragmentResult(resultKey ?: return, bundleOf(KEY_WHICH to Activity.RESULT_CANCELED))
|
||||||
}
|
|
||||||
|
|
||||||
fun show(target: Fragment, requestCode: Int = 0, tag: String = javaClass.simpleName) {
|
|
||||||
setTargetFragment(target, requestCode)
|
|
||||||
showAllowingStateLoss(target.parentFragmentManager, tag)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package be.mygod.vpnhotspot
|
package be.mygod.vpnhotspot
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import be.mygod.vpnhotspot.room.AppDatabase
|
|||||||
import be.mygod.vpnhotspot.room.ClientStats
|
import be.mygod.vpnhotspot.room.ClientStats
|
||||||
import be.mygod.vpnhotspot.room.TrafficRecord
|
import be.mygod.vpnhotspot.room.TrafficRecord
|
||||||
import be.mygod.vpnhotspot.util.SpanFormatter
|
import be.mygod.vpnhotspot.util.SpanFormatter
|
||||||
|
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
||||||
import be.mygod.vpnhotspot.util.toPluralInt
|
import be.mygod.vpnhotspot.util.toPluralInt
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
@@ -129,8 +130,9 @@ class ClientsFragment : Fragment() {
|
|||||||
return when (item?.itemId) {
|
return when (item?.itemId) {
|
||||||
R.id.nickname -> {
|
R.id.nickname -> {
|
||||||
val client = binding.client ?: return false
|
val client = binding.client ?: return false
|
||||||
NicknameDialogFragment().withArg(NicknameArg(client.mac, client.nickname))
|
NicknameDialogFragment().apply {
|
||||||
.show(this@ClientsFragment)
|
arg(NicknameArg(client.mac, client.nickname))
|
||||||
|
}.showAllowingStateLoss(parentFragmentManager)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.block, R.id.unblock -> {
|
R.id.block, R.id.unblock -> {
|
||||||
@@ -152,10 +154,10 @@ class ClientsFragment : Fragment() {
|
|||||||
binding.client?.let { client ->
|
binding.client?.let { client ->
|
||||||
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
|
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
|
||||||
withContext(Dispatchers.Unconfined) {
|
withContext(Dispatchers.Unconfined) {
|
||||||
StatsDialogFragment().withArg(StatsArg(
|
StatsDialogFragment().apply {
|
||||||
client.title.value ?: return@withContext,
|
arg(StatsArg(client.title.value ?: return@withContext,
|
||||||
AppDatabase.instance.trafficRecordDao.queryStats(client.mac.addr)
|
AppDatabase.instance.trafficRecordDao.queryStats(client.mac.addr)))
|
||||||
)).show(this@ClientsFragment)
|
}.showAllowingStateLoss(parentFragmentManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,19 @@ import androidx.databinding.BaseObservable
|
|||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import be.mygod.vpnhotspot.*
|
import be.mygod.vpnhotspot.*
|
||||||
import be.mygod.vpnhotspot.databinding.ListitemRepeaterBinding
|
import be.mygod.vpnhotspot.databinding.ListitemRepeaterBinding
|
||||||
import be.mygod.vpnhotspot.net.wifi.configuration.*
|
import be.mygod.vpnhotspot.net.wifi.configuration.*
|
||||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||||
import be.mygod.vpnhotspot.util.formatAddresses
|
import be.mygod.vpnhotspot.util.formatAddresses
|
||||||
|
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
@@ -91,7 +95,9 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun wps() {
|
fun wps() {
|
||||||
if (binder?.active == true) WpsDialogFragment().show(parent, TetheringFragment.REPEATER_WPS)
|
if (binder?.active == true) WpsDialogFragment().apply {
|
||||||
|
key()
|
||||||
|
}.showAllowingStateLoss(parent.parentFragmentManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +125,26 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
ServiceForegroundConnector(parent, this, RepeaterService::class)
|
ServiceForegroundConnector(parent, this, RepeaterService::class)
|
||||||
|
AlertDialogFragment.setResultListener<WifiApDialogFragment.Arg>(parent, javaClass.name) { which, ret ->
|
||||||
|
if (which == DialogInterface.BUTTON_POSITIVE) GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||||
|
updateConfiguration(ret!!.configuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AlertDialogFragment.setResultListener<WpsDialogFragment, WpsRet>(parent) { which, ret ->
|
||||||
|
when (which) {
|
||||||
|
DialogInterface.BUTTON_POSITIVE -> binder!!.startWps(ret!!.pin)
|
||||||
|
DialogInterface.BUTTON_NEUTRAL -> binder!!.startWps(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configure() = parent.viewLifecycleOwner.lifecycleScope.launchWhenCreated {
|
||||||
|
getConfiguration()?.let { config ->
|
||||||
|
WifiApDialogFragment().apply {
|
||||||
|
arg(WifiApDialogFragment.Arg(config, p2pMode = true))
|
||||||
|
key(this@RepeaterManager.javaClass.name)
|
||||||
|
}.showAllowingStateLoss(parent.parentFragmentManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val type get() = VIEW_TYPE_REPEATER
|
override val type get() = VIEW_TYPE_REPEATER
|
||||||
@@ -146,15 +172,8 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
data.onStatusChanged()
|
data.onStatusChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onWpsResult(which: Int, data: Intent?) {
|
|
||||||
when (which) {
|
|
||||||
DialogInterface.BUTTON_POSITIVE -> binder!!.startWps(AlertDialogFragment.getRet<WpsRet>(data!!).pin)
|
|
||||||
DialogInterface.BUTTON_NEUTRAL -> binder!!.startWps(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
suspend fun getConfiguration(): WifiConfiguration? {
|
private suspend fun getConfiguration(): WifiConfiguration? {
|
||||||
if (RepeaterService.safeMode) {
|
if (RepeaterService.safeMode) {
|
||||||
val networkName = RepeaterService.networkName
|
val networkName = RepeaterService.networkName
|
||||||
val passphrase = RepeaterService.passphrase
|
val passphrase = RepeaterService.passphrase
|
||||||
@@ -191,7 +210,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
SmartSnackbar.make(R.string.repeater_configure_failure).show()
|
SmartSnackbar.make(R.string.repeater_configure_failure).show()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
suspend fun updateConfiguration(config: WifiConfiguration) {
|
private suspend fun updateConfiguration(config: WifiConfiguration) {
|
||||||
if (RepeaterService.safeMode) {
|
if (RepeaterService.safeMode) {
|
||||||
RepeaterService.networkName = config.SSID
|
RepeaterService.networkName = config.SSID
|
||||||
RepeaterService.passphrase = config.preSharedKey
|
RepeaterService.passphrase = config.preSharedKey
|
||||||
|
|||||||
@@ -31,23 +31,15 @@ import be.mygod.vpnhotspot.net.wifi.configuration.WifiApDialogFragment
|
|||||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||||
import be.mygod.vpnhotspot.util.broadcastReceiver
|
import be.mygod.vpnhotspot.util.broadcastReceiver
|
||||||
import be.mygod.vpnhotspot.util.isNotGone
|
import be.mygod.vpnhotspot.util.isNotGone
|
||||||
|
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
|
|
||||||
class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener {
|
class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener {
|
||||||
companion object {
|
|
||||||
const val REPEATER_WPS = 3
|
|
||||||
const val CONFIGURE_REPEATER = 2
|
|
||||||
const val CONFIGURE_AP = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ManagerAdapter : ListAdapter<Manager, RecyclerView.ViewHolder>(Manager) {
|
inner class ManagerAdapter : ListAdapter<Manager, RecyclerView.ViewHolder>(Manager) {
|
||||||
internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
|
internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
|
||||||
@get:RequiresApi(26)
|
@get:RequiresApi(26)
|
||||||
@@ -168,25 +160,22 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.configuration_repeater -> {
|
R.id.configuration_repeater -> {
|
||||||
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
|
adapter.repeaterManager.configure()
|
||||||
adapter.repeaterManager.getConfiguration()?.let { config ->
|
|
||||||
WifiApDialogFragment().withArg(WifiApDialogFragment.Arg(config, p2pMode = true)).show(
|
|
||||||
this@TetheringFragment, CONFIGURE_REPEATER)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.configuration_temp_hotspot -> {
|
R.id.configuration_temp_hotspot -> {
|
||||||
WifiApDialogFragment().withArg(WifiApDialogFragment.Arg(
|
WifiApDialogFragment().apply {
|
||||||
adapter.localOnlyHotspotManager.binder?.configuration ?: return false,
|
arg(WifiApDialogFragment.Arg(adapter.localOnlyHotspotManager.binder?.configuration ?: return false,
|
||||||
readOnly = true
|
readOnly = true))
|
||||||
)).show(this, 0) // read-only, no callback needed
|
// no need for callback
|
||||||
|
}.showAllowingStateLoss(parentFragmentManager)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.configuration_ap -> try {
|
R.id.configuration_ap -> try {
|
||||||
WifiApDialogFragment().withArg(WifiApDialogFragment.Arg(
|
WifiApDialogFragment().apply {
|
||||||
WifiApManager.configuration
|
arg(WifiApDialogFragment.Arg(WifiApManager.configuration))
|
||||||
)).show(this, CONFIGURE_AP)
|
key()
|
||||||
|
}.showAllowingStateLoss(parentFragmentManager)
|
||||||
true
|
true
|
||||||
} catch (e: InvocationTargetException) {
|
} catch (e: InvocationTargetException) {
|
||||||
if (e.targetException !is SecurityException) Timber.w(e)
|
if (e.targetException !is SecurityException) Timber.w(e)
|
||||||
@@ -198,6 +187,16 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
AlertDialogFragment.setResultListener<WifiApDialogFragment, WifiApDialogFragment.Arg>(this) { which, ret ->
|
||||||
|
if (which == DialogInterface.BUTTON_POSITIVE) try {
|
||||||
|
WifiApManager.configuration = ret!!.configuration
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
Timber.d(e)
|
||||||
|
SmartSnackbar.make(R.string.configuration_rejected).show()
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
SmartSnackbar.make(e.targetException).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
binding = FragmentTetheringBinding.inflate(inflater, container, false)
|
binding = FragmentTetheringBinding.inflate(inflater, container, false)
|
||||||
binding.interfaces.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
binding.interfaces.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||||
binding.interfaces.itemAnimator = DefaultItemAnimator()
|
binding.interfaces.itemAnimator = DefaultItemAnimator()
|
||||||
@@ -224,27 +223,6 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
if (Build.VERSION.SDK_INT >= 27) ManageBar.Data.notifyChange()
|
if (Build.VERSION.SDK_INT >= 27) ManageBar.Data.notifyChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
val configuration by lazy { AlertDialogFragment.getRet<WifiApDialogFragment.Arg>(data!!).configuration }
|
|
||||||
when (requestCode) {
|
|
||||||
REPEATER_WPS -> adapter.repeaterManager.onWpsResult(resultCode, data)
|
|
||||||
CONFIGURE_REPEATER -> if (resultCode == DialogInterface.BUTTON_POSITIVE) {
|
|
||||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
|
||||||
adapter.repeaterManager.updateConfiguration(configuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CONFIGURE_AP -> if (resultCode == DialogInterface.BUTTON_POSITIVE) try {
|
|
||||||
WifiApManager.configuration = configuration
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
Timber.d(e)
|
|
||||||
SmartSnackbar.make(R.string.configuration_rejected).show()
|
|
||||||
} catch (e: InvocationTargetException) {
|
|
||||||
SmartSnackbar.make(e.targetException).show()
|
|
||||||
}
|
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
binder = service as TetheringService.Binder
|
binder = service as TetheringService.Binder
|
||||||
service.routingsChanged[this] = {
|
service.routingsChanged[this] = {
|
||||||
|
|||||||
Reference in New Issue
Block a user