Skip to content

Instantly share code, notes, and snippets.

@CapnSpellcheck
Last active April 30, 2018 08:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CapnSpellcheck/6b024eeb5e6bfd68f4b5d3d3a0da12d4 to your computer and use it in GitHub Desktop.
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…
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())
}
}
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