Skip to content

Instantly share code, notes, and snippets.

@darylsze
Last active August 5, 2021 01:24
Show Gist options
  • Save darylsze/3588b5a3428ed48c87db509d14a32938 to your computer and use it in GitHub Desktop.
Save darylsze/3588b5a3428ed48c87db509d14a32938 to your computer and use it in GitHub Desktop.
Kotlin DSL to runtime build a configuration-easy menu by enum options. You can control a set of configuration for each menu item, including prerequisite condition, ordering, general callback or individual callback.
fun onCreate() {
baseViewHolder.menu {
validate {
match(isAdmin) {
addItem(MenuOption.PIN)
}
}
addItem(MenuOption.COPY)
validate {
match(isLoggedIn) {
addItem(MenuOption.BOOKMARK)
}
match(isLoggedIn && _model.isSent) {
addItem(MenuOption.REPLY)
}
}
addItem(MenuOption.REPORT)
validate {
match(Env.isDebug) {
addItem(MenuOption.DEBUG)
}
}
addCommonCallback { menuOption ->
onMenuItemClickListener?.invoke(_model, menuOption)
}
}
}
import com.package.MenuOption
class MenuBuilder {
val menuConfigs = mutableListOf<MenuOptionConfig>()
var commonCallback: ((MenuOption) -> Unit)? = null
fun addItem(menuOption: MenuOption, callback: ((MenuOption) -> Unit)? = null, eachBuildVisibilityPredicate: (() -> Boolean)? = null) {
menuConfigs.add(MenuOptionConfig(
option = menuOption,
callback = callback,
preMenuShowValidate = eachBuildVisibilityPredicate
))
}
fun addItem(vararg menuOptionConfigs: MenuOptionConfig) {
menuConfigs.addAll(menuOptionConfigs)
}
fun addCommonCallback(callback: ((MenuOption) -> Unit)) {
commonCallback = callback
}
fun build(): List<MenuOptionConfig> {
return menuConfigs
.filter { it.preMenuShowValidate?.invoke() ?: true }
.map { config ->
// individual option can have optional separate callback
// but when user actual click, we need to map those item without customize callback to common callback (if provided in advance).
MenuOptionConfig(
option = config.option,
callback = config.callback ?: commonCallback,
preMenuShowValidate = config.preMenuShowValidate
)
}
}
fun validate(setup: MenuConditionValidator.() -> Unit = {}) {
val v2 = MenuConditionValidator()
v2.setup()
val configs = v2.items
.map {
MenuOptionConfig(
option = it.item,
callback = null,
preMenuShowValidate = it.evaluateLater
)
}
this.addItem(*configs.toTypedArray())
}
}
import com.package.MenuOption
open class MenuConditionValidator {
interface IMenuConditionValidation {
val config: MutableList<Config>
fun addItem(menuOption: MenuOption, evaluateLater: (() -> Boolean)? = null): IMenuConditionValidation
fun addItem(vararg menuOption: MenuOption): IMenuConditionValidation
}
data class Config(
val item: MenuOption,
val evaluateLater: (() -> Boolean)? = null
)
val items = mutableListOf<Config>()
fun match(
predicate: Boolean,
setup: IMenuConditionValidation.() -> Unit = {}
) {
val type = when (predicate) {
true -> Match()
else -> Mismatch()
}
type.setup()
items.addAll(type.config)
}
class Match: IMenuConditionValidation {
override val config: MutableList<Config> = mutableListOf()
override fun addItem(menuOption: MenuOption, evaluateLater: (() -> Boolean)?): IMenuConditionValidation {
config.add(Config(
item = menuOption,
evaluateLater = evaluateLater
))
return this
}
override fun addItem(vararg menuOption: MenuOption): IMenuConditionValidation {
config.addAll(menuOption.map {
Config(
item = it,
evaluateLater = null
)
})
return this
}
}
class Mismatch: IMenuConditionValidation {
override val config: MutableList<Config> = mutableListOf()
override fun addItem(menuOption: MenuOption, evaluateLater: (() -> Boolean)?): IMenuConditionValidation {
// do nothing
// fixme use sealedClass to avoid unneccessary required abstract methods
return this
}
override fun addItem(vararg menuOption: MenuOption): IMenuConditionValidation {
// do nothing
// fixme use sealedClass to avoid unneccessary required abstract methods
return this
}
}
}
enum class MenuOption(val displayNameKey: String) {
BOOKMARK(displayNameKey = "chat_common_bookmark_message"),
COPY(displayNameKey = "chat_common_copy"),
DEBUG(displayNameKey = "debug"),
DELETE(displayNameKey = "chat_common_delete"),
PIN(displayNameKey = "chat_common_pin"),
REPLY(displayNameKey = "chat_common_reply"),
REPORT(displayNameKey = "chat_common_report")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment