Rewritten MessageListActivity to be compliant with IOSP
Full compliance was not possible at every step but largely the operations are now separated and can be unit-tested in isolation. As part of this rewrite, the onClick event handler is now set programmatically and no longer in the layout file.
This commit is contained in:
parent
36b5f1cda8
commit
147e5ce56a
|
@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModelProviders
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.udevel.widgetlab.TypingIndicatorView
|
||||
import kotlinx.android.synthetic.main.activity_message_list.view.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.collections.ArrayList
|
||||
|
@ -26,11 +27,21 @@ class MessageListActivity : AppCompatActivity() {
|
|||
|
||||
private lateinit var adapter: ArrayAdapter<CharSequence>
|
||||
|
||||
private var showSpinner = true
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_message_list)
|
||||
|
||||
initVariables()
|
||||
adapter = initSpinner(model, spinner)
|
||||
bindMessageObserver(viewAdapter, model)
|
||||
initButton(horizontalLayout, ::step, spinner)
|
||||
val messages = initFirstChoice(viewAdapter, model, horizontalLayout)
|
||||
receiveMessages(messages, model, typingIndicator, recyclerView, horizontalLayout)
|
||||
}
|
||||
|
||||
private fun initVariables() {
|
||||
val viewManager = LinearLayoutManager(this)
|
||||
viewManager.stackFromEnd = true
|
||||
viewManager.reverseLayout = false
|
||||
|
@ -54,42 +65,88 @@ class MessageListActivity : AppCompatActivity() {
|
|||
// specify an viewAdapter (see also next example)
|
||||
adapter = viewAdapter
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSpinner(model: MessageListViewModel,
|
||||
spinner: Spinner): ArrayAdapter<CharSequence> {
|
||||
val spinnerOptions: ArrayList<CharSequence>? = model.spinnerOptions.value
|
||||
if (spinnerOptions != null) {
|
||||
adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, spinnerOptions)
|
||||
val arrayAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, spinnerOptions)
|
||||
// Specify the layout to use when the list of choices appears
|
||||
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
arrayAdapter.setNotifyOnChange(true)
|
||||
// Apply the adapter to the spinner
|
||||
spinner.adapter = arrayAdapter
|
||||
return arrayAdapter
|
||||
}
|
||||
// Specify the layout to use when the list of choices appears
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
adapter.setNotifyOnChange(true)
|
||||
// Apply the adapter to the spinner
|
||||
spinner.adapter = adapter
|
||||
|
||||
return ArrayAdapter(this, android.R.layout.simple_spinner_item, ArrayList<CharSequence>())
|
||||
}
|
||||
|
||||
private fun bindMessageObserver(viewAdapter: MessageListAdapter, model: MessageListViewModel) {
|
||||
val messageObserver = Observer<ArrayList<Message>> { messages ->
|
||||
viewAdapter.replaceMessages(messages)
|
||||
}
|
||||
|
||||
model.getMessages().observe(this, messageObserver)
|
||||
|
||||
initFirstChoice()
|
||||
}
|
||||
|
||||
fun sendMessage(view: View) {
|
||||
val text: String = spinner.selectedItem.toString()
|
||||
private fun initButton(horizontalLayout: LinearLayout, step: (String) -> Unit, spinner: Spinner) {
|
||||
horizontalLayout.button_send.setOnClickListener {
|
||||
val text: String = spinner.selectedItem.toString()
|
||||
step(text)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initFirstChoice(adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>,
|
||||
model: MessageListViewModel,
|
||||
horizontalLayout: LinearLayout): List<Message> {
|
||||
val isInitialized = model.initialized.value
|
||||
return if (isInitialized != null && !isInitialized) {
|
||||
model.spinnerOptions.value?.addAll(resources.getStringArray(R.array.components))
|
||||
adapter.notifyDataSetChanged()
|
||||
model.initialized.value = true
|
||||
listOf(Message(getString(R.string.greeting), false, System.currentTimeMillis()))
|
||||
} else {
|
||||
val spinnerVisible = model.spinnerVisible.value
|
||||
if (spinnerVisible != null && spinnerVisible) horizontalLayout.visibility = View.VISIBLE
|
||||
listOf()
|
||||
}
|
||||
}
|
||||
|
||||
private fun step(selection: String) {
|
||||
sendMessage(selection, model, recyclerView, horizontalLayout)
|
||||
val messages = branch(
|
||||
selection,
|
||||
model, adapter, ::getSolutionArray,
|
||||
::componentSelected,
|
||||
::problemSelected,
|
||||
::yesNoSelected,
|
||||
::triedAnswer,
|
||||
::doesItWorkAnswer,
|
||||
::didItWorkQuestion,
|
||||
::pleaseTry,
|
||||
::problemSolved,
|
||||
::nextSolutionQuestion,
|
||||
::doNotShowSpinner)
|
||||
receiveMessages(messages, model, typingIndicator, recyclerView, horizontalLayout, showSpinner)
|
||||
}
|
||||
|
||||
private fun sendMessage(text: String, model: MessageListViewModel,
|
||||
recyclerView: RecyclerView, horizontalLayout: LinearLayout) {
|
||||
val message = Message(text, true, System.currentTimeMillis())
|
||||
model.add(message)
|
||||
recyclerView.scrollToPosition(model.size() - 1)
|
||||
horizontalLayout.visibility = View.GONE
|
||||
model.spinnerVisible.value = false
|
||||
|
||||
nextStage(text)
|
||||
}
|
||||
|
||||
private fun receiveMessage(message: Message, showSpinner: Boolean = true) {
|
||||
receiveMessages(listOf(message), showSpinner)
|
||||
}
|
||||
|
||||
private fun receiveMessages(messages: List<Message>, showSpinner: Boolean = true) {
|
||||
private fun receiveMessages(messages: List<Message>,
|
||||
model: MessageListViewModel,
|
||||
typingIndicator: View,
|
||||
recyclerView: RecyclerView,
|
||||
horizontalLayout: LinearLayout,
|
||||
showSpinner: Boolean = true) {
|
||||
model.uiScope.launch {
|
||||
for (message in messages) {
|
||||
typingIndicator.visibility = View.VISIBLE
|
||||
|
@ -98,70 +155,88 @@ class MessageListActivity : AppCompatActivity() {
|
|||
model.add(message)
|
||||
recyclerView.scrollToPosition(model.size() - 1)
|
||||
}
|
||||
if (showSpinner) {
|
||||
if (messages.isNotEmpty() && showSpinner) {
|
||||
horizontalLayout.visibility = View.VISIBLE
|
||||
model.spinnerVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun nextStage(selection: String) {
|
||||
when (selection) {
|
||||
private fun doNotShowSpinner() {
|
||||
showSpinner = false
|
||||
}
|
||||
|
||||
private fun branch(selection: String,
|
||||
model: MessageListViewModel,
|
||||
adapter: ArrayAdapter<CharSequence>,
|
||||
getSolutionArray: (String) -> Array<String>,
|
||||
componentSelected: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>,
|
||||
problemSelected: (MessageListViewModel, ArrayAdapter<CharSequence>,
|
||||
(String) -> Array<String>) -> List<Message>,
|
||||
yesNoSelected: (String,
|
||||
MessageListViewModel,
|
||||
ArrayAdapter<CharSequence>,
|
||||
triedAnswer: (String, MessageListViewModel,
|
||||
didItWorkQuestion: (MessageListViewModel) -> List<Message>,
|
||||
pleaseTry: () -> List<Message>) -> List<Message>,
|
||||
doesItWorkAnswer: (String, MessageListViewModel, ArrayAdapter<CharSequence>,
|
||||
problemSolved: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>,
|
||||
nextSolutionQuestion: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>) -> List<Message>,
|
||||
didItWorkQuestion: (MessageListViewModel) -> List<Message>,
|
||||
pleaseTry: () -> List<Message>,
|
||||
problemSolved: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>,
|
||||
nextSolutionQuestion: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>) -> List<Message>,
|
||||
triedAnswer: (String, MessageListViewModel,
|
||||
didItWorkQuestion: (MessageListViewModel) -> List<Message>,
|
||||
pleaseTry: () -> List<Message>) -> List<Message>,
|
||||
doesItWorkAnswer: (String, MessageListViewModel, ArrayAdapter<CharSequence>,
|
||||
problemSolved: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>,
|
||||
nextSolutionQuestion: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>) -> List<Message>,
|
||||
didItWorkQuestion: (MessageListViewModel) -> List<Message>,
|
||||
pleaseTry: () -> List<Message>,
|
||||
problemSolved: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>,
|
||||
nextSolutionQuestion: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>,
|
||||
doNotShowSpinner: () -> Unit): List<Message> {
|
||||
|
||||
return when (selection) {
|
||||
in resources.getStringArray(R.array.components) -> {
|
||||
// first selection
|
||||
model.component.value = Component.valueOf(selection.toUpperCase())
|
||||
secondChoice()
|
||||
componentSelected(model, adapter)
|
||||
}
|
||||
in resources.getStringArray(R.array.monitor_problems),
|
||||
in resources.getStringArray(R.array.pc_problems),
|
||||
in resources.getStringArray(R.array.printer_problems),
|
||||
in resources.getStringArray(R.array.tv_problems),
|
||||
in resources.getStringArray(R.array.router_problems) -> {
|
||||
// second selection
|
||||
model.problem.value = Problem.valueOf(selection.toUpperCase().replace(' ', '_'))
|
||||
solutionQuestion()
|
||||
problemSelected(model, adapter, getSolutionArray)
|
||||
}
|
||||
in resources.getStringArray(R.array.binary) -> {
|
||||
when (model.currentSolutionState.value) {
|
||||
State.HAVE_YOU_TRIED -> triedAnswer(selection)
|
||||
State.DID_IT_WORK -> doesItWorkAnswer(selection)
|
||||
}
|
||||
yesNoSelected(selection, model, adapter,
|
||||
triedAnswer, doesItWorkAnswer,
|
||||
didItWorkQuestion, pleaseTry,
|
||||
problemSolved, nextSolutionQuestion)
|
||||
}
|
||||
getString(R.string.who_are_you) -> {
|
||||
receiveMessage(Message(
|
||||
doNotShowSpinner()
|
||||
listOf(Message(
|
||||
getString(R.string.gardner_second),
|
||||
false,
|
||||
System.currentTimeMillis()
|
||||
), false)
|
||||
))
|
||||
}
|
||||
getString(R.string.can_you_say_sth_different) -> {
|
||||
receiveMessages(listOf(
|
||||
doNotShowSpinner()
|
||||
listOf(
|
||||
Message(getString(R.string.okay), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.im_gardner), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.okay_saves_time), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.its_funny), false, System.currentTimeMillis())),
|
||||
false)
|
||||
Message(getString(R.string.its_funny), false, System.currentTimeMillis()))
|
||||
}
|
||||
// defacto this case should not happen
|
||||
else -> listOf()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initFirstChoice() {
|
||||
val isInitialized = model.initialized.value
|
||||
if (isInitialized != null && !isInitialized) {
|
||||
model.spinnerOptions.value?.addAll(resources.getStringArray(R.array.components))
|
||||
adapter.notifyDataSetChanged()
|
||||
model.initialized.value = true
|
||||
receiveMessage(Message(getString(R.string.greeting), false, System.currentTimeMillis()))
|
||||
}
|
||||
else {
|
||||
val spinnerVisible = model.spinnerVisible.value
|
||||
if (spinnerVisible != null && spinnerVisible) horizontalLayout.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private fun secondChoice() {
|
||||
private fun componentSelected(model: MessageListViewModel, adapter: ArrayAdapter<CharSequence>): List<Message> {
|
||||
model.spinnerOptions.value?.clear()
|
||||
when (model.component.value) {
|
||||
Component.PRINTER -> model.spinnerOptions.value?.addAll(resources.getStringArray(R.array.printer_problems))
|
||||
|
@ -171,16 +246,18 @@ class MessageListActivity : AppCompatActivity() {
|
|||
Component.ROUTER -> model.spinnerOptions.value?.addAll(resources.getStringArray(R.array.router_problems))
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
receiveMessages(listOf(
|
||||
|
||||
return listOf(
|
||||
Message(getString(R.string.okay), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.select_problem), false, System.currentTimeMillis())
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
private fun solutionQuestion() {
|
||||
private fun problemSelected(model: MessageListViewModel, adapter: ArrayAdapter<CharSequence>,
|
||||
getSolutionArray: (String) -> Array<String>): List<Message> {
|
||||
val format = "component_problem"
|
||||
val stringName = format.replace("component", model.component.value.toString().toLowerCase()).
|
||||
replace("problem", model.problem.value.toString().toLowerCase())
|
||||
val stringName = format.replace("component", model.component.value.toString()).
|
||||
replace("problem", model.problem.value.toString())
|
||||
model.solutions.value?.addAll(getSolutionArray(stringName))
|
||||
|
||||
val firstSolutionQuestion = getString(R.string.have_you_tried, model.solutions.value?. let { it[0] })
|
||||
|
@ -189,56 +266,85 @@ class MessageListActivity : AppCompatActivity() {
|
|||
model.spinnerOptions.value?.addAll(resources.getStringArray(R.array.binary))
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
receiveMessages(listOf(
|
||||
return listOf(
|
||||
Message(getString(R.string.okay), false, System.currentTimeMillis()),
|
||||
Message(firstSolutionQuestion, false, System.currentTimeMillis())
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
private fun triedAnswer(selection: String) {
|
||||
when (selection) {
|
||||
"yes" -> didItWorkQuestion()
|
||||
private fun yesNoSelected(selection: String, model: MessageListViewModel, adapter: ArrayAdapter<CharSequence>,
|
||||
triedAnswer: (String,
|
||||
MessageListViewModel,
|
||||
didItWorkQuestion: (MessageListViewModel) -> List<Message>,
|
||||
pleaseTry: () -> List<Message>) -> List<Message>,
|
||||
doesItWorkAnswer: (String,
|
||||
MessageListViewModel,
|
||||
ArrayAdapter<CharSequence>,
|
||||
problemSolved: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>,
|
||||
nextSolutionQuestion: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>) -> List<Message>,
|
||||
didItWorkQuestion: (MessageListViewModel) -> List<Message>,
|
||||
pleaseTry: () -> List<Message>,
|
||||
problemSolved: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>,
|
||||
nextSolutionQuestion: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>): List<Message> {
|
||||
val state: State? = model.currentSolutionState.value
|
||||
if (state != null) {
|
||||
return when (state) {
|
||||
State.HAVE_YOU_TRIED -> triedAnswer(selection, model, didItWorkQuestion, pleaseTry)
|
||||
State.DID_IT_WORK -> doesItWorkAnswer(selection, model, adapter, problemSolved, nextSolutionQuestion)
|
||||
}
|
||||
}
|
||||
// defacto this case should not happen
|
||||
return listOf()
|
||||
}
|
||||
|
||||
private fun triedAnswer(selection: String, model: MessageListViewModel,
|
||||
didItWorkQuestion: (MessageListViewModel) -> List<Message>,
|
||||
pleaseTry: () -> List<Message>): List<Message> {
|
||||
return when (selection) {
|
||||
"yes" -> didItWorkQuestion(model)
|
||||
else -> pleaseTry()
|
||||
}
|
||||
}
|
||||
|
||||
private fun doesItWorkAnswer(selection: String) {
|
||||
when (selection) {
|
||||
"yes" -> problemSolved()
|
||||
else -> nextSolutionQuestion()
|
||||
private fun doesItWorkAnswer(selection: String, model: MessageListViewModel, adapter: ArrayAdapter<CharSequence>,
|
||||
problemSolved: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>,
|
||||
nextSolutionQuestion: (MessageListViewModel, ArrayAdapter<CharSequence>) -> List<Message>): List<Message> {
|
||||
return when (selection) {
|
||||
"yes" -> problemSolved(model, adapter)
|
||||
else -> nextSolutionQuestion(model, adapter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun didItWorkQuestion() {
|
||||
receiveMessages(listOf(
|
||||
private fun didItWorkQuestion(model: MessageListViewModel): List<Message> {
|
||||
model.currentSolutionState.value = State.DID_IT_WORK
|
||||
return listOf(
|
||||
Message(getString(R.string.okay), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.did_it_work), false, System.currentTimeMillis())
|
||||
))
|
||||
model.currentSolutionState.value = State.DID_IT_WORK
|
||||
)
|
||||
}
|
||||
|
||||
private fun pleaseTry() {
|
||||
receiveMessages(listOf(
|
||||
private fun pleaseTry(): List<Message> {
|
||||
return listOf(
|
||||
Message(getString(R.string.okay), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.please_try), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.i_wait), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.have_you_tried, model.solutions.value?.let { it[(model.nextSolutionIndex.value ?: 1) - 1]}), false, System.currentTimeMillis())
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
private fun problemSolved() {
|
||||
private fun problemSolved(model: MessageListViewModel, adapter: ArrayAdapter<CharSequence>): List<Message> {
|
||||
model.spinnerOptions.value?.clear()
|
||||
model.spinnerOptions.value?.add(getString(R.string.can_you_say_sth_different))
|
||||
adapter.notifyDataSetChanged()
|
||||
receiveMessage(Message(getString(R.string.okay), false, System.currentTimeMillis()))
|
||||
return listOf(Message(getString(R.string.okay), false, System.currentTimeMillis()))
|
||||
}
|
||||
|
||||
private fun nextSolutionQuestion() {
|
||||
private fun nextSolutionQuestion(model: MessageListViewModel, adapter: ArrayAdapter<CharSequence>): List<Message> {
|
||||
if (model.nextSolutionIndex.value == model.solutions.value?.size) {
|
||||
// we exhausted all options
|
||||
var summary = """
|
||||
Component: ${model.component.value.toString().toLowerCase().capitalize()}
|
||||
Problem: ${model.problem.value.toString().replace('_', ' ').toLowerCase().capitalizeWords()}
|
||||
Component: ${model.component.value.toString()}
|
||||
Problem: ${model.problem.value.toString()}
|
||||
Tried Solutions:
|
||||
""".trimIndent()
|
||||
val solutions: ArrayList<CharSequence>? = model.solutions.value
|
||||
|
@ -253,7 +359,7 @@ class MessageListActivity : AppCompatActivity() {
|
|||
model.spinnerOptions.value?.add(getString(R.string.who_are_you))
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
receiveMessages(listOf(
|
||||
return listOf(
|
||||
Message(getString(R.string.okay), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.sorry), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.cant_help), false, System.currentTimeMillis()),
|
||||
|
@ -261,7 +367,7 @@ class MessageListActivity : AppCompatActivity() {
|
|||
Message(getString(R.string.okay_wait), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.tell_support), false, System.currentTimeMillis()),
|
||||
Message(summary, false, System.currentTimeMillis())
|
||||
))
|
||||
)
|
||||
}
|
||||
else {
|
||||
val solutionQuestion = getString(R.string.have_you_tried, model.solutions.value?.let {it[model.nextSolutionIndex.value ?: 0]})
|
||||
|
@ -271,11 +377,11 @@ class MessageListActivity : AppCompatActivity() {
|
|||
model.spinnerOptions.value?.addAll(resources.getStringArray(R.array.binary))
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
receiveMessages(listOf(
|
||||
return listOf(
|
||||
Message(getString(R.string.okay), false, System.currentTimeMillis()),
|
||||
Message(getString(R.string.sorry), false, System.currentTimeMillis()),
|
||||
Message(solutionQuestion, false, System.currentTimeMillis())
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,6 @@
|
|||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:onClick="sendMessage"
|
||||
android:text="@string/button_send"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="bottom" />
|
||||
|
|
Loading…
Reference in New Issue