package com.topjohnwu.magisk.view import android.app.Activity import android.content.DialogInterface import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.graphics.drawable.InsetDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatDialog import androidx.appcompat.content.res.AppCompatResources import androidx.databinding.Bindable import androidx.databinding.PropertyChangeRegistry import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.color.MaterialColors import com.google.android.material.shape.MaterialShapeDrawable import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding import com.topjohnwu.magisk.databinding.DiffItem import com.topjohnwu.magisk.databinding.ItemWrapper import com.topjohnwu.magisk.databinding.ObservableHost import com.topjohnwu.magisk.databinding.RvItem import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.setAdapter import com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener typealias DialogButtonClickListener = (DialogInterface) -> Unit class MagiskDialog( context: Activity, theme: Int = 0 ) : AppCompatDialog(context, theme) { private val binding: DialogMagiskBaseBinding = DialogMagiskBaseBinding.inflate(LayoutInflater.from(context)) private val data = Data() val activity: BaseActivity get() = ownerActivity as BaseActivity init { binding.setVariable(BR.data, data) setCancelable(true) setOwnerActivity(context) } inner class Data : ObservableHost { override var callbacks: PropertyChangeRegistry? = null @get:Bindable var icon: Drawable? = null set(value) = set(value, field, { field = it }, BR.icon) @get:Bindable var title: CharSequence = "" set(value) = set(value, field, { field = it }, BR.title) @get:Bindable var message: CharSequence = "" set(value) = set(value, field, { field = it }, BR.message) val buttonPositive = ButtonViewModel() val buttonNeutral = ButtonViewModel() val buttonNegative = ButtonViewModel() } enum class ButtonType { POSITIVE, NEUTRAL, NEGATIVE } interface Button { var icon: Int var text: Any var isEnabled: Boolean var doNotDismiss: Boolean fun onClick(listener: DialogButtonClickListener) } inner class ButtonViewModel : Button, ObservableHost { override var callbacks: PropertyChangeRegistry? = null @get:Bindable override var icon = 0 set(value) = set(value, field, { field = it }, BR.icon, BR.gone) @get:Bindable var message: String = "" set(value) = set(value, field, { field = it }, BR.message, BR.gone) override var text: Any get() = message set(value) { message = when (value) { is Int -> context.getText(value) else -> value }.toString() } @get:Bindable val gone get() = icon == 0 && message.isEmpty() @get:Bindable override var isEnabled = true set(value) = set(value, field, { field = it }, BR.enabled) override var doNotDismiss = false private var onClickAction: DialogButtonClickListener = {} override fun onClick(listener: DialogButtonClickListener) { onClickAction = listener } fun clicked() { onClickAction(this@MagiskDialog) if (!doNotDismiss) { dismiss() } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) super.setContentView(binding.root) val default = MaterialColors.getColor(context, com.google.android.material.R.attr.colorSurface, javaClass.canonicalName) val surfaceColor = MaterialColors.getColor(context, R.attr.colorSurfaceSurfaceVariant, default) val materialShapeDrawable = MaterialShapeDrawable(context, null, androidx.appcompat.R.attr.alertDialogStyle, com.google.android.material.R.style.MaterialAlertDialog_MaterialComponents) materialShapeDrawable.initializeElevationOverlay(context) materialShapeDrawable.fillColor = ColorStateList.valueOf(surfaceColor) materialShapeDrawable.elevation = context.resources.getDimension(R.dimen.margin_generic) materialShapeDrawable.setCornerSize(context.resources.getDimension(R.dimen.l_50)) val inset = context.resources.getDimensionPixelSize(com.google.android.material.R.dimen.appcompat_dialog_background_inset) window?.apply { setBackgroundDrawable(InsetDrawable(materialShapeDrawable, inset, inset, inset, inset)) setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) } } override fun setTitle(@StringRes titleId: Int) { data.title = context.getString(titleId) } override fun setTitle(title: CharSequence?) { data.title = title ?: "" } fun setMessage(@StringRes msgId: Int, vararg args: Any) { data.message = context.getString(msgId, *args) } fun setMessage(message: CharSequence) { data.message = message } fun setIcon(@DrawableRes drawableRes: Int) { data.icon = AppCompatResources.getDrawable(context, drawableRes) } fun setIcon(drawable: Drawable) { data.icon = drawable } fun setButton(buttonType: ButtonType, builder: Button.() -> Unit) { val button = when (buttonType) { ButtonType.POSITIVE -> data.buttonPositive ButtonType.NEUTRAL -> data.buttonNeutral ButtonType.NEGATIVE -> data.buttonNegative } button.apply(builder) } class DialogItem( override val item: CharSequence, val position: Int ) : RvItem(), DiffItem, ItemWrapper { override val layoutRes = R.layout.item_list_single_line } fun interface DialogClickListener { fun onClick(position: Int) } fun setListItems( list: Array, listener: DialogClickListener ) = setView( RecyclerView(context).also { it.isNestedScrollingEnabled = false it.layoutManager = LinearLayoutManager(context) val items = list.mapIndexed { i, cs -> DialogItem(cs, i) } val extraBindings = bindExtra { sa -> sa.put(BR.listener, DialogClickListener { pos -> listener.onClick(pos) dismiss() }) } it.setAdapter(items, extraBindings) } ) fun setView(view: View) { binding.dialogBaseContainer.removeAllViews() binding.dialogBaseContainer.addView( view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ) } fun resetButtons() { ButtonType.values().forEach { setButton(it) { text = "" icon = 0 isEnabled = true doNotDismiss = false onClick {} } } } // Prevent calling setContentView @Deprecated("Please use setView(view)", level = DeprecationLevel.ERROR) override fun setContentView(layoutResID: Int) {} @Deprecated("Please use setView(view)", level = DeprecationLevel.ERROR) override fun setContentView(view: View) {} @Deprecated("Please use setView(view)", level = DeprecationLevel.ERROR) override fun setContentView(view: View, params: ViewGroup.LayoutParams?) {} }