Commit 001d57bc authored by Joey's avatar Joey

glucose: use event-based architecture components

Change-Id: Idb2eb9437d4430899b8e95f1d3bbd81ce1bb0d1e
Signed-off-by: Joey's avatarJoey <bevilacquajoey@gmail.com>
parent 91fb79a4
......@@ -52,7 +52,14 @@ class Insulin {
this.timeFrame = TimeFrameConverter().toTimeFrame(timeFrame)
}
fun getDisplayedString(value: Float) = "%1\$s %2\$.1f".format(name, value)
fun getDisplayedString(value: Float) = StringBuilder()
.run {
if (value % 1 == 0f) append(value.toInt())
else append(value)
}
.append(" ")
.append(name)
.toString()
override fun equals(other: Any?): Boolean {
if (other == null || other !is Insulin) {
......
......@@ -143,7 +143,7 @@ class EditorViewModelTest {
glucoseRepo.insert(glucose)
viewModel.runPrepare(glucose.uid, pluginManager)
viewModel.glucose.observe(lifecycle, Observer {
assertTrue(viewModel.hasPotentialBasal())
assertTrue(viewModel.hasPotentialBasal(it))
})
}
......@@ -170,7 +170,7 @@ class EditorViewModelTest {
viewModel.runPrepare(glucose.uid, pluginManager)
viewModel.glucose.observe(lifecycle, blockingObserver {
viewModel.runApplySuggestion(test, testInsulin)
viewModel.runApplySuggestion(test, testInsulin.uid)
val fromDb = glucoseRepo.getById(glucose.uid)
assertEquals(test, fromDb.insulinValue0)
......
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.glucose.components
import android.view.View
import it.diab.core.arch.EventBusFactory
import it.diab.core.arch.UiComponent
import it.diab.glucose.components.views.EditableView
import it.diab.glucose.events.EditorEvents
import kotlinx.coroutines.CoroutineScope
class EditableComponent(
container: View,
scope: CoroutineScope,
bus: EventBusFactory
) : UiComponent {
private val uiView = EditableView(container, bus)
private var isEditing = false
/*
* Block view updates as for a very brief moment the LiveData db query will
* change to "default glucose" while inserting the new values in the db
* so the views would be mistakenly updated with wrong data
*/
private var blockUpdates = false
init {
bus.subscribe(EditorEvents.Requests::class, scope) {
when (it) {
is EditorEvents.Requests.IntentRequestMainAction -> {
if (isEditing) {
blockUpdates = true
bus.emit(EditorEvents.Edit::class, EditorEvents.Edit.IntentSave(uiView.getStatus()))
} else {
isEditing = true
uiView.switchToEditMode()
}
}
}
}
bus.subscribe(EditorEvents.Listeners::class, scope) {
when (it) {
is EditorEvents.Listeners.IntentChangedValueError -> uiView.setValueError(it.hasError)
is EditorEvents.Listeners.IntentChangedDate -> uiView.setDate(it.date, it.hasError)
}
}
bus.subscribe(EditorEvents.Edit::class, scope) {
when (it) {
is EditorEvents.Edit.IntentEdit -> {
if (!blockUpdates) {
isEditing = it.status.isEditing
uiView.setStatus(it.status)
}
}
}
}
}
}
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.glucose.components
import android.view.View
import it.diab.core.arch.EventBusFactory
import it.diab.glucose.components.views.InsulinDialogView
import it.diab.glucose.events.InsulinDialogEvent
import kotlinx.coroutines.CoroutineScope
class InsulinDialogComponent(
container: View,
scope: CoroutineScope,
bus: EventBusFactory
) {
init {
val uiView = InsulinDialogView(container, bus)
bus.subscribe(InsulinDialogEvent::class, scope) {
when (it) {
is InsulinDialogEvent.IntentEdit -> uiView.setStatus(it.status)
}
}
}
}
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
@file:JvmName("EditableStatus")
package it.diab.glucose.components.status
import it.diab.core.arch.ViewStatus
import java.util.Date
data class EditableInStatus(
val glucoseUid: Long,
val value: Int,
val date: Date,
val foodIntake: Int,
val insulinUid: Long,
val insulinDescription: String,
val basalUid: Long,
val basalDescription: String,
val isEditing: Boolean,
val supportsBasal: Boolean
) : ViewStatus
data class EditableOutStatus(
val value: Int,
val foodIntake: Int
) : ViewStatus
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
@file:JvmName("InsulinDialogStatus")
package it.diab.glucose.components.status
import it.diab.core.arch.ViewStatus
sealed class InsulinDialogInStatus : ViewStatus {
data class Edit(
val isEditing: Boolean,
val preferrableIndex: Int,
val selectableItems: List<String>,
val value: Float
) : InsulinDialogInStatus()
object Empty : InsulinDialogInStatus()
}
data class InsulinDialogOutStatus(
val selectedInsulin: Int,
val value: Float
) : ViewStatus
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.glucose.components.views
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.transition.TransitionManager
import com.google.android.material.floatingactionbutton.FloatingActionButton
import it.diab.core.arch.EventBusFactory
import it.diab.core.arch.UiView
import it.diab.glucose.R
import it.diab.glucose.components.status.EditableInStatus
import it.diab.glucose.components.status.EditableOutStatus
import it.diab.glucose.events.EditorEvents
import it.diab.glucose.suggestion.ui.SuggestionView
import it.diab.glucose.util.extensions.getDetailedString
import it.diab.glucose.util.extensions.setIconErrorStatus
import it.diab.glucose.util.extensions.setTextErrorStatus
import it.diab.glucose.widget.EatBar
import it.diab.glucose.widget.NumericKeyboardView
import java.util.Date
class EditableView(
container: View,
private val bus: EventBusFactory
) : UiView<EditableInStatus, EditableOutStatus>(container) {
private val constraint: ConstraintLayout =
container.findViewById(R.id.editor_root)
private val valueView: TextView =
container.findViewById(R.id.editor_value)
private val dateView: TextView =
container.findViewById(R.id.editor_time)
private val eatView: EatBar =
container.findViewById(R.id.editor_eat_level)
private val insulinView: TextView =
container.findViewById(R.id.editor_insulin)
private val basalView: TextView =
container.findViewById(R.id.editor_basal)
private val suggestionView: SuggestionView =
container.findViewById(R.id.editor_suggestion)
private val keyboardView: NumericKeyboardView =
container.findViewById(R.id.editor_keyboard)
private val fabView: FloatingActionButton =
container.findViewById(R.id.editor_fab)
private val closeView: ImageView =
container.findViewById(R.id.editor_close)
private var hasValueError = false
private var hasDateError = false
override fun setStatus(status: EditableInStatus) {
valueView.text = status.value.toString()
dateView.text = status.date.getDetailedString()
eatView.setProgress(status.foodIntake)
closeView.setOnClickListener {
bus.emit(EditorEvents.Requests::class, EditorEvents.Requests.IntentRequestClose)
}
fabView.setOnClickListener {
bus.emit(EditorEvents.Requests::class, EditorEvents.Requests.IntentRequestMainAction)
}
if (status.isEditing) {
setupEdit(false)
} else {
setupView(status)
}
}
override fun getStatus() = EditableOutStatus(
keyboardView.input,
eatView.getProgress()
)
fun setValueError(hasError: Boolean) {
if (hasError == hasValueError) {
return
}
hasValueError = hasError
valueView.setTextErrorStatus(hasError)
}
fun setDate(date: Date, hasError: Boolean) {
dateView.text = date.getDetailedString()
if (hasError == hasDateError) {
return
}
hasDateError = hasError
dateView.setIconErrorStatus(hasError)
}
fun switchToEditMode() {
setupEdit(true)
}
private fun setupView(status: EditableInStatus) {
ConstraintSet().apply {
clone(constraint.context, R.layout.constraint_editor_view)
applyTo(constraint)
}
eatView.lock()
dateView.setOnClickListener {}
fabView.setImageResource(R.drawable.ic_edit)
setupInsulins(status)
bus.emit(
EditorEvents.Requests::class,
EditorEvents.Requests.IntentRequestSuggestion(suggestionView)
)
}
private fun setupInsulins(status: EditableInStatus) {
insulinView.setOnClickListener {
bus.emit(
EditorEvents.Requests::class,
EditorEvents.Requests.IntentRequestEditInsulin(status.glucoseUid, false)
)
}
insulinView.text = if (status.insulinUid > 0L) status.insulinDescription
else insulinView.context.getString(R.string.glucose_editor_insulin_add)
if (!status.supportsBasal) {
basalView.visibility = View.GONE
return
}
basalView.setOnClickListener {
bus.emit(
EditorEvents.Requests::class,
EditorEvents.Requests.IntentRequestEditInsulin(status.glucoseUid, true)
)
}
basalView.text = if (status.basalUid > 0L) status.basalDescription
else insulinView.context.getString(R.string.glucose_editor_basal_add)
basalView.visibility = View.VISIBLE
}
private fun setupEdit(animate: Boolean) {
keyboardView.bindTextView(valueView, this::onValueChanged)
eatView.unlock()
dateView.setOnClickListener {
bus.emit(EditorEvents.Requests::class, EditorEvents.Requests.IntentRequestDate)
}
fabView.setImageResource(R.drawable.ic_done)
ConstraintSet().apply {
clone(constraint.context, R.layout.constraint_editor_edit)
if (animate) {
TransitionManager.beginDelayedTransition(constraint)
}
applyTo(constraint)
}
}
private fun onValueChanged(value: Int) {
bus.emit(EditorEvents.Listeners::class, EditorEvents.Listeners.IntentChangedValue(value))
}
}
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.glucose.components.views
import android.view.View
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.widget.AppCompatSpinner
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.google.android.material.button.MaterialButton
import it.diab.core.arch.EventBusFactory
import it.diab.core.arch.UiView
import it.diab.glucose.R
import it.diab.glucose.components.status.InsulinDialogInStatus
import it.diab.glucose.components.status.InsulinDialogOutStatus
import it.diab.glucose.events.InsulinDialogEvent
class InsulinDialogView(
container: View,
private val bus: EventBusFactory
) : UiView<InsulinDialogInStatus, InsulinDialogOutStatus>(container) {
private val constraintView: ConstraintLayout =
container.findViewById(R.id.glucose_editor_insulin_root)
private val titleView: TextView =
container.findViewById(R.id.glucose_editor_insulin_title)
private val selectorView: AppCompatSpinner =
container.findViewById(R.id.glucose_editor_insulin_spinner)
private val quantityView: EditText =
container.findViewById(R.id.glucose_editor_insulin_value)
private val editView: ImageView =
container.findViewById(R.id.glucose_editor_insulin_editor)
private val positiveButtonView: MaterialButton =
container.findViewById(R.id.glucose_editor_insulin_positive)
private val removeButtonView: MaterialButton =
container.findViewById(R.id.glucose_editor_insulin_negative)
private val emptyView: TextView =
container.findViewById(R.id.glucose_editor_insulin_empty)
init {
removeButtonView.setOnClickListener {
bus.emit(
InsulinDialogEvent::class,
InsulinDialogEvent.IntentRequestDelete
)
}
editView.setOnClickListener {
bus.emit(
InsulinDialogEvent::class,
InsulinDialogEvent.IntentRequestEditor
)
}
}
override fun setStatus(status: InsulinDialogInStatus) {
when (status) {
is InsulinDialogInStatus.Edit -> setupEdit(status)
is InsulinDialogInStatus.Empty -> setupEmpty()
}
}
override fun getStatus() = InsulinDialogOutStatus(
selectorView.selectedItemPosition,
quantityView.text.toString().toFloatOrNull() ?: 0f
)
private fun setupEdit(status: InsulinDialogInStatus.Edit) {
titleView.setText(
if (status.isEditing) R.string.glucose_editor_insulin_edit
else R.string.glucose_editor_insulin_add
)
selectorView.adapter = ArrayAdapter(
selectorView.context,
androidx.appcompat.R.layout.support_simple_spinner_dropdown_item,
status.selectableItems
)
selectorView.setSelection(status.preferrableIndex)
if (status.value > 0f) {
val quantityStr = "%.0f".format(status.value)
quantityView.setText(quantityStr)
}
removeButtonView.visibility = if (status.isEditing) View.VISIBLE else View.GONE
positiveButtonView.setText(R.string.glucose_editor_insulin_apply)
positiveButtonView.setOnClickListener {
bus.emit(
InsulinDialogEvent::class,
InsulinDialogEvent.IntentSave(getStatus())
)
}
}
private fun setupEmpty() {
applyEmptyLayout()
emptyView.visibility = View.VISIBLE
selectorView.visibility = View.GONE
quantityView.visibility = View.GONE
removeButtonView.visibility = View.GONE
positiveButtonView.setOnClickListener {
bus.emit(
InsulinDialogEvent::class,
InsulinDialogEvent.IntentRequestEditor
)
}
positiveButtonView.setText(R.string.glucose_editor_insulin_none_btn)
}
private fun applyEmptyLayout() {
ConstraintSet().apply {
clone(constraintView)
connect(
R.id.glucose_editor_insulin_positive, ConstraintSet.TOP,
R.id.glucose_editor_insulin_empty, ConstraintSet.BOTTOM
)
connect(
R.id.glucose_editor_insulin_positive, ConstraintSet.START,
ConstraintSet.PARENT_ID, ConstraintSet.START
)
connect(
R.id.glucose_editor_insulin_positive, ConstraintSet.END,
ConstraintSet.PARENT_ID, ConstraintSet.END
)
constrainPercentWidth(
R.id.glucose_editor_insulin_positive,
0.8f
)
applyTo(constraintView)
}
}
}
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.glucose.events
import it.diab.core.arch.ComponentEvent
import it.diab.glucose.components.status.EditableInStatus
import it.diab.glucose.components.status.EditableOutStatus
import it.diab.glucose.suggestion.ui.SuggestionUiInterface
import java.util.Date
object EditorEvents {
sealed class Edit : ComponentEvent {
class IntentEdit(val status: EditableInStatus) : Edit()
class IntentSave(val status: EditableOutStatus) : Edit()
}
sealed class Listeners : ComponentEvent {
class IntentChangedValue(val value: Int) : Listeners()
class IntentChangedDate(val date: Date, val hasError: Boolean) : Listeners()
class IntentChangedValueError(val hasError: Boolean) : Listeners()
}
sealed class Requests : ComponentEvent {
class IntentRequestEditInsulin(val uid: Long, val isBasal: Boolean) : Requests()
class IntentRequestSuggestion(val suggestionInterface: SuggestionUiInterface) : Requests()
object IntentRequestDate : Requests()
object IntentRequestClose : Requests()
object IntentRequestMainAction : Requests()
object IntentRequestShowError : Requests()
}
}
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.glucose.events
import it.diab.core.arch.ComponentEvent
import it.diab.glucose.components.status.InsulinDialogInStatus
import it.diab.glucose.components.status.InsulinDialogOutStatus
sealed class InsulinDialogEvent : ComponentEvent {
class IntentEdit(val status: InsulinDialogInStatus) : InsulinDialogEvent()
class IntentSave(val status: InsulinDialogOutStatus) : InsulinDialogEvent()
object IntentRequestDelete : InsulinDialogEvent()
object IntentRequestEditor : InsulinDialogEvent()
}
\ No newline at end of file
......@@ -9,42 +9,27 @@
package it.diab.glucose.fragments
import android.os.Bundle
import android.os.Handler
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.widget.AppCompatSpinner
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.ViewModelProviders
import com.google.android.material.button.MaterialButton
import androidx.lifecycle.viewModelScope
import it.diab.core.util.Activities
import it.diab.core.util.extensions.bus
import it.diab.core.util.intentTo
import it.diab.data.repositories.GlucoseRepository
import it.diab.data.repositories.InsulinRepository
import it.diab.glucose.R
import it.diab.glucose.ui.models.InsulinDialogUiModel
import it.diab.glucose.util.InsulinSelector
import it.diab.glucose.components.InsulinDialogComponent
import it.diab.glucose.components.status.InsulinDialogInStatus
import it.diab.glucose.components.status.InsulinDialogOutStatus
import it.diab.glucose.events.InsulinDialogEvent
import it.diab.glucose.viewmodels.InsulinDialogViewModel
import it.diab.glucose.viewmodels.InsulinDialogViewModelFactory
import it.diab.ui.util.UIUtils
import it.diab.ui.widgets.BottomSheetDialogFragmentExt
class InsulinDialogFragment : BottomSheetDialogFragmentExt() {
private lateinit var constraint: ConstraintLayout
private lateinit var nameSpinner: AppCompatSpinner
private lateinit var valueEditText: EditText
private lateinit var emptyText: TextView
private lateinit var positiveButton: MaterialButton
private lateinit var negativeButton: MaterialButton
private lateinit var editorIcon: ImageView
private lateinit var viewModel: InsulinDialogViewModel
private var wantsBasal = false
......@@ -65,21 +50,7 @@ class InsulinDialogFragment : BottomSheetDialogFragmentExt() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = inflater.inflate(R.layout.fragment_add_insulin, container, false)
constraint = view.findViewById(R.id.glucose_editor_insulin_root)
nameSpinner = view.findViewById(R.id.glucose_editor_insulin_spinner)
valueEditText = view.findViewById(R.id.glucose_editor_insulin_value)
emptyText = view.findViewById(R.id.glucose_editor_insulin_empty)
positiveButton = view.findViewById(R.id.glucose_editor_insulin_positive)
negativeButton = view.findViewById(R.id.glucose_editor_insulin_negative)
editorIcon = view.findViewById(R.id.glucose_editor_insulin_editor)
UIUtils.setWhiteNavBarIfNeeded(requireContext(), dialog)