troubleshooting-agent/app/src/main/java/de/twomartens/troubleshooting/MessageListActivity.kt

354 lines
15 KiB
Kotlin

package de.twomartens.troubleshooting
import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.udevel.widgetlab.TypingIndicatorView
import de.twomartens.troubleshooting.databinding.ActivityMessageListBinding
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MessageListActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var binding: ActivityMessageListBinding
private val viewAdapter: MessageListAdapter = MessageListAdapter()
private lateinit var spinner: Spinner
private lateinit var horizontalLayout: ConstraintLayout
private lateinit var typingIndicator: TypingIndicatorView
private lateinit var model: MessageListViewModel
private lateinit var adapter: ArrayAdapter<SpinnerOption>
private var showSpinner = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMessageListBinding.inflate(layoutInflater)
setContentView(binding.root)
initVariables()
adapter = initSpinner(model, spinner)
bindMessageObserver(viewAdapter, model)
initButton(::step, spinner)
val messages = initFirstChoice(adapter, model, horizontalLayout)
receiveMessages(messages, model, typingIndicator, recyclerView, horizontalLayout)
}
private fun initVariables() {
val viewManager = LinearLayoutManager(this)
viewManager.stackFromEnd = true
viewManager.reverseLayout = false
model = ViewModelProvider(this)[MessageListViewModel::class.java]
horizontalLayout = binding.layoutChatbox
typingIndicator = binding.indicator
typingIndicator.visibility = View.GONE
horizontalLayout.visibility = View.GONE
spinner = binding.spinner
recyclerView = binding.messageList.apply {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
}
}
private fun initSpinner(model: MessageListViewModel,
spinner: Spinner): ArrayAdapter<SpinnerOption> {
val spinnerOptions: ArrayList<SpinnerOption>? = model.spinnerOptions.value
if (spinnerOptions != null) {
val arrayAdapter = ArrayAdapter(this, R.layout.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
}
return ArrayAdapter(this, android.R.layout.simple_spinner_item, ArrayList<SpinnerOption>())
}
private fun bindMessageObserver(viewAdapter: MessageListAdapter, model: MessageListViewModel) {
val messageObserver = Observer<ArrayList<Message>> { messages ->
viewAdapter.replaceMessages(messages)
}
model.getMessages().observe(this, messageObserver)
}
private fun initButton(step: (SpinnerOption) -> Unit, spinner: Spinner) {
binding.buttonSend.setOnClickListener {
val option = spinner.selectedItem
if (option is SpinnerOption) {
step(option)
}
}
}
private fun initFirstChoice(adapter: ArrayAdapter<SpinnerOption>,
model: MessageListViewModel,
horizontalLayout: ConstraintLayout): List<Message> {
val isInitialized = model.initialized.value
return if (isInitialized != null && !isInitialized) {
model.spinnerOptions.value?.addAll(SpinnerOption.getOptions(
resources.getStringArray(R.array.components_values),
resources.getStringArray(R.array.components),
OptionType.COMPONENT))
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(selectedOption: SpinnerOption) {
sendMessage(selectedOption, model, recyclerView, horizontalLayout)
val messages = branch(
selectedOption,
model, adapter,
::componentSelected,
::problemSelected,
::didItWorkQuestion,
::pleaseTry,
::problemSolved,
::nextSolutionQuestion,
::doNotShowSpinner)
receiveMessages(messages, model, typingIndicator, recyclerView, horizontalLayout, showSpinner)
}
private fun sendMessage(selectedOption: SpinnerOption, model: MessageListViewModel,
recyclerView: RecyclerView, horizontalLayout: ConstraintLayout) {
val message = Message(selectedOption.toString(), true, System.currentTimeMillis())
model.add(message)
recyclerView.scrollToPosition(model.size() - 1)
horizontalLayout.visibility = View.GONE
model.spinnerVisible.value = false
}
private fun receiveMessages(messages: List<Message>,
model: MessageListViewModel,
typingIndicator: View,
recyclerView: RecyclerView,
horizontalLayout: ConstraintLayout,
showSpinner: Boolean = true) {
model.uiScope.launch {
for (message in messages) {
typingIndicator.visibility = View.VISIBLE
delay(1500)
typingIndicator.visibility = View.GONE
model.add(message)
recyclerView.scrollToPosition(model.size() - 1)
}
if (messages.isNotEmpty() && showSpinner) {
horizontalLayout.visibility = View.VISIBLE
model.spinnerVisible.value = true
}
}
}
private fun doNotShowSpinner() {
showSpinner = false
}
private fun branch(selectedOption: SpinnerOption,
model: MessageListViewModel,
adapter: ArrayAdapter<SpinnerOption>,
componentSelected: (MessageListViewModel, ArrayAdapter<SpinnerOption>) -> List<Message>,
problemSelected: (MessageListViewModel, ArrayAdapter<SpinnerOption>) -> List<Message>,
didItWorkQuestion: (MessageListViewModel, ArrayAdapter<SpinnerOption>) -> List<Message>,
pleaseTry: () -> List<Message>,
problemSolved: (MessageListViewModel, ArrayAdapter<SpinnerOption>) -> List<Message>,
nextSolutionQuestion: (MessageListViewModel, ArrayAdapter<SpinnerOption>) -> List<Message>,
doNotShowSpinner: () -> Unit): List<Message> {
return when (selectedOption.type) {
OptionType.COMPONENT -> {
model.component.value = Component.valueOf(selectedOption.id)
componentSelected(model, adapter)
}
OptionType.PROBLEM -> {
model.problem.value = Problem.valueOf(selectedOption.id)
problemSelected(model, adapter)
}
OptionType.BINARY_HAVE_YOU_TRIED -> {
when (selectedOption.id) {
"YES" -> didItWorkQuestion(model, adapter)
else -> pleaseTry()
}
}
OptionType.BINARY_DID_IT_WORK -> {
when (selectedOption.id) {
"YES" -> problemSolved(model, adapter)
else -> nextSolutionQuestion(model, adapter)
}
}
OptionType.STORY -> {
doNotShowSpinner()
if (selectedOption.id == "WHO_ARE_YOU") {
listOf(
Message(
getString(R.string.gardner_second),
false,
System.currentTimeMillis()
)
)
}
else {
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()))
}
}
}
}
private fun componentSelected(model: MessageListViewModel, adapter: ArrayAdapter<SpinnerOption>): List<Message> {
model.spinnerOptions.value?.clear()
model.spinnerOptions.value?.addAll(SpinnerOption.getOptions(
resources.getStringArray(resources.getIdentifier(
model.component.value?.name.plus("_problems_values"),
"array",
packageName)),
resources.getStringArray(resources.getIdentifier(
model.component.value?.name.plus("_problems"),
"array",
packageName)),
OptionType.PROBLEM))
adapter.notifyDataSetChanged()
return listOf(
Message(getString(R.string.okay), false, System.currentTimeMillis()),
Message(getString(R.string.select_problem), false, System.currentTimeMillis())
)
}
private fun problemSelected(model: MessageListViewModel, adapter: ArrayAdapter<SpinnerOption>): List<Message> {
model.solutions.value?.addAll(resources.getStringArray(resources.getIdentifier(
model.component.value?.name.plus("_").plus(model.problem.value?.name),
"array",
packageName
)))
val firstSolutionQuestion = getString(R.string.have_you_tried, model.solutions.value?. let { it[0] })
model.nextSolutionIndex.value = (model.nextSolutionIndex.value ?: 0) + 1
model.spinnerOptions.value?.clear()
model.spinnerOptions.value?.addAll(SpinnerOption.getOptions(
resources.getStringArray(R.array.binary_values),
resources.getStringArray(R.array.binary),
OptionType.BINARY_HAVE_YOU_TRIED))
adapter.notifyDataSetChanged()
return listOf(
Message(getString(R.string.okay), false, System.currentTimeMillis()),
Message(firstSolutionQuestion, false, System.currentTimeMillis())
)
}
private fun didItWorkQuestion(model: MessageListViewModel, adapter: ArrayAdapter<SpinnerOption>): List<Message> {
model.currentSolutionState.value = State.DID_IT_WORK
model.spinnerOptions.value?.clear()
model.spinnerOptions.value?.addAll(SpinnerOption.getOptions(
resources.getStringArray(R.array.binary_values),
resources.getStringArray(R.array.binary),
OptionType.BINARY_DID_IT_WORK))
adapter.notifyDataSetChanged()
return listOf(
Message(getString(R.string.okay), false, System.currentTimeMillis()),
Message(getString(R.string.did_it_work), false, System.currentTimeMillis())
)
}
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(model: MessageListViewModel, adapter: ArrayAdapter<SpinnerOption>): List<Message> {
model.spinnerOptions.value?.clear()
model.spinnerOptions.value?.add(SpinnerOption(
"SAY_SOMETHING_DIFFERENT",
getString(R.string.can_you_say_sth_different),
OptionType.STORY))
adapter.notifyDataSetChanged()
return listOf(Message(getString(R.string.okay), false, System.currentTimeMillis()))
}
private fun nextSolutionQuestion(model: MessageListViewModel, adapter: ArrayAdapter<SpinnerOption>): List<Message> {
if (model.nextSolutionIndex.value == model.solutions.value?.size) {
// we exhausted all options
var summary = """
Component: ${model.component.value.toString()}
Problem: ${model.problem.value.toString()}
Tried Solutions:
""".trimIndent()
val solutions: ArrayList<CharSequence>? = model.solutions.value
if (solutions != null) {
for (solution in solutions) {
val item = "\n - $solution"
summary = summary.plus(item)
}
}
model.spinnerOptions.value?.clear()
model.spinnerOptions.value?.add(SpinnerOption(
"WHO_ARE_YOU",
getString(R.string.who_are_you),
OptionType.STORY))
adapter.notifyDataSetChanged()
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()),
Message(getString(R.string.should_contact_support), false, System.currentTimeMillis()),
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]})
model.nextSolutionIndex.value = (model.nextSolutionIndex.value ?: 0) + 1
model.currentSolutionState.value = State.HAVE_YOU_TRIED
model.spinnerOptions.value?.clear()
model.spinnerOptions.value?.addAll(SpinnerOption.getOptions(
resources.getStringArray(R.array.binary_values),
resources.getStringArray(R.array.binary),
OptionType.BINARY_HAVE_YOU_TRIED))
adapter.notifyDataSetChanged()
return listOf(
Message(getString(R.string.okay), false, System.currentTimeMillis()),
Message(getString(R.string.sorry), false, System.currentTimeMillis()),
Message(solutionQuestion, false, System.currentTimeMillis())
)
}
}
}