Use new fragment result API

This commit is contained in:
Mygod
2020-06-01 18:52:40 -04:00
parent 21e4700b60
commit bf352297ec
5 changed files with 82 additions and 71 deletions

View File

@@ -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)
} }
} }

View File

@@ -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

View File

@@ -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)
} }
} }
} }

View File

@@ -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

View File

@@ -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] = {