Merge branch 'AddActionCP' into dev

This commit is contained in:
Milos Kozak 2021-10-09 10:10:58 +02:00
commit 65a14c8eca
10 changed files with 320 additions and 1 deletions

View file

@ -130,7 +130,6 @@
<string name="overview">Overview</string>
<string name="treatments">Treatments</string>
<string name="virtualpump">Virtual Pump</string>
<string name="careportal">Careportal</string>
<string name="configbuilder_pump">Pump</string>

View file

@ -39,6 +39,7 @@ abstract class AutomationModule {
@ContributesAndroidInjector abstract fun actionLoopSuspendInjector(): ActionLoopSuspend
@ContributesAndroidInjector abstract fun actionNotificationInjector(): ActionNotification
@ContributesAndroidInjector abstract fun actionAlarmInjector(): ActionAlarm
@ContributesAndroidInjector abstract fun actionCarePortalEventInjector(): ActionCarePortalEvent
@ContributesAndroidInjector abstract fun actionProfileSwitchInjector(): ActionProfileSwitch
@ContributesAndroidInjector abstract fun actionProfileSwitchPercentInjector(): ActionProfileSwitchPercent
@ContributesAndroidInjector abstract fun actionSendSMSInjector(): ActionSendSMS

View file

@ -293,6 +293,7 @@ class AutomationPlugin @Inject constructor(
ActionStopTempTarget(injector),
ActionNotification(injector),
ActionAlarm(injector),
ActionCarePortalEvent(injector),
ActionProfileSwitchPercent(injector),
ActionProfileSwitch(injector),
ActionSendSMS(injector)

View file

@ -51,6 +51,8 @@ abstract class Action(val injector: HasAndroidInjector) {
return when (type) {
ActionAlarm::class.java.name, // backward compatibility
ActionAlarm::class.java.simpleName -> ActionAlarm(injector).fromJSON(data.toString())
ActionCarePortalEvent::class.java.name,
ActionCarePortalEvent::class.java.simpleName -> ActionCarePortalEvent(injector).fromJSON(data.toString())
ActionDummy::class.java.name,
ActionDummy::class.java.simpleName -> ActionDummy(injector).fromJSON(data.toString())
ActionLoopDisable::class.java.name,

View file

@ -0,0 +1,119 @@
package info.nightscout.androidaps.plugins.general.automation.actions
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.database.entities.UserEntry
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InsertIfNewByTimestampTherapyEventTransaction
import info.nightscout.androidaps.extensions.fromConstant
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.general.automation.elements.*
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import org.json.JSONObject
import javax.inject.Inject
class ActionCarePortalEvent(injector: HasAndroidInjector) : Action(injector) {
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var repository: AppRepository
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var sp: SP
@Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider
@Inject lateinit var uel: UserEntryLogger
private val disposable = CompositeDisposable()
var note = InputString()
var duration = InputDuration(0, InputDuration.TimeUnit.MINUTES)
var cpEvent = InputCarePortalMenu(resourceHelper)
private var valuesWithUnit = mutableListOf<ValueWithUnit?>()
private constructor(injector: HasAndroidInjector, actionCPEvent: ActionCarePortalEvent) : this(injector) {
cpEvent = InputCarePortalMenu(resourceHelper, actionCPEvent.cpEvent.value)
}
override fun friendlyName(): Int = R.string.careportal
override fun shortDescription(): String = resourceHelper.gs(cpEvent.value.stringRes, note.value)
@DrawableRes override fun icon(): Int = cpEvent.value.drawableRes
override fun doAction(callback: Callback) {
val enteredBy = sp.getString("careportal_enteredby", "AndroidAPS")
val eventTime = dateUtil.now()
val therapyEvent = TherapyEvent(
timestamp = eventTime,
type = cpEvent.value.therapyEventType,
glucoseUnit = TherapyEvent.GlucoseUnit.fromConstant(profileFunction.getUnits())
)
valuesWithUnit.add(ValueWithUnit.TherapyEventType(therapyEvent.type))
therapyEvent.enteredBy = enteredBy
if ( therapyEvent.type == TherapyEvent.Type.QUESTION || therapyEvent.type == TherapyEvent.Type.ANNOUNCEMENT) {
val glucoseStatus = glucoseStatusProvider.glucoseStatusData
if (glucoseStatus != null) {
therapyEvent.glucose = glucoseStatus.glucose
therapyEvent.glucoseType = TherapyEvent.MeterType.SENSOR
valuesWithUnit.add(ValueWithUnit.fromGlucoseUnit(glucoseStatus.glucose, profileFunction.getUnits().asText))
valuesWithUnit.add(ValueWithUnit.TherapyEventMeterType(TherapyEvent.MeterType.SENSOR))
}
} else {
therapyEvent.duration = T.mins(duration.value.toLong()).msecs()
valuesWithUnit.add(ValueWithUnit.Minute(duration.value).takeIf { !duration.value.equals(0) } )
}
therapyEvent.note = note.value
valuesWithUnit.add(ValueWithUnit.SimpleString(note.value).takeIf { note.value.isNotBlank() } )
disposable += repository.runTransactionForResult(InsertIfNewByTimestampTherapyEventTransaction(therapyEvent))
.subscribe(
{ result -> result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted therapy event $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while saving therapy event", it) }
)
uel.log(UserEntry.Action.CAREPORTAL, UserEntry.Sources.Automation, title, valuesWithUnit)
}
override fun toJSON(): String {
val data = JSONObject()
.put("cpEvent", cpEvent.value)
.put("note", note.value)
.put("durationInMinutes", duration.value)
return JSONObject()
.put("type", this.javaClass.name)
.put("data", data)
.toString()
}
override fun fromJSON(data: String): Action {
val o = JSONObject(data)
cpEvent.value = InputCarePortalMenu.EventType.valueOf(JsonHelper.safeGetString(o, "cpEvent")!!)
note.value = JsonHelper.safeGetString(o, "note", "")
duration.value = JsonHelper.safeGetInt(o, "durationInMinutes")
return this
}
override fun hasDialog(): Boolean = true
override fun generateDialog(root: LinearLayout) {
LayoutBuilder()
.add(cpEvent)
.add(LabelWithElement(resourceHelper, resourceHelper.gs(R.string.duration_min_label), "", duration))
.add(LabelWithElement(resourceHelper, resourceHelper.gs(R.string.notes_label), "", note))
.build(root)
}
override fun isValid(): Boolean = true
}

View file

@ -0,0 +1,84 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import android.widget.Spinner
import androidx.annotation.DrawableRes
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import androidx.core.graphics.drawable.IconCompat
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.database.entities.UserEntry
import info.nightscout.androidaps.utils.resources.ResourceHelper
class InputCarePortalMenu(private val resourceHelper: ResourceHelper) : Element() {
enum class EventType (val therapyEventType: TherapyEvent.Type) {
NOTE (TherapyEvent.Type.NOTE),
EXERCISE (TherapyEvent.Type.EXERCISE),
QUESTION (TherapyEvent.Type.QUESTION),
ANNOUNCEMENT (TherapyEvent.Type.ANNOUNCEMENT);
@get:StringRes val stringRes: Int
get() = when (this) {
NOTE -> R.string.careportal_note_message
EXERCISE -> R.string.careportal_exercise_message
QUESTION -> R.string.careportal_question_message
ANNOUNCEMENT -> R.string.careportal_announcement_message
}
@get:DrawableRes val drawableRes: Int
get() = when (this) {
NOTE -> R.drawable.ic_cp_note
EXERCISE -> R.drawable.ic_cp_exercise
QUESTION -> R.drawable.ic_cp_question
ANNOUNCEMENT -> R.drawable.ic_cp_announcement
}
companion object {
fun labels(resourceHelper: ResourceHelper): List<String> {
val list: MutableList<String> = ArrayList()
for (e in values()) {
list.add(resourceHelper.gs(e.stringRes))
}
return list
}
}
}
constructor(resourceHelper: ResourceHelper, value: EventType) : this(resourceHelper) {
this.value = value
}
var value = EventType.NOTE
override fun addToLayout(root: LinearLayout) {
val spinner = Spinner(root.context)
val spinnerArrayAdapter = ArrayAdapter(root.context, R.layout.spinner_centered, EventType.labels(resourceHelper))
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerArrayAdapter
val spinnerParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
spinnerParams.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
spinner.layoutParams = spinnerParams
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = EventType.values()[position]
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
spinner.setSelection(value.ordinal)
root.addView(spinner)
}
fun setValue(eventType: EventType): InputCarePortalMenu {
value = eventType
return this
}
}

View file

@ -0,0 +1,81 @@
package info.nightscout.androidaps.plugins.general.automation.actions
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.database.transactions.InsertIfNewByTimestampTherapyEventTransaction
import info.nightscout.androidaps.database.transactions.Transaction
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.plugins.general.automation.elements.InputCarePortalMenu
import info.nightscout.androidaps.plugins.general.automation.elements.InputDuration
import info.nightscout.androidaps.plugins.general.automation.elements.InputString
import info.nightscout.androidaps.queue.Callback
import io.reactivex.Single
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.`when`
class ActionCarePortalEventTest : ActionsTestBase() {
private lateinit var sut: ActionCarePortalEvent
@Before
fun setup() {
`when`(sp.getString(ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn("AndroidAPS")
`when`(
resourceHelper.gs(
ArgumentMatchers.eq(R.string.careportal_note_message),
ArgumentMatchers.anyString()
)
).thenReturn("Note : %s")
`when`(dateUtil.now()).thenReturn(0)
`when`(profileFunction.getUnits()).thenReturn(GlucoseUnit.MGDL)
`when`(repository.runTransactionForResult(anyObject<Transaction<InsertIfNewByTimestampTherapyEventTransaction.TransactionResult>>()))
.thenReturn(Single.just(InsertIfNewByTimestampTherapyEventTransaction.TransactionResult().apply {
}))
sut = ActionCarePortalEvent(injector)
sut.cpEvent = InputCarePortalMenu(resourceHelper)
sut.cpEvent.value = InputCarePortalMenu.EventType.NOTE
sut.note = InputString("Asd")
sut.duration = InputDuration(5, InputDuration.TimeUnit.MINUTES)
}
@Test fun friendlyNameTest() {
Assert.assertEquals(R.string.careportal, sut.friendlyName())
}
@Test fun shortDescriptionTest() {
Assert.assertEquals("Note : %s", sut.shortDescription())
}
@Test fun iconTest() {
Assert.assertEquals(R.drawable.ic_cp_note, sut.icon())
}
@Test fun doActionTest() {
sut.doAction(object : Callback() {
override fun run() {
Assert.assertTrue(result.success)
}
})
}
@Test fun hasDialogTest() {
Assert.assertTrue(sut.hasDialog())
}
@Test fun toJSONTest() {
Assert.assertEquals(
"{\"data\":{\"note\":\"Asd\",\"cpEvent\":\"NOTE\",\"durationInMinutes\":5},\"type\":\"info.nightscout.androidaps.plugins.general.automation.actions.ActionCarePortalEvent\"}",
sut.toJSON()
)
}
@Test fun fromJSONTest() {
sut.note = InputString("Asd")
sut.fromJSON("{\"note\":\"Asd\",\"cpEvent\":\"NOTE\",\"durationInMinutes\":5}")
Assert.assertEquals("Asd", sut.note.value)
Assert.assertEquals(5, sut.duration.value)
Assert.assertEquals(InputCarePortalMenu.EventType.NOTE, sut.cpEvent.value)
}
}

View file

@ -131,6 +131,14 @@ open class ActionsTestBase : TestBaseWithProfile() {
it.rxBus = rxBus
it.uel = uel
}
if (it is ActionCarePortalEvent) {
it.resourceHelper = resourceHelper
it.repository = repository
it.sp = sp
it.dateUtil = dateUtil
it.profileFunction = profileFunction
it.uel = uel
}
if (it is PumpEnactResult) {
it.resourceHelper = resourceHelper
}

View file

@ -0,0 +1,19 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTestBase
import org.junit.Assert
import org.junit.Test
class InputCarePortalEventTest : TriggerTestBase() {
@Test
fun labelsTest() {
Assert.assertEquals(4, InputCarePortalMenu.EventType.labels(resourceHelper).size)
}
@Test
fun setValueTest() {
val cp = InputCarePortalMenu(resourceHelper, InputCarePortalMenu.EventType.EXERCISE)
Assert.assertEquals(InputCarePortalMenu.EventType.EXERCISE, cp.value)
}
}

View file

@ -250,12 +250,17 @@
<string name="activitymonitorformat"><![CDATA[<b><span style=\"color:yellow\">%1$s:</span></b> <b>%2$s</b> in <b>%3$d</b> days<br>]]></string>
<!-- Translator-->
<string name="careportal">Careportal</string>
<string name="careportal_bgcheck">BG Check</string>
<string name="careportal_mbg">Manual BG or Calibration</string>
<string name="careportal_announcement">Announcement</string>
<string name="careportal_note">Note</string>
<string name="careportal_question">Question</string>
<string name="careportal_exercise">Exercise</string>
<string name="careportal_announcement_message">Announcement : %1$s</string>
<string name="careportal_note_message">Note : %1$s</string>
<string name="careportal_question_message">Question : %1$s</string>
<string name="careportal_exercise_message">Exercise : %1$s</string>
<string name="careportal_pumpsitechange">Pump Site Change</string>
<string name="careportal_cgmsensorinsert">CGM Sensor Insert</string>
<string name="careportal_cgmsensorstart">CGM Sensor Start</string>