Used kotlin with() whenever possible.

This commit is contained in:
Erik C. Thauvin 2016-09-07 22:38:06 -07:00
parent 0caa0190d5
commit 9f68f0a41b
2 changed files with 288 additions and 180 deletions

View file

@ -147,106 +147,114 @@ class MainActivity : AppCompatActivity(), AnkoLogger {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val fields = arrayListOf<EditText>() // editText, size
val fields = arrayListOf<Pair<EditText, Int>>()
initConfigurations() initConfigurations()
verticalLayout { with(config.params) {
padding = dip(20) verticalLayout {
padding = dip(20)
textView { // config name
text = config.params.name.toUpperCase() textView {
bottomPadding = dip(5) text = name.toUpperCase()
typeface = Typeface.DEFAULT_BOLD bottomPadding = dip(5)
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24f) typeface = Typeface.DEFAULT_BOLD
isFocusableInTouchMode = true setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24f)
singleLine = true isFocusableInTouchMode = true
ellipsize = TextUtils.TruncateAt.END singleLine = true
} ellipsize = TextUtils.TruncateAt.END
}
textInputLayout { // phone
horizontalPadding = dip(40) textInputLayout {
val phone = editText() { horizontalPadding = dip(40)
lparams(width = matchParent) val edtPhone = editText() {
inputType = InputType.TYPE_CLASS_PHONE lparams(width = matchParent)
hint = getString(R.string.hint_phone_number) inputType = InputType.TYPE_CLASS_PHONE
hint = getString(R.string.hint_phone_number)
if (config.params.phone.isNotBlank()) { if (phone.isNotBlank()) {
setText(config.params.phone) setText(phone)
}
setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_call_black_24dp, 0)
setOnFocusChangeListener { view, hasFocus ->
if (!hasFocus) {
phone = (view as EditText).text.toString()
saveConfig()
}
}
}
fields.add(Pair(edtPhone, 0))
}
// master code
textInputLayout {
horizontalPadding = dip(40)
val edtMasterCode = editText() {
lparams(width = matchParent)
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
hint = getString(R.string.hint_master_code)
filters = arrayOf(InputFilter.LengthFilter(size))
setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_verified_user_black_24dp, 0)
imeOptions = EditorInfo.IME_ACTION_DONE
if (master.isNotBlank()) {
setText(master)
}
setOnFocusChangeListener { view, hasFocus ->
if (!hasFocus) {
master = (view as EditText).text.toString()
saveConfig()
}
}
setOnEditorActionListener { v, id, event ->
if (id == EditorInfo.IME_ACTION_DONE) {
clearFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(windowToken, 0)
true
} else {
false
}
}
}
fields.add(Pair(edtMasterCode, size))
}
// programming title
textView {
topPadding = dip(10)
text = getString(R.string.programming_heading)
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18f)
typeface = Typeface.DEFAULT_BOLD
}.lparams(width = matchParent)
// options list
listView {
val opts = config.opts.sorted()
val titles = arrayListOf<String>()
opts.all {
titles.add(it.title)
} }
setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_call_black_24dp, 0) adapter = ArrayAdapter<String>(this@MainActivity, android.R.layout.simple_list_item_1, titles)
setOnFocusChangeListener { view, hasFocus -> isTextFilterEnabled = true
if (!hasFocus) { isScrollbarFadingEnabled = false
config.params.phone = (view as EditText).text.toString() onItemClickListener = AdapterView.OnItemClickListener { parent, v, position, id ->
if (validateFields(fields)) {
saveConfig() saveConfig()
startActivity<ProgrammingActivity>(
"net.thauvin.erik.android.tesremoteprogrammer.models.Params" to config.params,
"net.thauvin.erik.android.tesremoteprogrammer.models.Option" to opts[position])
} }
} }
} }.lparams(width = matchParent)
fields.add(phone)
} }
textInputLayout {
horizontalPadding = dip(40)
val masterCode = editText() {
lparams(width = matchParent)
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
hint = getString(R.string.hint_master_code)
filters = arrayOf(InputFilter.LengthFilter(config.params.size))
setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_verified_user_black_24dp, 0)
imeOptions = EditorInfo.IME_ACTION_DONE
if (config.params.master.isNotBlank()) {
setText(config.params.master)
}
setOnFocusChangeListener { view, hasFocus ->
if (!hasFocus) {
config.params.master = (view as EditText).text.toString()
saveConfig()
}
}
setOnEditorActionListener { v, id, event ->
if (id == EditorInfo.IME_ACTION_DONE) {
clearFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(windowToken, 0)
true
} else {
false
}
}
}
fields.add(masterCode)
}
textView {
topPadding = dip(10)
text = getString(R.string.programming_heading)
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18f)
typeface = Typeface.DEFAULT_BOLD
}.lparams(width = matchParent)
val opts = config.opts.sorted()
val titles = arrayListOf<String>()
opts.all {
titles.add(it.title)
}
listView {
adapter = ArrayAdapter<String>(this@MainActivity, android.R.layout.simple_list_item_1, titles)
isTextFilterEnabled = true
isScrollbarFadingEnabled = false
onItemClickListener = AdapterView.OnItemClickListener { parent, v, position, id ->
if (validateFields(fields, config.params.size)) {
saveConfig()
startActivity<ProgrammingActivity>(
"net.thauvin.erik.android.tesremoteprogrammer.models.Params" to config.params,
"net.thauvin.erik.android.tesremoteprogrammer.models.Option" to opts[position])
}
}
}.lparams(width = matchParent)
} }
} }
@ -347,105 +355,203 @@ class MainActivity : AppCompatActivity(), AnkoLogger {
val len = errors.length val len = errors.length
with(config) { with(config) {
if (params.name.isBlank()) { // params
errors.append(getString(R.string.validate_missing_param, "name")) with(params) {
} // name
if (name.isBlank()) {
if (params.type.isBlank()) { errors.append(getString(R.string.validate_missing_param, "name"))
errors.append(getString(R.string.validate_missing_param, "type")) }
} else if (!Dtmf.isValidType(params.type)) {
errors.append(getString(R.string.validate_invalid_param, "type")) // type
} if (type.isBlank()) {
errors.append(getString(R.string.validate_missing_param, "type"))
if (params.size < 1) { } else if (!Dtmf.isValidType(type)) {
errors.append(getString(R.string.validate_invalid_param, "size")) errors.append(getString(R.string.validate_invalid_param, "type"))
}
}
if (params.ack.isBlank()) {
errors.append(getString(R.string.validate_missing_param, "ack")) // size
if (size < 1) {
errors.append(getString(R.string.validate_invalid_param, "size"))
}
// ack
if (ack.isBlank()) {
errors.append(getString(R.string.validate_missing_param, "ack"))
}
} }
// options
if (opts.size == 0) { if (opts.size == 0) {
errors.append(getString(R.string.validate_missing_opts)) errors.append(getString(R.string.validate_missing_opts))
} } else {
opts.forEachIndexed { i, option ->
opts.forEachIndexed { i, option -> // gson will create a null object on trailing comma
if (option.fields.size == 0) { // see: https://github.com/google/gson/issues/494
errors.append(getString(R.string.validate_missing_fields, i + 1)) if (option == null) {
} errors.append(getString(R.string.validate_syntax_error, "opts[]"))
} else {
if (option.nosteps && option.nodial) { with(option) {
errors.append(getString(R.string.validate_invalid_option, i + 1, "nodial/nosteps")) // title
} if (title.isBlank()) {
errors.append(getString(
if (option.dtmf.isBlank()) { R.string.validate_missing_opts_prop,
errors.append(getString(R.string.validate_invalid_dtmf, i + 1, "''")) i + 1,
} "title"))
option.fields.forEachIndexed { j, field ->
if (field.size <= 0) {
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1,
"size=${field.size}"))
}
if (field.digits.isNotBlank() && !field.digits.isDigits()) {
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1, "digits"))
}
if (field.minSize >= 0 && field.minSize > field.size) {
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1,
"minSize=${field.minSize}/size-${field.size}"))
}
if (!field.alpha) {
if (field.minSize == 0) {
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1,
"minSize=${field.minSize}"))
}
if (field.min >= 0 || field.max >= 0) {
if (field.max < 1) {
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1,
"max=${field.max}"))
} }
if (field.min < 0) { // nosteps/nodial
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1, if (nosteps && nodial) {
"min=${field.min}")) errors.append(getString(
R.string.validate_invalid_option,
i + 1,
"nodial/nosteps"))
} }
if (field.min > field.max) { // dtmf
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1, if (dtmf.isBlank()) {
"min=${field.min}/max=${field.max}")) errors.append(getString(
} R.string.validate_missing_opts_prop,
i + 1,
"dtmf"))
} else if (fields.size == 0) { // fields missing
errors.append(getString(
R.string.validate_missing_opts_prop,
i + 1,
"fields"))
} else {
val blank = "\\0"
val mock = Dtmf.mock(option, blank)
if (!params.type.isDKS() && !field.zeros) { if (!Dtmf.validate(mock,
if (field.min > 0 && field.minSize > 0) { "${MainActivity.PAUSE}${params.ack}${params.alt}$blank")) {
if (field.min.toString().length != field.minSize) { errors.append(getString(
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1, R.string.validate_invalid_opts_prop,
"minSize=${field.minSize}/min=${field.min}")) i + 1,
} "dtmf",
mock.replace(blank, "&#10003;")))
} }
}
if (field.size > 0 && field.max > 0) { // fields
if (field.max.toString().length != field.size) { fields.forEachIndexed { j, field ->
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1, if (field == null) {
"size=${field.size}/max=${field.max}")) errors.append(getString(
R.string.validate_syntax_error,
"opts[${i+j}], field[$j]"))
} else {
with(field) {
// size
if (size <= 0) {
errors.append(getString(
R.string.validate_invalid_field_prop,
i + 1,
j + 1,
resources.getQuantityString(R.plurals.error_prop, 1),
"size=$size"))
}
// digits
if (digits.isNotBlank() && !digits.isDigits()) {
errors.append(getString(
R.string.validate_invalid_field_prop,
i + 1,
j + 1,
resources.getQuantityString(R.plurals.error_prop, 1),
"digits='$digits'"))
}
// minSize
if (minSize >= 0 && minSize > size) {
errors.append(getString(
R.string.validate_invalid_field_prop,
i + 1,
j + 1,
resources.getQuantityString(R.plurals.error_prop, 2),
"minSize=$minSize > size=$size"))
}
// numeric fields only
if (!alpha) {
if (minSize == 0) {
errors.append(getString(
R.string.validate_invalid_field_prop,
i + 1,
j + 1,
resources.getQuantityString(R.plurals.error_prop, 1),
"minSize=$minSize"))
}
// min/max
if (min >= 0 || max >= 0) {
if (max < 1) {
errors.append(getString(
R.string.validate_invalid_field_prop,
i + 1,
j + 1,
resources.getQuantityString(R.plurals.error_prop, 1),
"max=$max"))
}
if (min < 0) {
errors.append(getString(
R.string.validate_invalid_field_prop,
i + 1,
j + 1,
resources.getQuantityString(R.plurals.error_prop, 1),
"min=$min"))
}
if (min > max) {
errors.append(getString(
R.string.validate_invalid_field_prop,
i + 1,
j + 1,
resources.getQuantityString(R.plurals.error_prop, 2),
"min=$min > max=$max"))
}
}
// no leading zeros
if (!params.type.isDKS() && !zeros) {
// minSize/min
if (min >= 0 && minSize > 0) {
if (min.toString().length != minSize) {
errors.append(getString(
R.string.validate_invalid_field_prop,
i + 1,
j + 1,
resources.getQuantityString(R.plurals.error_prop, 2),
"minSize=$minSize/min=$min"))
}
}
// size/max
if (size > 0 && max > 0) {
if (max.toString().length != size) {
errors.append(getString(
R.string.validate_invalid_field_prop,
i + 1,
j + 1,
resources.getQuantityString(R.plurals.error_prop, 2),
"size=$size/max=$max"))
}
}
}
}
// unused fields
if (!dtmf.contains(Dtmf.DTMF_FIELD.format(j + 1))) {
errors.append(getString(
R.string.validate_unused_field,
i + 1,
j + 1))
}
} }
} }
} }
} }
} }
if (!option.dtmf.contains(Dtmf.DTMF_FIELD.format(j + 1))) {
errors.append(getString(R.string.validate_unused_field, i + 1, j + 1))
}
}
val blank = "\\0"
val dtmf = Dtmf.mock(option, blank)
if (!Dtmf.validate(dtmf, "${MainActivity.Companion.PAUSE}${params.ack}${params.alt}$blank")) {
errors.append(getString(R.string.validate_invalid_dtmf, i + 1, dtmf.replace(blank, "&#10003;")))
} }
} }
} }
@ -453,17 +559,19 @@ class MainActivity : AppCompatActivity(), AnkoLogger {
return errors.length == len return errors.length == len
} }
fun validateFields(fields: ArrayList<EditText>, size: Int): Boolean { fun validateFields(fields: ArrayList<Pair<EditText, Int>>): Boolean {
var isValid = true var isValid = true
fields.forEach { fields.forEach {
if (it.text.isNullOrBlank()) { with(it) {
it.error = getString(R.string.error_required) if (first.text.isNullOrBlank()) {
isValid = false first.error = getString(R.string.error_required)
} else if (size > 0 && (fields[1].text.length != size)) { isValid = false
isValid = false } else if (second > 0 && first.text.length != second) {
fields[1].error = getString(R.string.error_invalid_size, size, first.error = getString(R.string.error_invalid_size, second,
resources.getQuantityString(R.plurals.error_digit, size), "") resources.getQuantityString(R.plurals.error_digit, second), "")
isValid = false
}
} }
} }

View file

@ -22,7 +22,7 @@ import android.os.Parcelable
import java.io.Serializable import java.io.Serializable
data class Option(var title: String, data class Option(var title: String,
var fields: List<Field>, var fields: List<Field?>,
var nodial: Boolean, var nodial: Boolean,
var nosteps: Boolean, var nosteps: Boolean,
var dtmf: String) : Parcelable, Serializable, Comparable<Option> { var dtmf: String) : Parcelable, Serializable, Comparable<Option> {