Last active
April 30, 2018 08:09
-
-
Save CapnSpellcheck/6b024eeb5e6bfd68f4b5d3d3a0da12d4 to your computer and use it in GitHub Desktop.
A class to make simple the use of DialogFragment with AlertDialog. You can set a few properties directly, or provide a delegate and deal with Builder directly. *NEW*: This now supports instance state restoration. Assigning a delegate is now deprecated. Instead, implement a provideDelegate() function. For example, Use ActivityDelegatingDialogFrag…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package letstwinkle.com.twinkle.util | |
import android.annotation.SuppressLint | |
import android.os.Bundle | |
import android.support.v4.os.ConfigurationCompat | |
import android.text.Spannable | |
import android.text.SpannableStringBuilder | |
import android.text.style.ImageSpan | |
import android.view.View | |
import android.view.ViewTreeObserver | |
import android.view.inputmethod.EditorInfo | |
import android.widget.* | |
import letstwinkle.com.twinkle.* | |
import letstwinkle.com.twinkle.model.Interests | |
import letstwinkle.com.twinkle.widget.StableArrayAdapter | |
private const val tag_ = "DialogListPicker" | |
internal class DialogListPicker | |
{ | |
companion object { | |
fun forOccupation(occus: List<String>, defaultIndex: Int = -1): SimpleDialogFragment | |
{ | |
val frag = forOccupation(defaultIndex) | |
frag.arguments.putStringArray("items", occus.toTypedArray()) | |
return frag | |
} | |
fun forOccupation(defaultIndex: Int = -1): SimpleDialogFragment { | |
return makeFragment(Dialogs.ChooseOccupation, defaultIndex) | |
} | |
@SuppressLint("InflateParams") | |
fun forEducationSite(sites: List<String>, defaultIndex: Int = -1): SimpleDialogFragment | |
{ | |
val frag = forEducationSite(defaultIndex) | |
frag.arguments.putStringArray("items", sites.toTypedArray()) | |
return frag | |
} | |
fun forEducationSite(defaultIndex: Int = -1): SimpleDialogFragment { | |
return makeFragment(Dialogs.ChooseEduSite, defaultIndex) | |
} | |
@SuppressLint("InflateParams") | |
fun forInterests(excluding: ArrayList<Int> = arrayListOf()): SimpleDialogFragment | |
{ | |
return makeFragment(Dialogs.ChooseInterest, -1, excluding) | |
} | |
private fun makeFragment(id: Int, defaultIndex: Int = -1, excluding: ArrayList<Int>? = null) | |
: SimpleDialogFragment | |
{ | |
val frag = ListPickerDialogFragment() | |
frag.ID = id | |
frag.canceledOnTouchOutside = false | |
val args = Bundle() | |
args.putInt("default", defaultIndex) | |
args.putIntegerArrayList("exclude", excluding) | |
frag.arguments = args | |
return frag | |
} | |
} | |
} | |
internal class ListPickerDialogFragment() : SimpleDialogFragment(), SearchView.OnQueryTextListener { | |
private var defaultIndex = Adapter.NO_SELECTION | |
private lateinit var adapter: StableArrayAdapter<String> | |
override fun createCustomView(): View? { | |
return this.activity.layoutInflater.inflate(R.layout.dialog_searchable_rv, null) | |
} | |
override fun onStart() { | |
super.onStart() | |
defaultIndex = this.arguments.getInt("default") | |
val adapter: StableArrayAdapter<String> | |
if (this.ID == Dialogs.ChooseInterest) { | |
val excludeList = this.arguments.getIntegerArrayList("exclude") ?: emptyList<Int>() | |
val entries = Interests.getStableEntries(excludeList) | |
adapter = StableArrayAdapter(android.R.layout.simple_list_item_checked, | |
this.activity, | |
entries) | |
} else if (this.arguments.getStringArray("items") != null) { | |
adapter = StableArrayAdapter(this.activity, | |
android.R.layout.simple_list_item_checked, | |
this.arguments.getStringArray("items")) | |
} else if (this.ID == Dialogs.ChooseEduSite) { | |
val entries = Global.splitRawIntoArray(R.raw.schools) | |
adapter = StableArrayAdapter(this.activity, | |
android.R.layout.simple_list_item_checked, | |
entries) | |
} else if (this.ID == Dialogs.ChooseOccupation) { | |
val entries = Global.splitRawIntoArray(R.raw.occupations) | |
adapter = StableArrayAdapter(this.activity, | |
android.R.layout.simple_list_item_checked, | |
entries) | |
} else { | |
throw IllegalArgumentException("Invalid Dialog ID for ListPickerDialogFragment") | |
} | |
this.adapter = adapter | |
attachDelayer(adapter.filter) | |
val listView = this.dialog.findViewById<ListView>(R.id.results) | |
listView.adapter = adapter | |
listView.tag = this.ID | |
// call the listener, but enforce that list is no longer clickable (could also dismiss dlog here) | |
listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, pos, id -> | |
listView.isEnabled = false | |
val event = AdapterViewOnItemClick(parent, view, pos, id) | |
TwinkleApplication.instance.ottobus.post(event) | |
} | |
if (defaultIndex > 0) { | |
listView.setItemChecked(defaultIndex, true) | |
listView.setSelection(defaultIndex) | |
} | |
listView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { | |
override fun onGlobalLayout() { | |
if (listView.height > 0) { | |
listView.layoutParams.height = listView.height | |
listView.viewTreeObserver.removeOnGlobalLayoutListener(this) | |
} | |
} | |
}) | |
// Edu picker: if Hindi: all schools shown in English, so change keyboard | |
if (ID == Dialogs.ChooseEduSite) { | |
val searchView = this.dialog.findViewById<SearchView>(R.id.searchView) | |
val ctx = this.dialog.context | |
if (ConfigurationCompat.getLocales(ctx.resources.configuration).get(0).language == "hi") { | |
searchView.imeOptions = searchView.imeOptions or EditorInfo.IME_FLAG_FORCE_ASCII | |
} | |
} | |
val searchView = this.dialog.findViewById<SearchView>(R.id.searchView) | |
searchView?.setOnQueryTextListener(this) | |
try { | |
val ssb = SpannableStringBuilder(" ") | |
ssb.setSpan(ImageSpan(searchView?.context, R.drawable.abc_ic_search_api_material), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | |
searchView?.queryHint = ssb | |
} catch (ex: Exception) { | |
} | |
} | |
// OnQueryTextListener | |
override fun onQueryTextSubmit(query: String?): Boolean { | |
return true | |
} | |
override fun onQueryTextChange(newText: String?): Boolean { | |
adapter.filter?.filter(newText) | |
return true | |
} | |
} | |
private fun attachDelayer(filter: Filter) { | |
ignoreExceptions { | |
val delayerClass = Class.forName("android.widget.Filter\$Delayer", true, filter.javaClass.classLoader) | |
val setDelayer = Filter::class.java.getDeclaredMethod("setDelayer", delayerClass) | |
setDelayer.invoke(filter, MyDelayerInvocationHandler.proxy()) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package letstwinkle.com.twinkle.util | |
import android.app.* | |
import android.content.DialogInterface | |
import android.content.DialogInterface.* | |
import android.os.Bundle | |
import android.support.annotation.LayoutRes | |
import android.support.annotation.StringRes | |
import android.util.Log | |
import android.view.View | |
import android.view.ViewGroup | |
private const val tag_ = "SimpleDialogFragment" | |
open internal class SimpleDialogFragment : DialogFragment(), OnClickListener, OnCancelListener, OnDismissListener { | |
var ID: Int = -1 | |
@StringRes var title: Int? = null | |
@StringRes var message: Int? = null | |
@StringRes var positiveLabel: Int? = null | |
@StringRes var negativeLabel: Int? = null | |
@StringRes var neutralLabel: Int? = null | |
var listItems: Array<CharSequence>? = null | |
var messageCS: CharSequence? = null // takes precedecnce | |
var canceledOnTouchOutside = true | |
protected var delegate: SimpleDialogDelegate? = null | |
protected var destroyed = false | |
val customView: View? | |
get() = this.dialog.findViewById<ViewGroup>(android.R.id.custom)?.getChildAt(0) | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
savedInstanceState?.let { | |
// all of our public properties won't have any value, but would have been stashed | |
// into the arguments in onSaveInstanceState | |
ID = savedInstanceState.getInt("id") | |
title = savedInstanceState.get("title") as Int? | |
message = savedInstanceState.get("msg") as Int? | |
positiveLabel = savedInstanceState.get("pos") as Int? | |
negativeLabel = savedInstanceState.get("neg") as Int? | |
neutralLabel = savedInstanceState.get("neut") as Int? | |
listItems = savedInstanceState.getCharSequenceArray("list") | |
messageCS = savedInstanceState.getCharSequence("msgCS") | |
canceledOnTouchOutside = savedInstanceState.getBoolean("coto") | |
} | |
} | |
override fun onAttach(activity: Activity) { | |
super.onAttach(activity) | |
Log.d("SimpleDialogFragment", "onAttach checking provideDelegate") | |
delegate = provideDelegate() | |
Log.d("SimpleDialogFragment", "onAttach found delegate: ${delegate != null}") | |
} | |
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | |
val builder = AlertDialog.Builder(this.activity) | |
title?.let {builder.setTitle(it)} | |
message?.let {builder.setMessage(it)} | |
messageCS?.let {builder.setMessage(it)} | |
positiveLabel?.let {builder.setPositiveButton(it, this)} | |
negativeLabel?.let {builder.setNegativeButton(it, this)} | |
neutralLabel?.let {builder.setNeutralButton(it, this)} | |
createCustomView()?.let { | |
Log.d(tag_, "onCreateDialog: createCustomView") | |
builder.setView(it) | |
} | |
listItems?.let {builder.setItems(listItems, this)} | |
delegate?.onCreateDialog(builder, this) | |
val dialog = builder.create() | |
if (!canceledOnTouchOutside) | |
dialog.setCanceledOnTouchOutside(false) | |
return dialog | |
} | |
override fun onSaveInstanceState(outState: Bundle) { | |
super.onSaveInstanceState(outState) | |
outState.putInt("id", ID) | |
title?.let {outState.putInt("title", it)} | |
message?.let {outState.putInt("msg", it)} | |
positiveLabel?.let {outState.putInt("pos", it)} | |
negativeLabel?.let {outState.putInt("neg", it)} | |
neutralLabel?.let {outState.putInt("neut", it)} | |
listItems?.let {outState.putCharSequenceArray("list", it)} | |
messageCS?.let {outState.putCharSequence("msgCS", it)} | |
outState.putBoolean("coto", canceledOnTouchOutside) | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
delegate = null | |
destroyed = true | |
} | |
override fun onClick(dialog: DialogInterface?, which: Int) { | |
delegate?.simpleDialogClicked(this, which) | |
} | |
override fun onCancel(dialog: DialogInterface?) { | |
super.onCancel(dialog) | |
delegate?.simpleDialogCanceled(this) | |
} | |
override fun onDismiss(dialog: DialogInterface?) { | |
super.onDismiss(dialog) | |
delegate?.simpleDialogDismissed(this) | |
} | |
override fun onStart() { | |
super.onStart() | |
delegate?.simpleDialogStarted(this) | |
} | |
override fun show(manager: FragmentManager, tag: String) { | |
// to do without try-catch requires API 26 FragmentManager#isStateSaved | |
try { | |
super.show(manager, tag) | |
} catch (ex: IllegalStateException) { | |
} | |
} | |
// Non-overrides | |
open fun provideDelegate() : SimpleDialogDelegate? = null | |
/** | |
* Override this to establish a view as the AlertDialog's 'custom' view. | |
*/ | |
open fun createCustomView(): View? = null | |
fun showAllowingStateLoss(fragMgr: FragmentManager, tag: String) { | |
if (!fragMgr.isDestroyed) | |
fragMgr.beginTransaction().add(this, tag).commitAllowingStateLoss() | |
} | |
} | |
internal interface SimpleDialogDelegate { | |
fun onCreateDialog(builder: AlertDialog.Builder, frag: SimpleDialogFragment) { onCreateDialog(builder)} | |
fun onCreateDialog(builder: AlertDialog.Builder) {} | |
fun simpleDialogClicked(dlog: SimpleDialogFragment, which: Int) {} | |
fun simpleDialogCanceled(dlog: SimpleDialogFragment) {} | |
fun simpleDialogDismissed(dlog: SimpleDialogFragment) {} | |
fun simpleDialogStarted(dlog: SimpleDialogFragment) {} | |
} | |
open internal class ActivityDelegatingDialogFragment : SimpleDialogFragment() { | |
override fun onAttach(activity: Activity) { | |
super.onAttach(activity) | |
if (activity is SimpleDialogDelegate) { | |
delegate = activity | |
Log.d("ADDF", "found activity delegate") | |
} | |
else | |
throw IllegalStateException("ActivityDelegatingDialogFragment must be attached to a context that implements SimpleDialogDelegate") | |
} | |
} | |
internal class SimpleLayoutDialogFragment : ActivityDelegatingDialogFragment() { | |
override fun createCustomView(): View? { | |
val layoutRes = this.arguments.getInt(LAYOUT_RES) | |
return this.activity.layoutInflater.inflate(layoutRes, null) | |
} | |
companion object { | |
private const val LAYOUT_RES = "layres" | |
fun newInstance(@LayoutRes layoutRes: Int): SimpleLayoutDialogFragment { | |
val frag = SimpleLayoutDialogFragment() | |
val args = Bundle() | |
args.putInt(LAYOUT_RES, layoutRes) | |
frag.arguments = args | |
return frag | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment