Commit a2b5f9f4 authored by Mygod's avatar Mygod

Use AlertDialog with DialogFragment

A helper class is created to achieve this goal.

This way we can persist dialogs after configuration changes.
parent ca0cb97a
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
......@@ -38,6 +39,10 @@ android {
}
}
androidExtensions {
experimental = true
}
def lifecycleVersion = '2.0.0'
def roomVersion = '2.0.0'
def workVersion = '1.0.0-rc01'
......
......@@ -21,6 +21,7 @@
package com.github.shadowsocks.database
import android.net.Uri
import android.os.Parcelable
import android.util.Base64
import android.util.Log
import android.util.LongSparseArray
......@@ -32,6 +33,7 @@ import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.parsePort
import kotlinx.android.parcel.Parcelize
import org.json.JSONArray
import org.json.JSONObject
import org.json.JSONTokener
......@@ -41,7 +43,31 @@ import java.net.URISyntaxException
import java.util.*
@Entity
class Profile : Serializable {
@Parcelize
data class Profile(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
var name: String? = "",
var host: String = "198.199.101.152",
var remotePort: Int = 8388,
var password: String = "u1rRWTssNv0p",
var method: String = "aes-256-cfb",
var route: String = "all",
var remoteDns: String = "8.8.8.8",
var proxyApps: Boolean = false,
var bypass: Boolean = false,
var udpdns: Boolean = false,
var ipv6: Boolean = true,
var individual: String = "",
var tx: Long = 0,
var rx: Long = 0,
var userOrder: Long = 0,
var plugin: String? = null,
var udpFallback: Long? = null,
@Ignore // not persisted in db, only used by direct boot
var dirty: Boolean = false
) : Parcelable, Serializable {
companion object {
private const val TAG = "ShadowParser"
private const val serialVersionUID = 0L
......@@ -200,29 +226,6 @@ class Profile : Serializable {
fun deleteAll(): Int
}
@PrimaryKey(autoGenerate = true)
var id: Long = 0
var name: String? = ""
var host: String = "198.199.101.152"
var remotePort: Int = 8388
var password: String = "u1rRWTssNv0p"
var method: String = "aes-256-cfb"
var route: String = "all"
var remoteDns: String = "8.8.8.8"
var proxyApps: Boolean = false
var bypass: Boolean = false
var udpdns: Boolean = false
var ipv6: Boolean = true
var individual: String = ""
var tx: Long = 0
var rx: Long = 0
var userOrder: Long = 0
var plugin: String? = null
var udpFallback: Long? = null
@Ignore // not persisted in db, only used by direct boot
var dirty: Boolean = false
val formattedAddress get() = (if (host.contains(":")) "[%s]:%d" else "%s:%d").format(host, remotePort)
val formattedName get() = if (name.isNullOrEmpty()) formattedAddress else name!!
......
......@@ -5,6 +5,7 @@ import java.util.regex.Pattern
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
def getCurrentFlavor() {
String task = getGradle().getStartParameter().getTaskRequests().toString()
......@@ -49,6 +50,10 @@ android {
new File(project(':core').buildDir, "intermediates/bundles/${getCurrentFlavor()}/jni")
}
androidExtensions {
experimental = true
}
dependencies {
implementation project(':core')
implementation "androidx.browser:browser:1.0.0"
......
......@@ -23,6 +23,7 @@ package com.github.shadowsocks
import android.app.Activity
import android.app.backup.BackupManager
import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
import android.net.VpnService
import android.nfc.NdefMessage
......@@ -30,6 +31,7 @@ import android.nfc.NfcAdapter
import android.os.Bundle
import android.os.DeadObjectException
import android.os.Handler
import android.os.Parcelable
import android.util.Log
import android.view.KeyCharacterMap
import android.view.KeyEvent
......@@ -51,6 +53,8 @@ import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.plugin.Empty
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.Key
......@@ -58,6 +62,7 @@ import com.github.shadowsocks.widget.ServiceButton
import com.github.shadowsocks.widget.StatsBar
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.parcel.Parcelize
class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPreferenceDataStoreChangeListener,
NavigationView.OnNavigationItemSelectedListener {
......@@ -68,6 +73,17 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
var stateListener: ((Int) -> Unit)? = null
}
@Parcelize
data class ProfilesArg(val profiles: List<Profile>) : Parcelable
class ImportProfilesDialogFragment : AlertDialogFragment<ProfilesArg, Empty>() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
setTitle(R.string.add_profile_dialog)
setPositiveButton(R.string.yes) { _, _ -> arg.profiles.forEach { ProfileManager.createProfile(it) } }
setNegativeButton(R.string.no, null)
setMessage(arg.profiles.joinToString("\n"))
}
}
// UI
private lateinit var fab: ServiceButton
private lateinit var stats: StatsBar
......@@ -188,17 +204,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
}
if (sharedStr.isNullOrEmpty()) return
val profiles = Profile.findAllUrls(sharedStr, Core.currentProfile?.first).toList()
if (profiles.isEmpty()) {
snackbar().setText(R.string.profile_invalid_input).show()
return
}
AlertDialog.Builder(this)
.setTitle(R.string.add_profile_dialog)
.setPositiveButton(R.string.yes) { _, _ -> profiles.forEach { ProfileManager.createProfile(it) } }
.setNegativeButton(R.string.no, null)
.setMessage(profiles.joinToString("\n"))
.create()
.show()
if (profiles.isEmpty()) snackbar().setText(R.string.profile_invalid_input).show()
else ImportProfilesDialogFragment().withArg(ProfilesArg(profiles)).show(supportFragmentManager, null)
}
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) {
......
......@@ -21,12 +21,15 @@
package com.github.shadowsocks
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.plugin.Empty
import com.github.shadowsocks.plugin.PluginContract
import com.github.shadowsocks.preference.DataStore
......@@ -35,6 +38,15 @@ class ProfileConfigActivity : AppCompatActivity() {
const val REQUEST_CODE_PLUGIN_HELP = 1
}
class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
setTitle(R.string.unsaved_changes_prompt)
setPositiveButton(R.string.yes, listener)
setNegativeButton(R.string.no, listener)
setNeutralButton(android.R.string.cancel, null)
}
}
private val child by lazy { supportFragmentManager.findFragmentById(R.id.content) as ProfileConfigFragment }
override fun onCreate(savedInstanceState: Bundle?) {
......@@ -59,13 +71,8 @@ class ProfileConfigActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item)
override fun onBackPressed() {
if (DataStore.dirty) AlertDialog.Builder(this)
.setTitle(R.string.unsaved_changes_prompt)
.setPositiveButton(R.string.yes) { _, _ -> child.saveAndExit() }
.setNegativeButton(R.string.no) { _, _ -> finish() }
.setNeutralButton(android.R.string.cancel, null)
.create()
.show() else super.onBackPressed()
if (DataStore.dirty) UnsavedChangesDialogFragment().show(child, ProfileConfigFragment.REQUEST_UNSAVED_CHANGES)
else super.onBackPressed()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
......
......@@ -22,8 +22,10 @@ package com.github.shadowsocks
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
......@@ -33,10 +35,7 @@ import androidx.preference.SwitchPreference
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.plugin.PluginContract
import com.github.shadowsocks.plugin.PluginManager
import com.github.shadowsocks.plugin.PluginOptions
import com.github.shadowsocks.plugin.*
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.IconListPreference
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
......@@ -47,11 +46,26 @@ import com.github.shadowsocks.utils.Key
import com.google.android.material.snackbar.Snackbar
import com.takisoft.preferencex.EditTextPreference
import com.takisoft.preferencex.PreferenceFragmentCompat
import kotlinx.android.parcel.Parcelize
class ProfileConfigFragment : PreferenceFragmentCompat(),
Preference.OnPreferenceChangeListener, OnPreferenceDataStoreChangeListener {
companion object {
private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1
const val REQUEST_UNSAVED_CHANGES = 2
}
@Parcelize
data class ProfileIdArg(val profileId: Long) : Parcelable
class DeleteConfirmationDialogFragment : AlertDialogFragment<ProfileIdArg, Empty>() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
setTitle(R.string.delete_confirm_prompt)
setPositiveButton(R.string.yes) { _, _ ->
ProfileManager.delProfile(arg.profileId)
requireActivity().finish()
}
setNegativeButton(R.string.no, null)
}
}
private var profileId = -1L
......@@ -116,7 +130,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
bundleOf(Pair("key", Key.pluginConfigure),
Pair(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, pluginConfiguration.selected)))
fun saveAndExit() {
private fun saveAndExit() {
val profile = ProfileManager.getProfile(profileId) ?: Profile()
profile.id = profileId
profile.deserialize()
......@@ -161,28 +175,26 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_PLUGIN_CONFIGURE) when (resultCode) {
when (requestCode) {
REQUEST_CODE_PLUGIN_CONFIGURE -> when (resultCode) {
Activity.RESULT_OK -> {
val options = data?.getStringExtra(PluginContract.EXTRA_OPTIONS)
pluginConfigure.text = options
onPreferenceChange(null, options)
}
PluginContract.RESULT_FALLBACK -> showPluginEditor()
} else super.onActivityResult(requestCode, resultCode, data)
}
REQUEST_UNSAVED_CHANGES -> when (resultCode) {
DialogInterface.BUTTON_POSITIVE -> saveAndExit()
DialogInterface.BUTTON_NEGATIVE -> requireActivity().finish()
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_delete -> {
val activity = requireActivity()
AlertDialog.Builder(activity)
.setTitle(R.string.delete_confirm_prompt)
.setPositiveButton(R.string.yes) { _, _ ->
ProfileManager.delProfile(profileId)
activity.finish()
}
.setNegativeButton(R.string.no, null)
.create()
.show()
DeleteConfirmationDialogFragment().withArg(ProfileIdArg(profileId)).show(this)
true
}
R.id.action_apply -> {
......
......@@ -22,9 +22,12 @@ package com.github.shadowsocks.acl
import android.content.ClipData
import android.content.ClipboardManager
import android.content.DialogInterface
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.text.Editable
import android.text.TextWatcher
import android.view.*
......@@ -43,10 +46,12 @@ import com.github.shadowsocks.R
import com.github.shadowsocks.ToolbarFragment
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.resolveResourceId
import com.github.shadowsocks.widget.UndoSnackbarManager
import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.parcel.Parcelize
import java.net.IDN
import java.net.MalformedURLException
import java.net.URL
......@@ -55,6 +60,8 @@ import java.util.regex.PatternSyntaxException
class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ActionMode.Callback {
companion object {
private const val REQUEST_CODE_ADD = 1
private const val REQUEST_CODE_EDIT = 2
private const val TEMPLATE_REGEX_DOMAIN = "(^|\\.)%s$"
private const val SELECTED_SUBNETS = "com.github.shadowsocks.acl.CustomRulesFragment.SELECTED_SUBNETS"
......@@ -63,6 +70,13 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
// unescaped: (?<=^(\(\^\|\\\.\)|\^\(\.\*\\\.\)\?)).*(?=\$$)
private val domainPattern = "(?<=^(\\(\\^\\|\\\\\\.\\)|\\^\\(\\.\\*\\\\\\.\\)\\?)).*(?=\\\$\$)".toRegex()
private fun AclItem(item: Any) = when (item) {
is String -> AclItem(item, false)
is Subnet -> AclItem(item.toString(), false)
is URL -> AclItem(item.toString(), true)
else -> throw IllegalArgumentException("item")
}
}
private enum class Template {
......@@ -70,53 +84,50 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
Domain,
Url;
}
private inner class AclRuleDialog(item: Any = "") : TextWatcher, AdapterView.OnItemSelectedListener {
val builder: AlertDialog.Builder
val templateSelector: Spinner
val editText: EditText
private val inputLayout: TextInputLayout
private lateinit var dialog: AlertDialog
@Parcelize
data class AclItem(val item: String = "", val isUrl: Boolean = false) : Parcelable {
fun toAny() = if (isUrl) URL(item) else Subnet.fromString(item) ?: item
}
@Parcelize
data class AclEditResult(val edited: AclItem, val replacing: AclItem) : Parcelable
class AclRuleDialogFragment : AlertDialogFragment<AclItem, AclEditResult>(),
TextWatcher, AdapterView.OnItemSelectedListener {
lateinit var templateSelector: Spinner
lateinit var editText: EditText
private lateinit var inputLayout: TextInputLayout
private lateinit var positive: Button
init {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
val activity = requireActivity()
val view = activity.layoutInflater.inflate(R.layout.dialog_acl_rule, null)
templateSelector = view.findViewById(R.id.template_selector)
editText = view.findViewById(R.id.content)
inputLayout = view.findViewById(R.id.content_layout)
when (item) {
is String -> {
val match = domainPattern.find(item)
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(arg.item)
when {
arg.isUrl -> templateSelector.setSelection(Template.Url.ordinal)
Subnet.fromString(arg.item) == null -> {
val match = domainPattern.find(arg.item)
if (match != null) {
templateSelector.setSelection(Template.Domain.ordinal)
editText.setText(IDN.toUnicode(match.value.replace("\\.", "."),
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES))
} else {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item)
}
}
is URL -> {
templateSelector.setSelection(Template.Url.ordinal)
editText.setText(item.toString())
}
else -> {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item.toString())
}
}
templateSelector.onItemSelectedListener = this
editText.addTextChangedListener(this)
builder = AlertDialog.Builder(activity)
.setTitle(R.string.edit_rule)
.setNegativeButton(android.R.string.cancel, null)
.setView(view)
templateSelector.onItemSelectedListener = this@AclRuleDialogFragment
editText.addTextChangedListener(this@AclRuleDialogFragment)
setTitle(R.string.edit_rule)
setPositiveButton(android.R.string.ok, listener)
setNegativeButton(android.R.string.cancel, null)
if (arg.item.isNotEmpty()) setNeutralButton(R.string.delete, listener)
setView(view)
}
fun show() {
dialog = builder.create()
dialog.show()
positive = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
override fun onStart() {
super.onStart()
positive = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
validate()
}
......@@ -157,15 +168,14 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
inputLayout.error = message
}
fun add(): Int? {
val text = editText.text.toString()
return when (Template.values()[templateSelector.selectedItemPosition]) {
Template.Generic -> adapter.addToProxy(text)
Template.Domain -> adapter.addHostname(TEMPLATE_REGEX_DOMAIN.format(Locale.ENGLISH, IDN.toASCII(text,
override val ret get() = AclEditResult(editText.text.toString().let { text ->
when (Template.values()[templateSelector.selectedItemPosition]) {
Template.Generic -> AclItem(text)
Template.Domain -> AclItem(TEMPLATE_REGEX_DOMAIN.format(Locale.ENGLISH, IDN.toASCII(text,
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES).replace(".", "\\.")))
Template.Url -> adapter.addURL(URL(text))
}
Template.Url -> AclItem(text, true)
}
}, arg)
}
private inner class AclRuleViewHolder(view: View) : RecyclerView.ViewHolder(view),
......@@ -197,22 +207,10 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
}
override fun onClick(v: View?) {
if (selectedItems.isNotEmpty()) onLongClick(v) else {
val dialog = AclRuleDialog(item)
dialog.builder
.setNeutralButton(R.string.delete) { _, _ ->
adapter.remove(item)
undoManager.remove(Pair(-1, item))
}
.setPositiveButton(android.R.string.ok) { _, _ ->
adapter.remove(item)
val index = dialog.add() ?: adapter.add(item)
if (index != null) list.post { list.scrollToPosition(index) }
}
dialog.show()
if (selectedItems.isNotEmpty()) onLongClick(v)
else AclRuleDialogFragment().withArg(AclItem(item)).show(this@CustomRulesFragment, REQUEST_CODE_EDIT)
}
}
override fun onLongClick(p0: View?): Boolean {
override fun onLongClick(v: View?): Boolean {
if (!selectedItems.add(item)) selectedItems.remove(item) // toggle
onSelectedItemsUpdated()
itemView.isSelected = !itemView.isSelected
......@@ -427,9 +425,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
R.id.action_manual_settings -> {
val dialog = AclRuleDialog()
dialog.builder.setPositiveButton(android.R.string.ok) { _, _ -> dialog.add() }
dialog.show()
AclRuleDialogFragment().withArg(AclItem()).show(this, REQUEST_CODE_ADD)
true
}
R.id.action_import_clipboard -> {
......@@ -451,6 +447,25 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
else -> false
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val editing = when (requestCode) {
REQUEST_CODE_ADD -> false
REQUEST_CODE_EDIT -> true
else -> return super.onActivityResult(requestCode, resultCode, data)
}
val ret by lazy { AlertDialogFragment.getRet<AclEditResult>(data!!) }
when (resultCode) {
DialogInterface.BUTTON_POSITIVE -> {
if (editing) adapter.remove(ret.replacing.toAny())
adapter.add(ret.edited.toAny())?.also { list.post { list.scrollToPosition(it) } }
}
DialogInterface.BUTTON_NEUTRAL -> ret.replacing.toAny().let { item ->
adapter.remove(item)
undoManager.remove(Pair(-1, item))
}
}
}
override fun onDetach() {
undoManager.flush()
mode?.finish()
......
* 1.2.0:
* New helper class `AlertDialogFragment` for creating `AlertDialog` that persists through configuration changes;
* Dependency update: `com.google.android.material:material:1.1.0-alpha03`.
* 1.1.0:
* Having control characters in plugin options is no longer allowed.
......
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply from: 'gradle-mvn-push.gradle'
......@@ -23,6 +24,10 @@ android {
}
}
androidExtensions {
experimental = true
}
dependencies {
api "androidx.core:core-ktx:1.0.1"
api "com.google.android.material:material:1.1.0-alpha03"
......
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2019 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.plugin
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.fragment.app.Fragment
import kotlinx.android.parcel.Parcelize
/**
* Based on: https://android.googlesource.com/platform/packages/apps/ExactCalculator/+/8c43f06/src/com/android/calculator2/AlertDialogFragment.java
*/
abstract class AlertDialogFragment<Arg : Parcelable, Ret : Parcelable> :
AppCompatDialogFragment(), DialogInterface.OnClickListener {
companion object {
private const val KEY_ARG = "arg"
private const val KEY_RET = "ret"
fun <T : Parcelable> getRet(data: Intent) = data.extras!!.getParcelable<T>(KEY_RET)!!
}
protected abstract fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener)
protected val arg by lazy { arguments!!.getParcelable<Arg>(KEY_ARG)!! }
protected open val ret: Ret? get() = null
fun withArg(arg: Arg) = apply { arguments = Bundle().apply { putParcelable(KEY_ARG, arg) } }
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog =
AlertDialog.Builder(requireContext()).also { it.prepare(this) }.create()
override fun onClick(dialog: DialogInterface?, which: Int) {
targetFragment?.onActivityResult(targetRequestCode, which, ret?.let {
Intent().replaceExtras(Bundle().apply { putParcelable(KEY_RET, it) })
})
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
targetFragment?.onActivityResult(targetRequestCode, Activity.RESULT_CANCELED, null)
}
fun show(target: Fragment, requestCode: Int = 0, tag: String = javaClass.simpleName) {
setTargetFragment(target, requestCode)
show(target.fragmentManager ?: return, tag)
}
}
@Parcelize
class Empty : Parcelable
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment