Skip to content

Instantly share code, notes, and snippets.

@AndSky90
Created November 16, 2019 14:06
Show Gist options
  • Save AndSky90/cddbcb48127357991623e47441800c95 to your computer and use it in GitHub Desktop.
Save AndSky90/cddbcb48127357991623e47441800c95 to your computer and use it in GitHub Desktop.
CustomView editable Form factory
class DateAttrDelegate(field: EditText) :
DynamicAttrDelegate(field) {
init {
field.isClickable = false
field.isFocusable = false
field.setOnEditorActionListener { _: TextView, _: Int, _: KeyEvent -> true }
field.setOnClickListener { openDateSelector() }
}
private var dateIsoString: String = ""
override fun isFieldValid(): Boolean? =
field.text.isValid(ValidatorType.VALIDATE_DATE, data.required)
override fun setFieldData(attribute: FormComponent) {
data = attribute
dateIsoString = getAnswerFromCache() ?: data.value.orEmpty()
setFormattedDateToField(dateIsoString)
}
private fun setFormattedDateToField(dateIsoString : String) {
field.setText(formatOnlyDate(dateIsoString, true), TextView.BufferType.EDITABLE)
}
private fun openDateSelector() {
val timeDate: GregorianCalendar = getDateFromIsoString(dateIsoString)
val year = timeDate.get(Calendar.YEAR)
val month = timeDate.get(Calendar.MONTH)
val day = timeDate.get(Calendar.DAY_OF_MONTH)
val datePicker = DatePickerDialog(field.context, R.style.DatePickerTheme, onDateSelected, year, month, day).apply {
setCancelable(true)
setButton(BUTTON_NEUTRAL, " ") { _, _ ->
dateIsoString = ""
saveAnswerToCache(dateIsoString)
setFormattedDateToField(dateIsoString)
dismiss()
}
}
datePicker.setOnShowListener {
val button = datePicker.getButton(BUTTON_NEUTRAL)
val drawable = ContextCompat.getDrawable(field.context, R.drawable.ic_delete_red_24)
drawable!!.setBounds(
(drawable.intrinsicWidth * 0.5).toInt(),
0,
(drawable.intrinsicWidth * 1.5).toInt(),
drawable.intrinsicHeight
)
button.setCompoundDrawables(drawable, null, null, null)
}
datePicker.show()
}
private val onDateSelected =
DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
dateIsoString = serialiseDateTime(year, month, dayOfMonth)
saveAnswerToCache(dateIsoString)
setFormattedDateToField(dateIsoString)
}
override fun getFieldValue(): Pair<String, String> {
return Pair(data.guid, formatForSendToApi(dateIsoString))
}
}
abstract class DynamicAttrDelegate(val field: EditText) : IDynamicAttrField {
lateinit var data: FormComponent
fun saveAnswerToCache(value: String?) {
MemoryCache.setCachedAttribute(data.guid, value)
}
fun getAnswerFromCache(): String? =
MemoryCache.getCachedAttribute(data.guid)
}
object DynamicAttrFieldFactory {
fun configureFieldDelegate(
field: EditText, attribute: FormComponent
): DynamicAttrDelegate {
val inputField: DynamicAttrDelegate = when (attribute.type) {
AttributeTypes.FORM_STATIC_ATTR -> NameAttrDelegate(field)
AttributeTypes.FORM_TEXT -> TextAttrDelegate(field)
AttributeTypes.FORM_NUMBER -> NumberAttrDelegate(field)
AttributeTypes.FORM_PHONE -> PhoneAttrDelegate(field)
AttributeTypes.FORM_LINK -> LinkAttrDelegate(field)
AttributeTypes.FORM_EMAIL -> EmailAttrDelegate(field)
AttributeTypes.FORM_DATE -> DateAttrDelegate(field)
AttributeTypes.FORM_SELECT -> SelectorAttrDelegate(field)
else -> TextAttrDelegate(field)
}
inputField.setFieldData(attribute)
return inputField
}
}
class EditorAttributeView(context: Context, attrs: AttributeSet?) :
ConstraintLayout(context, attrs),
IEditorAttributeView {
init {
inflate(R.layout.dynamic_attr_editor, true)
setOnClickListener {
attrField.requestFocus()
attrField.performClick()
}
setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) {
divider.setBackgroundColor(ContextCompat.getColor(context, R.color.colorAccent))
errorText.text = ""
} else {
v.hideKeyboard()
divider.setBackgroundColor(ContextCompat.getColor(context, R.color.colorDivider))
errorText.text = ""
}
}
}
private lateinit var fieldDelegate: IDynamicAttrField
override fun setData(attribute: FormComponent) {
attrTitle.text =
if (attribute.required == true)
TextUtils.concat(
attribute.title, getRedString(attrTitle.context, " *")
) else
attribute.title
fieldDelegate = DynamicAttrFieldFactory.configureFieldDelegate(attrField, attribute)
}
override fun isValidOrShowError(): Boolean {
val valid = fieldDelegate.isFieldValid()
setValidationErrorVisibility(valid)
return (valid == true)
}
override fun getAttributeValueData(): Pair<String, String> {
return fieldDelegate.getFieldValue()
}
/**Внутренний метод, отображение ошибки*/
private fun setValidationErrorVisibility(valid: Boolean?) {
when (valid) {
true -> {
divider.setBackgroundColor(ContextCompat.getColor(context, R.color.colorDivider))
errorText.text = ""
}
false -> {
divider.setBackgroundColor(ContextCompat.getColor(context, R.color.colorAccentRed))
errorText.text = context.getString(R.string.error_wrong_input_hint)
}
null -> {
divider.setBackgroundColor(ContextCompat.getColor(context, R.color.colorAccentRed))
errorText.text = context.getString(R.string.error_empty_input_hint)
}
}
}
}
interface IDynamicAttrField {
fun setFieldData(attribute: FormComponent)
fun isFieldValid(): Boolean?
fun getFieldValue(): Pair<String, String>
}
object ProfileEditorAttrFactory {
private const val KEY_NAME = "Name"
private const val KEY_SURNAME = "Surname"
private const val KEY_PATRONYMIC = "Patronymic"
fun createDynamicAttributes(containerView: ViewGroup, attribute: FormComponent) {
if (attribute.type == "form:group") {
if (!attribute.attributes.isNullOrEmpty())
attribute.attributes?.forEach {
addAttribute(containerView, it)
}
else log("Пустая группа атрибутов пользователя ${attribute.guid}, ${attribute.title}")
} else addAttribute(containerView, attribute)
}
fun createStaticAttributes(
containerView: ViewGroup, surname: String, name: String, patronymic: String
) {
val surnameComponent = FormComponent(
guid = KEY_SURNAME,
type = FORM_STATIC_ATTR,
name = KEY_SURNAME,
value = surname,
required = true,
title = containerView.context.getString(R.string.title_surname_text)
)
val nameComponent = FormComponent(
guid = KEY_NAME,
type = FORM_STATIC_ATTR,
name = KEY_NAME,
value = name,
required = true,
title = containerView.context.getString(R.string.title_name_text)
)
val patronymicComponent = FormComponent(
guid = KEY_PATRONYMIC,
type = FORM_STATIC_ATTR,
name = KEY_PATRONYMIC,
value = patronymic,
required = false,
title = containerView.context.getString(R.string.title_patronymic_text)
)
addAttribute(containerView, surnameComponent)
addAttribute(containerView, nameComponent)
addAttribute(containerView, patronymicComponent)
}
private fun addAttribute(containerView: ViewGroup, attribute: FormComponent) {
val view: EditorAttributeView =
EditorAttributeView(containerView.context, null)
.apply { setData(attribute) }
containerView.addView(view)
}
}
class SelectorAttrDelegate(field: EditText) :
DynamicAttrDelegate(field) {
init {
field.isClickable = false
field.isFocusable = false
field.setOnEditorActionListener { _: TextView, _: Int, _: KeyEvent -> true }
field.setOnClickListener { openSelector() }
}
private var selectedGuid = ""
private var selectedIndex = -1
private var optionsTitleArray = listOf<String>()
override fun isFieldValid(): Boolean? =
if (selectedGuid.isBlank()) {
if (data.required == true) null else true
} else {
data.options?.firstOrNull { it.guid == selectedGuid } != null
}
override fun setFieldData(attribute: FormComponent) {
data = attribute
selectedGuid = getAnswerFromCache() ?: getAnswerFromModel().orEmpty()
if (selectedGuid.isNotBlank())
selectedIndex = data.options?.indexOfFirst { it.guid == selectedGuid } ?: -1
if (selectedIndex >= 0)
field.setText(data.options?.get(selectedIndex)?.title, TextView.BufferType.EDITABLE)
optionsTitleArray = data.options?.map { it.title.orEmpty() } ?: listOf()
}
private fun getAnswerFromModel(): String? =
data.options?.firstOrNull { it.selected == true }?.guid
private fun openSelector() {
var alertDialog : AlertDialog? = null
val listView = RecyclerView(field.context)
listView.layoutParams = ViewGroup.LayoutParams(
AbsListView.LayoutParams.WRAP_CONTENT,
AbsListView.LayoutParams.WRAP_CONTENT
)
listView.setPadding(0, 8.dpToPx(), 0, 8.dpToPx())
listView.clipToPadding = true
listView.setBackgroundColor(Color.WHITE)
listView.layoutManager = LinearLayoutManager(field.context)
val adapter = SelectorDialogAdapter(
listItems = optionsTitleArray,
onClick = { position ->
selectedIndex = position
val selected = data.options?.get(selectedIndex)
selectedGuid = selected?.guid.orEmpty()
saveAnswerToCache(selectedGuid)
Handler().postDelayed({
field.setText(selected?.title, TextView.BufferType.EDITABLE)
alertDialog?.dismiss()
},300)
}
)
adapter.checked = selectedIndex
listView.adapter = adapter
val builder = AlertDialog.Builder(field.context)
builder.setView(listView)
alertDialog = builder.create()
alertDialog.show()
}
override fun getFieldValue(): Pair<String, String> {
return Pair(data.guid, selectedGuid)
}
}
class SelectorDialogAdapter(
val listItems: List<String>,
val onClick: (Int) -> Unit
) : RecyclerView.Adapter<SelectorDialogAdapter.SelectorDialogViewHolder>() {
var checked = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
SelectorDialogViewHolder(parent.inflate(R.layout.dialog_attr_selector, false), onClick)
override fun onBindViewHolder(holder: SelectorDialogViewHolder, position: Int) =
holder.bindItem(listItems[position])
override fun getItemCount() = listItems.size
inner class SelectorDialogViewHolder(
val view: View,
val onClick: (Int) -> Unit
) : RecyclerView.ViewHolder(view) {
fun bindItem(title: String) {
(itemView as CheckedTextView).text = title
itemView.isChecked = (checked == adapterPosition)
itemView.setOnClickListener {
if (checked != adapterPosition)
notifyItemChanged(checked)
checked = adapterPosition
onClick(adapterPosition)
notifyItemChanged(adapterPosition)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment