Merge remote-tracking branch 'Nightscout/dev' into ShortClick

This commit is contained in:
Philoul 2020-12-17 08:33:09 +01:00
commit f514f58c6a
33 changed files with 514 additions and 230 deletions

View file

@ -135,6 +135,9 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '1.8'
} }
buildFeatures {
viewBinding true
}
lintOptions { lintOptions {
checkReleaseBuilds false checkReleaseBuilds false
disable 'MissingTranslation' disable 'MissingTranslation'

View file

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

View file

@ -15,12 +15,18 @@ import java.util.*
import javax.inject.Inject import javax.inject.Inject
class AutomationEvent(private val injector: HasAndroidInjector) { class AutomationEvent(private val injector: HasAndroidInjector) {
@Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var aapsLogger: AAPSLogger
var title: String? = null
var isEnabled = true
var systemAction: Boolean = false // true = generated by AAPS, false = entered by user
var readOnly: Boolean = false // removing, editing disabled
var autoRemove: Boolean = false // auto-remove once used
var trigger: Trigger = TriggerConnector(injector) var trigger: Trigger = TriggerConnector(injector)
val actions: MutableList<Action> = ArrayList() val actions: MutableList<Action> = ArrayList()
var title: String? = null
var isEnabled = true
var lastRun: Long = 0 var lastRun: Long = 0
init { init {
@ -43,6 +49,9 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
return JSONObject() return JSONObject()
.put("title", title) .put("title", title)
.put("enabled", isEnabled) .put("enabled", isEnabled)
.put("systemAction", systemAction)
.put("readOnly", readOnly)
.put("autoRemove", autoRemove)
.put("trigger", trigger.toJSON()) .put("trigger", trigger.toJSON())
.put("actions", array) .put("actions", array)
.toString() .toString()
@ -52,6 +61,9 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
val d = JSONObject(data) val d = JSONObject(data)
title = d.optString("title", "") title = d.optString("title", "")
isEnabled = d.optBoolean("enabled", true) isEnabled = d.optBoolean("enabled", true)
systemAction = d.optBoolean("systemAction", false)
readOnly = d.optBoolean("readOnly", false)
autoRemove = d.optBoolean("autoRemove", false)
trigger = TriggerDummy(injector).instantiate(JSONObject(d.getString("trigger"))) trigger = TriggerDummy(injector).instantiate(JSONObject(d.getString("trigger")))
?: TriggerConnector(injector) ?: TriggerConnector(injector)
val array = d.getJSONArray("actions") val array = d.getJSONArray("actions")

View file

@ -9,11 +9,8 @@ import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -21,6 +18,8 @@ import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.AutomationEventItemBinding
import info.nightscout.androidaps.databinding.AutomationFragmentBinding
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.ItemTouchHelperAdapter import info.nightscout.androidaps.plugins.general.automation.dragHelpers.ItemTouchHelperAdapter
@ -34,14 +33,15 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.alertDialogs.OKDialog.showConfirmation import info.nightscout.androidaps.utils.alertDialogs.OKDialog.showConfirmation
import info.nightscout.androidaps.utils.extensions.plusAssign import info.nightscout.androidaps.utils.extensions.plusAssign
import info.nightscout.androidaps.utils.extensions.toVisibility
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.automation_fragment.*
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
class AutomationFragment : DaggerFragment(), OnStartDragListener { class AutomationFragment : DaggerFragment(), OnStartDragListener {
@Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var fabricPrivacy: FabricPrivacy
@ -53,20 +53,27 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
private var itemTouchHelper: ItemTouchHelper? = null private var itemTouchHelper: ItemTouchHelper? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { private var _binding: AutomationFragmentBinding? = null
return inflater.inflate(R.layout.automation_fragment, container, false)
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = AutomationFragmentBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
eventListAdapter = EventListAdapter() eventListAdapter = EventListAdapter()
automation_eventListView.layoutManager = LinearLayoutManager(context) binding.eventListView.layoutManager = LinearLayoutManager(context)
automation_eventListView.adapter = eventListAdapter binding.eventListView.adapter = eventListAdapter
automation_logView.movementMethod = ScrollingMovementMethod() binding.logView.movementMethod = ScrollingMovementMethod()
automation_fabAddEvent.setOnClickListener { binding.fabAddEvent.setOnClickListener {
val dialog = EditEventDialog() val dialog = EditEventDialog()
val args = Bundle() val args = Bundle()
args.putString("event", AutomationEvent(mainApp).toJSON()) args.putString("event", AutomationEvent(mainApp).toJSON())
@ -77,7 +84,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
val callback: ItemTouchHelper.Callback = SimpleItemTouchHelperCallback(eventListAdapter) val callback: ItemTouchHelper.Callback = SimpleItemTouchHelperCallback(eventListAdapter)
itemTouchHelper = ItemTouchHelper(callback) itemTouchHelper = ItemTouchHelper(callback)
itemTouchHelper?.attachToRecyclerView(automation_eventListView) itemTouchHelper?.attachToRecyclerView(binding.eventListView)
} }
@ -105,13 +112,18 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
disposable.clear() disposable.clear()
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
@Synchronized @Synchronized
private fun updateGui() { private fun updateGui() {
eventListAdapter.notifyDataSetChanged() eventListAdapter.notifyDataSetChanged()
val sb = StringBuilder() val sb = StringBuilder()
for (l in automationPlugin.executionLog.reversed()) for (l in automationPlugin.executionLog.reversed())
sb.append(l).append("<br>") sb.append(l).append("<br>")
automation_logView?.text = HtmlHelper.fromHtml(sb.toString()) binding.logView.text = HtmlHelper.fromHtml(sb.toString())
} }
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) { override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
@ -132,6 +144,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
} }
inner class EventListAdapter : RecyclerView.Adapter<EventListAdapter.ViewHolder>(), ItemTouchHelperAdapter { inner class EventListAdapter : RecyclerView.Adapter<EventListAdapter.ViewHolder>(), ItemTouchHelperAdapter {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.automation_event_item, parent, false) val v = LayoutInflater.from(parent.context).inflate(R.layout.automation_event_item, parent, false)
return ViewHolder(v, parent.context) return ViewHolder(v, parent.context)
@ -147,36 +160,38 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val event = automationPlugin.automationEvents[position] val event = automationPlugin.automationEvents[position]
holder.eventTitle.text = event.title holder.binding.eventTitle.text = event.title
holder.enabled.isChecked = event.isEnabled holder.binding.enabled.isChecked = event.isEnabled
holder.iconLayout.removeAllViews() holder.binding.enabled.isEnabled = !event.readOnly
holder.binding.iconLayout.removeAllViews()
// trigger icons // trigger icons
val triggerIcons = HashSet<Int>() val triggerIcons = HashSet<Int>()
fillIconSet(event.trigger as TriggerConnector, triggerIcons) fillIconSet(event.trigger as TriggerConnector, triggerIcons)
for (res in triggerIcons) { for (res in triggerIcons) {
addImage(res, holder.context, holder.iconLayout) addImage(res, holder.context, holder.binding.iconLayout)
} }
// arrow icon // arrow icon
val iv = ImageView(holder.context) val iv = ImageView(holder.context)
iv.setImageResource(R.drawable.ic_arrow_forward_white_24dp) iv.setImageResource(R.drawable.ic_arrow_forward_white_24dp)
iv.layoutParams = LinearLayout.LayoutParams(resourceHelper.dpToPx(24), resourceHelper.dpToPx(24)) iv.layoutParams = LinearLayout.LayoutParams(resourceHelper.dpToPx(24), resourceHelper.dpToPx(24))
iv.setPadding(resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4), 0) iv.setPadding(resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4), 0)
holder.iconLayout.addView(iv) holder.binding.iconLayout.addView(iv)
// action icons // action icons
val actionIcons = HashSet<Int>() val actionIcons = HashSet<Int>()
for (action in event.actions) { for (action in event.actions) {
actionIcons.add(action.icon()) actionIcons.add(action.icon())
} }
for (res in actionIcons) { for (res in actionIcons) {
addImage(res, holder.context, holder.iconLayout) addImage(res, holder.context, holder.binding.iconLayout)
} }
// enabled event // enabled event
holder.enabled.setOnClickListener { holder.binding.enabled.setOnClickListener {
event.isEnabled = holder.enabled.isChecked event.isEnabled = holder.binding.enabled.isChecked
rxBus.send(EventAutomationDataChanged()) rxBus.send(EventAutomationDataChanged())
} }
// edit event // edit event
holder.rootLayout.setOnClickListener { if (!event.readOnly)
holder.binding.rootLayout.setOnClickListener {
val dialog = EditEventDialog() val dialog = EditEventDialog()
val args = Bundle() val args = Bundle()
args.putString("event", event.toJSON()) args.putString("event", event.toJSON())
@ -185,7 +200,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
dialog.show(childFragmentManager, "EditEventDialog") dialog.show(childFragmentManager, "EditEventDialog")
} }
// Start a drag whenever the handle view it touched // Start a drag whenever the handle view it touched
holder.iconSort.setOnTouchListener { v: View, motionEvent: MotionEvent -> holder.binding.iconSort.setOnTouchListener { v: View, motionEvent: MotionEvent ->
if (motionEvent.action == MotionEvent.ACTION_DOWN) { if (motionEvent.action == MotionEvent.ACTION_DOWN) {
this@AutomationFragment.onStartDrag(holder) this@AutomationFragment.onStartDrag(holder)
return@setOnTouchListener true return@setOnTouchListener true
@ -193,16 +208,18 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
v.onTouchEvent(motionEvent) v.onTouchEvent(motionEvent)
} }
// remove event // remove event
holder.iconTrash.setOnClickListener { holder.binding.iconTrash.setOnClickListener {
showConfirmation(requireContext(), resourceHelper.gs(R.string.removerecord) + " " + automationPlugin.automationEvents[position].title, showConfirmation(requireContext(), resourceHelper.gs(R.string.removerecord) + " " + automationPlugin.automationEvents[position].title,
Runnable { Runnable {
automationPlugin.automationEvents.removeAt(position) automationPlugin.automationEvents.removeAt(position)
notifyItemRemoved(position) notifyItemRemoved(position)
rxBus.send(EventAutomationDataChanged()) rxBus.send(EventAutomationDataChanged())
rxBus.send(EventAutomationUpdateGui()) rxBus.send(EventAutomationUpdateGui())
}, Runnable { rxBus.send(EventAutomationUpdateGui()) }, Runnable {
rxBus.send(EventAutomationUpdateGui())
}) })
} }
holder.binding.iconTrash.visibility = (!event.readOnly).toVisibility()
} }
override fun getItemCount(): Int = automationPlugin.automationEvents.size override fun getItemCount(): Int = automationPlugin.automationEvents.size
@ -227,12 +244,8 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
} }
inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view), ItemTouchHelperViewHolder { inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view), ItemTouchHelperViewHolder {
val rootLayout: RelativeLayout = view.findViewById(R.id.rootLayout)
val iconLayout: LinearLayout = view.findViewById(R.id.iconLayout) val binding = AutomationEventItemBinding.bind(view)
val eventTitle: TextView = view.findViewById(R.id.viewEventTitle)
val iconTrash: ImageView = view.findViewById(R.id.iconTrash)
val iconSort: ImageView = view.findViewById(R.id.iconSort)
val enabled: CheckBox = view.findViewById(R.id.automation_enabled)
override fun onItemSelected() = itemView.setBackgroundColor(Color.LTGRAY) override fun onItemSelected() = itemView.setBackgroundColor(Color.LTGRAY)

View file

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

View file

@ -0,0 +1,72 @@
package info.nightscout.androidaps.plugins.general.automation.actions
import android.content.Context
import android.content.Intent
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.ErrorHelperActivity
import info.nightscout.androidaps.activities.PreferencesActivity
import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.automation.elements.InputString
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationUserMessage
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.alertDialogs.WarningDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import org.json.JSONObject
import javax.inject.Inject
class ActionAlarm(injector: HasAndroidInjector) : Action(injector) {
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var nsUpload: NSUpload
@Inject lateinit var context: Context
var text = InputString(injector)
override fun friendlyName(): Int = R.string.alarm
override fun shortDescription(): String = resourceHelper.gs(R.string.alarm_message, text.value)
@DrawableRes override fun icon(): Int = R.drawable.ic_access_alarm_24dp
override fun doAction(callback: Callback) {
val i = Intent(context, ErrorHelperActivity::class.java)
i.putExtra("soundid", R.raw.modern_alarm)
i.putExtra("status", text.value)
i.putExtra("title", resourceHelper.gs(R.string.alarm))
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(i)
callback.result(PumpEnactResult(injector).success(true).comment(R.string.ok))?.run()
}
override fun toJSON(): String {
val data = JSONObject().put("text", text.value)
return JSONObject()
.put("type", this.javaClass.name)
.put("data", data)
.toString()
}
override fun fromJSON(data: String): Action {
val o = JSONObject(data)
text.value = JsonHelper.safeGetString(o, "text", "")
return this
}
override fun hasDialog(): Boolean = true
override fun generateDialog(root: LinearLayout) {
LayoutBuilder()
.add(LabelWithElement(injector, resourceHelper.gs(R.string.alarm_short), "", text))
.build(root)
}
}

View file

@ -6,24 +6,30 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RadioButton import android.widget.RadioButton
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R import info.nightscout.androidaps.databinding.AutomationDialogChooseActionBinding
import info.nightscout.androidaps.dialogs.DialogFragmentWithDate import info.nightscout.androidaps.dialogs.DialogFragmentWithDate
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.automation.actions.Action import info.nightscout.androidaps.plugins.general.automation.actions.Action
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationAddAction import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationAddAction
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
import kotlinx.android.synthetic.main.automation_dialog_choose_action.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.reflect.full.primaryConstructor import kotlin.reflect.full.primaryConstructor
class ChooseActionDialog : DialogFragmentWithDate() { class ChooseActionDialog : DialogFragmentWithDate() {
@Inject lateinit var automationPlugin: AutomationPlugin @Inject lateinit var automationPlugin: AutomationPlugin
@Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var mainApp: MainApp @Inject lateinit var mainApp: MainApp
private var checkedIndex = -1 private var checkedIndex = -1
private var _binding: AutomationDialogChooseActionBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
// restore checked radio button // restore checked radio button
@ -32,7 +38,8 @@ class ChooseActionDialog : DialogFragmentWithDate() {
} }
onCreateViewGeneral() onCreateViewGeneral()
return inflater.inflate(R.layout.automation_dialog_choose_action, container, false) _binding = AutomationDialogChooseActionBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -42,11 +49,16 @@ class ChooseActionDialog : DialogFragmentWithDate() {
val radioButton = RadioButton(context) val radioButton = RadioButton(context)
radioButton.setText(a.friendlyName()) radioButton.setText(a.friendlyName())
radioButton.tag = a.javaClass.name radioButton.tag = a.javaClass.name
automation_radioGroup.addView(radioButton) binding.radioGroup.addView(radioButton)
} }
if (checkedIndex != -1) if (checkedIndex != -1)
(automation_radioGroup.getChildAt(checkedIndex) as RadioButton).isChecked = true (binding.radioGroup.getChildAt(checkedIndex) as RadioButton).isChecked = true
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun submit(): Boolean { override fun submit(): Boolean {
@ -70,16 +82,16 @@ class ChooseActionDialog : DialogFragmentWithDate() {
} }
private fun getActionClass(): String? { private fun getActionClass(): String? {
val radioButtonID = automation_radioGroup.checkedRadioButtonId val radioButtonID = binding.radioGroup.checkedRadioButtonId
val radioButton = automation_radioGroup.findViewById<RadioButton>(radioButtonID) val radioButton = binding.radioGroup.findViewById<RadioButton>(radioButtonID)
return radioButton?.let { return radioButton?.let {
it.tag as String it.tag as String
} }
} }
private fun determineCheckedIndex(): Int { private fun determineCheckedIndex(): Int {
for (i in 0 until automation_radioGroup.childCount) { for (i in 0 until binding.radioGroup.childCount) {
if ((automation_radioGroup.getChildAt(i) as RadioButton).isChecked) if ((binding.radioGroup.getChildAt(i) as RadioButton).isChecked)
return i return i
} }
return -1 return -1

View file

@ -6,34 +6,42 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RadioButton import android.widget.RadioButton
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R import info.nightscout.androidaps.databinding.AutomationDialogChooseTriggerBinding
import info.nightscout.androidaps.dialogs.DialogFragmentWithDate import info.nightscout.androidaps.dialogs.DialogFragmentWithDate
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger
import kotlinx.android.synthetic.main.automation_dialog_choose_trigger.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.reflect.full.primaryConstructor import kotlin.reflect.full.primaryConstructor
class ChooseTriggerDialog : DialogFragmentWithDate() { class ChooseTriggerDialog : DialogFragmentWithDate() {
@Inject lateinit var automationPlugin: AutomationPlugin @Inject lateinit var automationPlugin: AutomationPlugin
@Inject lateinit var mainApp: MainApp @Inject lateinit var mainApp: MainApp
private var checkedIndex = -1 private var checkedIndex = -1
private var clickListener: OnClickListener? = null private var clickListener: OnClickListener? = null
private var _binding: AutomationDialogChooseTriggerBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
interface OnClickListener { interface OnClickListener {
fun onClick(newTriggerObject: Trigger) fun onClick(newTriggerObject: Trigger)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View {
// restore checked radio button // restore checked radio button
savedInstanceState?.let { bundle -> savedInstanceState?.let { bundle ->
checkedIndex = bundle.getInt("checkedIndex") checkedIndex = bundle.getInt("checkedIndex")
} }
onCreateViewGeneral() onCreateViewGeneral()
return inflater.inflate(R.layout.automation_dialog_choose_trigger, container, false) _binding = AutomationDialogChooseTriggerBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -43,11 +51,16 @@ class ChooseTriggerDialog : DialogFragmentWithDate() {
val radioButton = RadioButton(context) val radioButton = RadioButton(context)
radioButton.setText(t.friendlyName()) radioButton.setText(t.friendlyName())
radioButton.tag = t.javaClass.name radioButton.tag = t.javaClass.name
automation_chooseTriggerRadioGroup.addView(radioButton) binding.chooseTriggerRadioGroup.addView(radioButton)
} }
if (checkedIndex != -1) if (checkedIndex != -1)
(automation_chooseTriggerRadioGroup.getChildAt(checkedIndex) as RadioButton).isChecked = true (binding.chooseTriggerRadioGroup.getChildAt(checkedIndex) as RadioButton).isChecked = true
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun submit(): Boolean { override fun submit(): Boolean {
@ -74,16 +87,16 @@ class ChooseTriggerDialog : DialogFragmentWithDate() {
} }
private fun getTriggerClass(): String? { private fun getTriggerClass(): String? {
val radioButtonID = automation_chooseTriggerRadioGroup.checkedRadioButtonId val radioButtonID = binding.chooseTriggerRadioGroup.checkedRadioButtonId
val radioButton = automation_chooseTriggerRadioGroup.findViewById<RadioButton>(radioButtonID) val radioButton = binding.chooseTriggerRadioGroup.findViewById<RadioButton>(radioButtonID)
return radioButton?.let { return radioButton?.let {
it.tag as String it.tag as String
} }
} }
private fun determineCheckedIndex(): Int { private fun determineCheckedIndex(): Int {
for (i in 0 until automation_chooseTriggerRadioGroup.childCount) { for (i in 0 until binding.chooseTriggerRadioGroup.childCount) {
if ((automation_chooseTriggerRadioGroup.getChildAt(i) as RadioButton).isChecked) if ((binding.chooseTriggerRadioGroup.getChildAt(i) as RadioButton).isChecked)
return i return i
} }
return -1 return -1

View file

@ -5,41 +5,48 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R import info.nightscout.androidaps.databinding.AutomationDialogActionBinding
import info.nightscout.androidaps.dialogs.DialogFragmentWithDate import info.nightscout.androidaps.dialogs.DialogFragmentWithDate
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.automation.actions.Action import info.nightscout.androidaps.plugins.general.automation.actions.Action
import info.nightscout.androidaps.plugins.general.automation.actions.ActionDummy import info.nightscout.androidaps.plugins.general.automation.actions.ActionDummy
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateAction import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateAction
import kotlinx.android.synthetic.main.automation_dialog_action.*
import org.json.JSONObject import org.json.JSONObject
import javax.inject.Inject import javax.inject.Inject
class EditActionDialog : DialogFragmentWithDate() { class EditActionDialog : DialogFragmentWithDate() {
@Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var mainApp: MainApp @Inject lateinit var mainApp: MainApp
private var action: Action? = null private var action: Action? = null
private var actionPosition: Int = -1 private var actionPosition: Int = -1
private var _binding: AutomationDialogActionBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View {
// load data from bundle // load data from bundle
(savedInstanceState ?: arguments)?.let { bundle -> (savedInstanceState ?: arguments)?.let { bundle ->
actionPosition = bundle.getInt("actionPosition", -1) actionPosition = bundle.getInt("actionPosition", -1)
bundle.getString("action")?.let { action = ActionDummy(mainApp).instantiate(JSONObject(it)) } bundle.getString("action")?.let { action = ActionDummy(mainApp).instantiate(JSONObject(it)) }
} }
onCreateViewGeneral() onCreateViewGeneral()
return inflater.inflate(R.layout.automation_dialog_action, container, false) _binding = AutomationDialogActionBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
action?.let { action?.let {
automation_actionTitle.setText(it.friendlyName()) binding.actionTitle.setText(it.friendlyName())
automation_editActionLayout.removeAllViews() binding.editActionLayout.removeAllViews()
it.generateDialog(automation_editActionLayout) it.generateDialog(binding.editActionLayout)
} }
} }

View file

@ -11,6 +11,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.AutomationDialogEventBinding
import info.nightscout.androidaps.dialogs.DialogFragmentWithDate import info.nightscout.androidaps.dialogs.DialogFragmentWithDate
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.automation.AutomationEvent import info.nightscout.androidaps.plugins.general.automation.AutomationEvent
@ -27,10 +28,10 @@ import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.extensions.plusAssign import info.nightscout.androidaps.utils.extensions.plusAssign
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.automation_dialog_event.*
import javax.inject.Inject import javax.inject.Inject
class EditEventDialog : DialogFragmentWithDate() { class EditEventDialog : DialogFragmentWithDate() {
@Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var mainApp: MainApp @Inject lateinit var mainApp: MainApp
@Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var fabricPrivacy: FabricPrivacy
@ -42,8 +43,14 @@ class EditEventDialog : DialogFragmentWithDate() {
private var disposable: CompositeDisposable = CompositeDisposable() private var disposable: CompositeDisposable = CompositeDisposable()
private var _binding: AutomationDialogEventBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View {
event = AutomationEvent(mainApp) event = AutomationEvent(mainApp)
// load data from bundle // load data from bundle
(savedInstanceState ?: arguments)?.let { bundle -> (savedInstanceState ?: arguments)?.let { bundle ->
@ -52,16 +59,17 @@ class EditEventDialog : DialogFragmentWithDate() {
} }
onCreateViewGeneral() onCreateViewGeneral()
return inflater.inflate(R.layout.automation_dialog_event, container, false) _binding = AutomationDialogEventBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
automation_inputEventTitle.setText(event.title) binding.inputEventTitle.setText(event.title)
automation_triggerDescription.text = event.trigger.friendlyDescription() binding.triggerDescription.text = event.trigger.friendlyDescription()
automation_editTrigger.setOnClickListener { binding.editTrigger.setOnClickListener {
val args = Bundle() val args = Bundle()
args.putString("trigger", event.trigger.toJSON()) args.putString("trigger", event.trigger.toJSON())
val dialog = EditTriggerDialog() val dialog = EditTriggerDialog()
@ -71,10 +79,10 @@ class EditEventDialog : DialogFragmentWithDate() {
// setup action list view // setup action list view
actionListAdapter = ActionListAdapter() actionListAdapter = ActionListAdapter()
automation_actionListView.layoutManager = LinearLayoutManager(context) binding.actionListView.layoutManager = LinearLayoutManager(context)
automation_actionListView.adapter = actionListAdapter binding.actionListView.adapter = actionListAdapter
automation_addAction.setOnClickListener { ChooseActionDialog().show(childFragmentManager, "ChooseActionDialog") } binding.addAction.setOnClickListener { ChooseActionDialog().show(childFragmentManager, "ChooseActionDialog") }
showPreconditions() showPreconditions()
@ -99,7 +107,7 @@ class EditEventDialog : DialogFragmentWithDate() {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ .subscribe({
event.trigger = it.trigger event.trigger = it.trigger
automation_triggerDescription.text = event.trigger.friendlyDescription() binding.triggerDescription.text = event.trigger.friendlyDescription()
}, { fabricPrivacy.logException(it) } }, { fabricPrivacy.logException(it) }
) )
disposable += rxBus disposable += rxBus
@ -113,7 +121,7 @@ class EditEventDialog : DialogFragmentWithDate() {
override fun submit(): Boolean { override fun submit(): Boolean {
// check for title // check for title
val title = automation_inputEventTitle?.text?.toString() ?: return false val title = binding.inputEventTitle.text?.toString() ?: return false
if (title.isEmpty()) { if (title.isEmpty()) {
ToastUtils.showToastInUiThread(context, R.string.automation_missing_task_name) ToastUtils.showToastInUiThread(context, R.string.automation_missing_task_name)
return false return false
@ -143,6 +151,7 @@ class EditEventDialog : DialogFragmentWithDate() {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
disposable.clear() disposable.clear()
_binding = null
} }
override fun onSaveInstanceState(savedInstanceState: Bundle) { override fun onSaveInstanceState(savedInstanceState: Bundle) {
@ -154,12 +163,12 @@ class EditEventDialog : DialogFragmentWithDate() {
private fun showPreconditions() { private fun showPreconditions() {
val forcedTriggers = event.getPreconditions() val forcedTriggers = event.getPreconditions()
if (forcedTriggers.size() > 0) { if (forcedTriggers.size() > 0) {
automation_forcedTriggerDescription.visibility = View.VISIBLE binding.forcedTriggerDescription.visibility = View.VISIBLE
automation_forcedTriggerDescriptionLabel.visibility = View.VISIBLE binding.forcedTriggerDescriptionLabel.visibility = View.VISIBLE
automation_forcedTriggerDescription.text = forcedTriggers.friendlyDescription() binding.forcedTriggerDescription.text = forcedTriggers.friendlyDescription()
} else { } else {
automation_forcedTriggerDescription.visibility = View.GONE binding.forcedTriggerDescription.visibility = View.GONE
automation_forcedTriggerDescriptionLabel.visibility = View.GONE binding.forcedTriggerDescriptionLabel.visibility = View.GONE
} }
} }

View file

@ -5,7 +5,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R import info.nightscout.androidaps.databinding.AutomationDialogEditTriggerBinding
import info.nightscout.androidaps.dialogs.DialogFragmentWithDate import info.nightscout.androidaps.dialogs.DialogFragmentWithDate
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateTrigger import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateTrigger
@ -19,11 +19,11 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.extensions.plusAssign import info.nightscout.androidaps.utils.extensions.plusAssign
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.automation_dialog_edit_trigger.*
import org.json.JSONObject import org.json.JSONObject
import javax.inject.Inject import javax.inject.Inject
class EditTriggerDialog : DialogFragmentWithDate() { class EditTriggerDialog : DialogFragmentWithDate() {
@Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var mainApp: MainApp @Inject lateinit var mainApp: MainApp
@Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var fabricPrivacy: FabricPrivacy
@ -32,15 +32,22 @@ class EditTriggerDialog : DialogFragmentWithDate() {
private var triggers: Trigger? = null private var triggers: Trigger? = null
private var _binding: AutomationDialogEditTriggerBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View {
// load data from bundle // load data from bundle
(savedInstanceState ?: arguments)?.let { bundle -> (savedInstanceState ?: arguments)?.let { bundle ->
bundle.getString("trigger")?.let { triggers = TriggerDummy(mainApp).instantiate(JSONObject(it)) } bundle.getString("trigger")?.let { triggers = TriggerDummy(mainApp).instantiate(JSONObject(it)) }
} }
onCreateViewGeneral() onCreateViewGeneral()
return inflater.inflate(R.layout.automation_dialog_edit_trigger, container, false) _binding = AutomationDialogEditTriggerBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -50,16 +57,16 @@ class EditTriggerDialog : DialogFragmentWithDate() {
.toObservable(EventTriggerChanged::class.java) .toObservable(EventTriggerChanged::class.java)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ .subscribe({
automation_layoutTrigger.removeAllViews() binding.layoutTrigger.removeAllViews()
triggers?.generateDialog(automation_layoutTrigger) triggers?.generateDialog(binding.layoutTrigger)
}, { fabricPrivacy.logException(it) }) }, { fabricPrivacy.logException(it) })
disposable += rxBus disposable += rxBus
.toObservable(EventTriggerRemove::class.java) .toObservable(EventTriggerRemove::class.java)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ .subscribe({
findParent(triggers, it.trigger)?.list?.remove(it.trigger) findParent(triggers, it.trigger)?.list?.remove(it.trigger)
automation_layoutTrigger.removeAllViews() binding.layoutTrigger.removeAllViews()
triggers?.generateDialog(automation_layoutTrigger) triggers?.generateDialog(binding.layoutTrigger)
}, { fabricPrivacy.logException(it) }) }, { fabricPrivacy.logException(it) })
disposable += rxBus disposable += rxBus
@ -67,17 +74,18 @@ class EditTriggerDialog : DialogFragmentWithDate() {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ .subscribe({
findParent(triggers, it.trigger)?.list?.add(it.trigger.duplicate()) findParent(triggers, it.trigger)?.list?.add(it.trigger.duplicate())
automation_layoutTrigger.removeAllViews() binding.layoutTrigger.removeAllViews()
triggers?.generateDialog(automation_layoutTrigger) triggers?.generateDialog(binding.layoutTrigger)
}, { fabricPrivacy.logException(it) }) }, { fabricPrivacy.logException(it) })
// display root trigger // display root trigger
triggers?.generateDialog(automation_layoutTrigger) triggers?.generateDialog(binding.layoutTrigger)
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
disposable.clear() disposable.clear()
_binding = null
} }
override fun submit(): Boolean { override fun submit(): Boolean {

View file

@ -37,11 +37,7 @@ import info.nightscout.androidaps.dialogs.InsulinDialog
import info.nightscout.androidaps.dialogs.TreatmentDialog import info.nightscout.androidaps.dialogs.TreatmentDialog
import info.nightscout.androidaps.dialogs.WizardDialog import info.nightscout.androidaps.dialogs.WizardDialog
import info.nightscout.androidaps.events.* import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.interfaces.Constraint
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification
@ -76,36 +72,16 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.overview_buttons_layout.* import kotlinx.android.synthetic.main.overview_buttons_layout.*
import kotlinx.android.synthetic.main.overview_buttons_layout.overview_carbsbutton
import kotlinx.android.synthetic.main.overview_buttons_layout.overview_insulinbutton
import kotlinx.android.synthetic.main.overview_buttons_layout.overview_quickwizardbutton
import kotlinx.android.synthetic.main.overview_buttons_layout.overview_treatmentbutton
import kotlinx.android.synthetic.main.overview_buttons_layout.overview_wizardbutton
import kotlinx.android.synthetic.main.overview_fragment.overview_notifications import kotlinx.android.synthetic.main.overview_fragment.overview_notifications
import kotlinx.android.synthetic.main.overview_fragment_nsclient.* import kotlinx.android.synthetic.main.overview_fragment_nsclient.*
import kotlinx.android.synthetic.main.overview_graphs_layout.overview_bggraph import kotlinx.android.synthetic.main.overview_graphs_layout.*
import kotlinx.android.synthetic.main.overview_graphs_layout.overview_chartMenuButton
import kotlinx.android.synthetic.main.overview_graphs_layout.overview_iobcalculationprogess
import kotlinx.android.synthetic.main.overview_graphs_layout.overview_iobgraph
import kotlinx.android.synthetic.main.overview_info_layout.* import kotlinx.android.synthetic.main.overview_info_layout.*
import kotlinx.android.synthetic.main.overview_info_layout.overview_arrow
import kotlinx.android.synthetic.main.overview_info_layout.overview_basebasal
import kotlinx.android.synthetic.main.overview_info_layout.overview_bg
import kotlinx.android.synthetic.main.overview_info_layout.overview_cob
import kotlinx.android.synthetic.main.overview_info_layout.overview_extendedbolus
import kotlinx.android.synthetic.main.overview_info_layout.overview_iob
import kotlinx.android.synthetic.main.overview_info_layout.overview_sensitivity
import kotlinx.android.synthetic.main.overview_info_layout.overview_time
import kotlinx.android.synthetic.main.overview_info_layout.overview_timeagoshort
import kotlinx.android.synthetic.main.overview_loop_pumpstatus_layout.* import kotlinx.android.synthetic.main.overview_loop_pumpstatus_layout.*
import kotlinx.android.synthetic.main.overview_statuslights_layout.* import kotlinx.android.synthetic.main.overview_statuslights_layout.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.abs import kotlin.math.abs
@ -155,9 +131,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
private var loopHandler = Handler() private var loopHandler = Handler()
private var refreshLoop: Runnable? = null private var refreshLoop: Runnable? = null
private val worker = Executors.newSingleThreadScheduledExecutor()
private var scheduledUpdate: ScheduledFuture<*>? = null
private val secondaryGraphs = ArrayList<GraphView>() private val secondaryGraphs = ArrayList<GraphView>()
private val secondaryGraphsLabel = ArrayList<TextView>() private val secondaryGraphsLabel = ArrayList<TextView>()
@ -518,21 +491,19 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
} }
} }
var task: Runnable? = null
private fun scheduleUpdateGUI(from: String) { private fun scheduleUpdateGUI(from: String) {
class UpdateRunnable : Runnable { class UpdateRunnable : Runnable {
override fun run() { override fun run() {
activity?.runOnUiThread {
updateGUI(from) updateGUI(from)
scheduledUpdate = null task = null
} }
} }
} view?.removeCallbacks(task)
// prepare task for execution in 500 milliseconds task = UpdateRunnable()
// cancel waiting task to prevent multiple updates view?.postDelayed(task, 500)
scheduledUpdate?.cancel(false)
val task: Runnable = UpdateRunnable()
scheduledUpdate = worker.schedule(task, 500, TimeUnit.MILLISECONDS)
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@ -558,6 +529,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
val units = profileFunction.getUnits() val units = profileFunction.getUnits()
val lowLine = defaultValueHelper.determineLowLine() val lowLine = defaultValueHelper.determineLowLine()
val highLine = defaultValueHelper.determineHighLine() val highLine = defaultValueHelper.determineHighLine()
val lastRun = loopPlugin.lastRun
val predictionsAvailable = if (config.APS) lastRun?.request?.hasPredictions == true else config.NSCLIENT
try {
updateGraph(lastRun, predictionsAvailable, lowLine, highLine, pump, profile)
} catch (e: IllegalStateException) {
return // view no longer exists
}
//Start with updating the BG as it is unaffected by loop. //Start with updating the BG as it is unaffected by loop.
// **** BG value **** // **** BG value ****
@ -660,8 +639,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
overview_apsmode_text?.visibility = View.GONE overview_apsmode_text?.visibility = View.GONE
overview_time_llayout?.visibility = View.VISIBLE overview_time_llayout?.visibility = View.VISIBLE
} }
val lastRun = loopPlugin.lastRun
val predictionsAvailable = if (config.APS) lastRun?.request?.hasPredictions == true else config.NSCLIENT
// temp target // temp target
val tempTarget = treatmentsPlugin.tempTargetFromHistory val tempTarget = treatmentsPlugin.tempTargetFromHistory
@ -799,8 +776,9 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
iobCobCalculatorPlugin.getLastAutosensData("Overview")?.let { autosensData -> iobCobCalculatorPlugin.getLastAutosensData("Overview")?.let { autosensData ->
String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100) String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
} ?: "" } ?: ""
}
// ****** GRAPH ******* private fun updateGraph(lastRun: LoopInterface.LastRun?, predictionsAvailable: Boolean, lowLine: Double, highLine: Double, pump: PumpInterface, profile: Profile) {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
overview_bggraph ?: return@launch overview_bggraph ?: return@launch
val graphData = GraphData(injector, overview_bggraph, iobCobCalculatorPlugin, treatmentsPlugin) val graphData = GraphData(injector, overview_bggraph, iobCobCalculatorPlugin, treatmentsPlugin)

View file

@ -291,7 +291,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
return; return;
} }
executeTask(new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient), dataMap); (new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataMap);
} }
} }
} }
@ -402,7 +402,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
} }
} }
entries.putDataMapArrayList("entries", dataMaps); entries.putDataMapArrayList("entries", dataMaps);
executeTask(new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient), entries); (new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, entries);
} }
sendPreferences(); sendPreferences();
sendBasals(); sendBasals();
@ -553,8 +553,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
dm.putDataMapArrayList("temps", temps); dm.putDataMapArrayList("temps", temps);
dm.putDataMapArrayList("boluses", boluses); dm.putDataMapArrayList("boluses", boluses);
dm.putDataMapArrayList("predictions", predictions); dm.putDataMapArrayList("predictions", predictions);
(new SendToDataLayerThread(BASAL_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dm);
executeTask(new SendToDataLayerThread(BASAL_DATA_PATH, googleApiClient), dm);
} }
private DataMap tempDatamap(long startTime, double startBasal, long to, double toBasal, double amount) { private DataMap tempDatamap(long startTime, double startBasal, long to, double toBasal, double amount) {
@ -601,7 +600,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis()); dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putString("openSettings", "openSettings"); dataMapRequest.getDataMap().putString("openSettings", "openSettings");
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendNotification", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("OpenSettings", "No connection to wearable available!"); Log.e("OpenSettings", "No connection to wearable available!");
@ -617,7 +615,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
dataMapRequest.getDataMap().putString("progressstatus", status); dataMapRequest.getDataMap().putString("progressstatus", status);
dataMapRequest.getDataMap().putInt("progresspercent", progresspercent); dataMapRequest.getDataMap().putInt("progresspercent", progresspercent);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendBolusProgress", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("BolusProgress", "No connection to wearable available!"); Log.e("BolusProgress", "No connection to wearable available!");
@ -637,7 +634,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: " + actionstring); aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendActionConfirmationRequest", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("confirmationRequest", "No connection to wearable available!"); Log.e("confirmationRequest", "No connection to wearable available!");
@ -657,7 +653,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: " + actionstring); aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendChangeConfirmationRequest", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("changeConfirmRequest", "No connection to wearable available!"); Log.e("changeConfirmRequest", "No connection to wearable available!");
@ -675,7 +670,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
aapsLogger.debug(LTag.WEAR, "Canceling notification on wear: " + actionstring); aapsLogger.debug(LTag.WEAR, "Canceling notification on wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendCancelNotificationRequest", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("cancelNotificationReq", "No connection to wearable available!"); Log.e("cancelNotificationReq", "No connection to wearable available!");
@ -701,8 +695,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
currentBasal = generateBasalString(); currentBasal = generateBasalString();
//bgi //bgi
double bgi = -(bolusIob.activity + basalIob.activity) * 5 * Profile.fromMgdlToUnits(profile.getIsfMgdl(), profileFunction.getUnits()); double bgi = -(bolusIob.activity + basalIob.activity) * 5 * Profile.fromMgdlToUnits(profile.getIsfMgdl(), profileFunction.getUnits());
bgiString = "" + ((bgi >= 0) ? "+" : "") + DecimalFormatter.to1Decimal(bgi); bgiString = "" + ((bgi >= 0) ? "+" : "") + DecimalFormatter.to1Decimal(bgi);
@ -740,7 +732,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
dataMapRequest.getDataMap().putBoolean("showBgi", sp.getBoolean(R.string.key_wear_showbgi, false)); dataMapRequest.getDataMap().putBoolean("showBgi", sp.getBoolean(R.string.key_wear_showbgi, false));
dataMapRequest.getDataMap().putInt("batteryLevel", (phoneBattery >= 30) ? 1 : 0); dataMapRequest.getDataMap().putInt("batteryLevel", (phoneBattery >= 30) ? 1 : 0);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendStatus", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("SendStatus", "No connection to wearable available!"); Log.e("SendStatus", "No connection to wearable available!");
@ -757,29 +748,12 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis()); dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putBoolean("wearcontrol", wearcontrol); dataMapRequest.getDataMap().putBoolean("wearcontrol", wearcontrol);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendPreferences", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("SendStatus", "No connection to wearable available!"); Log.e("SendStatus", "No connection to wearable available!");
} }
} }
private void debugData(String source, Object data) {
// Log.d(TAG, "WR: " + source + " " + data);
}
@SuppressWarnings("UNCHECKED")
private void executeTask(AsyncTask task, DataMap... parameters) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, parameters);
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// } else {
// task.execute();
// }
}
@NonNull @NonNull
private String generateStatusString(Profile profile, String currentBasal, String iobSum, String iobDetail, String bgiString) { private String generateStatusString(Profile profile, String currentBasal, String iobSum, String iobDetail, String bgiString) {

View file

@ -29,7 +29,7 @@
app:srcCompat="@drawable/ic_action_orange_48dp" /> app:srcCompat="@drawable/ic_action_orange_48dp" />
<TextView <TextView
android:id="@+id/automation_actionTitle" android:id="@+id/actionTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
@ -49,7 +49,7 @@
android:padding="5dp" /> android:padding="5dp" />
<LinearLayout <LinearLayout
android:id="@+id/automation_editActionLayout" android:id="@+id/editActionLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"

View file

@ -29,7 +29,6 @@
app:srcCompat="@drawable/ic_action_orange_48dp" /> app:srcCompat="@drawable/ic_action_orange_48dp" />
<TextView <TextView
android:id="@+id/actions_care_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
@ -50,7 +49,7 @@
android:padding="5dp" /> android:padding="5dp" />
<RadioGroup <RadioGroup
android:id="@+id/automation_radioGroup" android:id="@+id/radioGroup"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="10dp" /> android:padding="10dp" />

View file

@ -29,7 +29,6 @@
app:srcCompat="@drawable/ic_trigger_green_48dp" /> app:srcCompat="@drawable/ic_trigger_green_48dp" />
<TextView <TextView
android:id="@+id/actions_care_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
@ -43,7 +42,7 @@
</RelativeLayout> </RelativeLayout>
<RadioGroup <RadioGroup
android:id="@+id/automation_chooseTriggerRadioGroup" android:id="@+id/chooseTriggerRadioGroup"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="10dp"/> android:padding="10dp"/>

View file

@ -29,7 +29,6 @@
app:srcCompat="@drawable/ic_trigger_green_48dp" /> app:srcCompat="@drawable/ic_trigger_green_48dp" />
<TextView <TextView
android:id="@+id/automation_actionTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
@ -43,7 +42,7 @@
</RelativeLayout> </RelativeLayout>
<LinearLayout <LinearLayout
android:id="@+id/automation_layoutTrigger" android:id="@+id/layoutTrigger"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="10dp" android:padding="10dp"

View file

@ -55,7 +55,7 @@
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/automation_inputEventTitle" android:id="@+id/inputEventTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/taskname" /> android:hint="@string/taskname" />
@ -81,7 +81,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/automation_editTrigger" android:id="@+id/editTrigger"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
@ -94,7 +94,7 @@
</RelativeLayout> </RelativeLayout>
<TextView <TextView
android:id="@+id/automation_triggerDescription" android:id="@+id/triggerDescription"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
@ -102,14 +102,14 @@
android:orientation="vertical" /> android:orientation="vertical" />
<TextView <TextView
android:id="@+id/automation_forcedTriggerDescriptionLabel" android:id="@+id/forcedTriggerDescriptionLabel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/preconditions" android:text="@string/preconditions"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/automation_forcedTriggerDescription" android:id="@+id/forcedTriggerDescription"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
@ -144,7 +144,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/automation_addAction" android:id="@+id/addAction"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
@ -157,7 +157,7 @@
</RelativeLayout> </RelativeLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/automation_actionListView" android:id="@+id/actionListView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"

View file

@ -14,22 +14,22 @@
android:padding="8dp"> android:padding="8dp">
<CheckBox <CheckBox
android:id="@+id/automation_enabled" android:id="@+id/enabled"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentTop="true" /> android:layout_alignParentTop="true" />
<TextView <TextView
android:id="@+id/viewEventTitle" android:id="@+id/eventTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignTop="@+id/automation_enabled" android:layout_alignTop="@+id/enabled"
android:layout_alignBottom="@+id/automation_enabled" android:layout_alignBottom="@+id/enabled"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:layout_toStartOf="@+id/iconTrash" android:layout_toStartOf="@+id/iconTrash"
android:layout_toEndOf="@id/automation_enabled" android:layout_toEndOf="@id/enabled"
android:text="Title" android:text="Title"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textStyle="bold" /> android:textStyle="bold" />
@ -59,7 +59,7 @@
android:id="@+id/iconLayout" android:id="@+id/iconLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/automation_enabled" android:layout_below="@id/enabled"
android:orientation="horizontal" /> android:orientation="horizontal" />

View file

@ -6,23 +6,23 @@
tools:context="info.nightscout.androidaps.plugins.general.automation.AutomationFragment"> tools:context="info.nightscout.androidaps.plugins.general.automation.AutomationFragment">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/automation_eventListView" android:id="@+id/eventListView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_above="@+id/automation_logView" android:layout_above="@+id/logView"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginBottom="15dp" /> android:layout_marginBottom="15dp" />
<TextView <TextView
android:id="@+id/automation_logView" android:id="@+id/logView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="100dp" android:layout_height="100dp"
android:scrollbars = "vertical" android:scrollbars = "vertical"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/automation_fabAddEvent" android:id="@+id/fabAddEvent"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
@ -32,6 +32,7 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:src="@drawable/ic_add_black_24dp" android:src="@drawable/ic_add_black_24dp"
tools:ignore="RelativeOverlap" /> tools:ignore="RelativeOverlap"
android:contentDescription="@string/addnew" />
</RelativeLayout> </RelativeLayout>

Binary file not shown.

View file

@ -1174,7 +1174,9 @@
<string name="notification">Notification</string> <string name="notification">Notification</string>
<string name="notification_message">Notification: %1$s</string> <string name="notification_message">Notification: %1$s</string>
<string name="alarm_message">Alarm: %1$s</string>
<string name="message_short">Msg:</string> <string name="message_short">Msg:</string>
<string name="alarm_short">Alarm:</string>
<string name="profilepercentage">Profile percentage</string> <string name="profilepercentage">Profile percentage</string>
<string name="percent_u">Percent [%]:</string> <string name="percent_u">Percent [%]:</string>
<string name="startprofile">Start profile %1$d%% for %2$d min</string> <string name="startprofile">Start profile %1$d%% for %2$d min</string>
@ -1411,5 +1413,6 @@
<string name="filter">Filter</string> <string name="filter">Filter</string>
<string name="copytolocalprofile_invalid">Unable to create local profile. Profile is invalid.</string> <string name="copytolocalprofile_invalid">Unable to create local profile. Profile is invalid.</string>
<string name="cta_dont_kill_my_app_info">Don\'t kill my app?</string> <string name="cta_dont_kill_my_app_info">Don\'t kill my app?</string>
<string name="alarm">Alarm</string>
</resources> </resources>

View file

@ -51,7 +51,7 @@ class AutomationEventTest : TestBase() {
event.addAction(ActionLoopEnable(injector)) event.addAction(ActionLoopEnable(injector))
// export to json // export to json
val eventJsonExpected = "{\"trigger\":\"{\\\"data\\\":{\\\"connectorType\\\":\\\"AND\\\",\\\"triggerList\\\":[\\\"{\\\\\\\"data\\\\\\\":{\\\\\\\"connectorType\\\\\\\":\\\\\\\"AND\\\\\\\",\\\\\\\"triggerList\\\\\\\":[]},\\\\\\\"type\\\\\\\":\\\\\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\\\\\\\"}\\\"]},\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\\\"}\",\"title\":\"Test\",\"actions\":[\"{\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.actions.ActionLoopEnable\\\"}\"],\"enabled\":true}" val eventJsonExpected = "{\"autoRemove\":false,\"readOnly\":false,\"trigger\":\"{\\\"data\\\":{\\\"connectorType\\\":\\\"AND\\\",\\\"triggerList\\\":[\\\"{\\\\\\\"data\\\\\\\":{\\\\\\\"connectorType\\\\\\\":\\\\\\\"AND\\\\\\\",\\\\\\\"triggerList\\\\\\\":[]},\\\\\\\"type\\\\\\\":\\\\\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\\\\\\\"}\\\"]},\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\\\"}\",\"title\":\"Test\",\"systemAction\":false,\"actions\":[\"{\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.actions.ActionLoopEnable\\\"}\"],\"enabled\":true}"
Assert.assertEquals(eventJsonExpected, event.toJSON()) Assert.assertEquals(eventJsonExpected, event.toJSON())
// clone // clone

View file

@ -0,0 +1,97 @@
package info.nightscout.androidaps.plugins.general.automation.actions
import android.content.Context
import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.TestBase
import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.automation.elements.InputString
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.resources.ResourceHelper
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
@RunWith(PowerMockRunner::class)
@PrepareForTest(NSUpload::class, RxBusWrapper::class)
class ActionAlarmTest : TestBase() {
@Mock lateinit var resourceHelper: ResourceHelper
@Mock lateinit var rxBus: RxBusWrapper
@Mock lateinit var nsUpload: NSUpload
@Mock lateinit var context: Context
private lateinit var sut: ActionAlarm
var injector: HasAndroidInjector = HasAndroidInjector {
AndroidInjector {
if (it is ActionAlarm) {
it.resourceHelper = resourceHelper
it.rxBus = rxBus
it.nsUpload = nsUpload
it.context = context
}
if (it is PumpEnactResult) {
it.aapsLogger = aapsLogger
it.resourceHelper = resourceHelper
}
}
}
@Before
fun setup() {
PowerMockito.mockStatic(NSUpload::class.java)
`when`(resourceHelper.gs(R.string.ok)).thenReturn("OK")
`when`(resourceHelper.gs(R.string.alarm)).thenReturn("Alarm")
`when`(resourceHelper.gs(ArgumentMatchers.eq(R.string.alarm_message), ArgumentMatchers.anyString())).thenReturn("Alarm: %s")
sut = ActionAlarm(injector)
}
@Test fun friendlyNameTest() {
Assert.assertEquals(R.string.alarm, sut.friendlyName())
}
@Test fun shortDescriptionTest() {
sut.text = InputString(injector, "Asd")
Assert.assertEquals("Alarm: %s", sut.shortDescription())
}
@Test fun iconTest() {
Assert.assertEquals(R.drawable.ic_access_alarm_24dp, sut.icon())
}
@Test fun doActionTest() {
sut.doAction(object : Callback() {
override fun run() {
Assert.assertTrue(result.success)
}
})
PowerMockito.verifyStatic(NSUpload::class.java, Mockito.times(1))
}
@Test fun hasDialogTest() {
Assert.assertTrue(sut.hasDialog())
}
@Test fun toJSONTest() {
sut.text = InputString(injector, "Asd")
Assert.assertEquals("{\"data\":{\"text\":\"Asd\"},\"type\":\"info.nightscout.androidaps.plugins.general.automation.actions.ActionAlarm\"}", sut.toJSON())
}
@Test fun fromJSONTest() {
sut.text = InputString(injector, "Asd")
sut.fromJSON("{\"text\":\"Asd\"}")
Assert.assertEquals("Asd", sut.text.value)
}
}

View file

@ -1,6 +1,7 @@
package info.nightscout.androidaps.dialogs package info.nightscout.androidaps.dialogs
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -9,9 +10,10 @@ import android.view.WindowManager
import dagger.android.support.DaggerDialogFragment import dagger.android.support.DaggerDialogFragment
import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.activities.ErrorHelperActivity
import info.nightscout.androidaps.core.R import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.core.databinding.DialogErrorBinding
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.services.AlarmSoundServiceHelper import info.nightscout.androidaps.services.AlarmSoundServiceHelper
import kotlinx.android.synthetic.main.dialog_error.* import info.nightscout.androidaps.utils.T
import javax.inject.Inject import javax.inject.Inject
class ErrorDialog : DaggerDialogFragment() { class ErrorDialog : DaggerDialogFragment() {
@ -24,8 +26,16 @@ class ErrorDialog : DaggerDialogFragment() {
var title: String = "" var title: String = ""
var sound: Int = 0 var sound: Int = 0
private var loopHandler = Handler()
private var _binding: DialogErrorBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View {
dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE) dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE)
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
isCancelable = true isCancelable = true
@ -37,21 +47,27 @@ class ErrorDialog : DaggerDialogFragment() {
sound = bundle.getInt("sound", R.raw.error) sound = bundle.getInt("sound", R.raw.error)
} }
aapsLogger.debug("Error dialog displayed") aapsLogger.debug("Error dialog displayed")
return inflater.inflate(R.layout.dialog_error, container, false) _binding = DialogErrorBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
error_title.text = title binding.title.text = title
overview_error_ok.setOnClickListener { binding.ok.setOnClickListener {
aapsLogger.debug("USER ENTRY: Error dialog ok button pressed") aapsLogger.debug("USER ENTRY: Error dialog ok button pressed")
dismiss() dismiss()
} }
overview_error_mute.setOnClickListener { binding.mute.setOnClickListener {
aapsLogger.debug("USER ENTRY: Error dialog mute button pressed") aapsLogger.debug("USER ENTRY: Error dialog mute button pressed")
stopAlarm() stopAlarm()
} }
binding.mute5min.setOnClickListener {
aapsLogger.debug("USER ENTRY: Error dialog mute 5 min button pressed")
stopAlarm()
loopHandler.postDelayed(this::startAlarm, T.mins(5).msecs())
}
startAlarm() startAlarm()
} }
@ -69,12 +85,18 @@ class ErrorDialog : DaggerDialogFragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
overview_error_status.text = status binding.status.text = status
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
override fun dismiss() { override fun dismiss() {
super.dismissAllowingStateLoss() super.dismissAllowingStateLoss()
helperActivity?.finish() helperActivity?.finish()
loopHandler.removeCallbacksAndMessages(null)
stopAlarm() stopAlarm()
} }

View file

@ -67,13 +67,17 @@ public class Notification {
public static final int OVER_24H_TIME_CHANGE_REQUESTED = 54; public static final int OVER_24H_TIME_CHANGE_REQUESTED = 54;
public static final int INVALID_VERSION = 55; public static final int INVALID_VERSION = 55;
public static final int PERMISSION_SYSTEM_WINDOW = 56; public static final int PERMISSION_SYSTEM_WINDOW = 56;
public static final int OMNIPOD_PUMP_ALARM = 57;
public static final int TIME_OR_TIMEZONE_CHANGE = 58; public static final int TIME_OR_TIMEZONE_CHANGE = 58;
public static final int OMNIPOD_POD_NOT_ATTACHED = 59; public static final int OMNIPOD_POD_NOT_ATTACHED = 59;
public static final int CARBS_REQUIRED = 60; public static final int CARBS_REQUIRED = 60;
public static final int OMNIPOD_POD_SUSPENDED = 61; public static final int OMNIPOD_POD_SUSPENDED = 61;
public static final int OMNIPOD_POD_ALERTS_UPDATED = 62; public static final int OMNIPOD_POD_ALERTS_UPDATED = 62;
public static final int OMNIPOD_POD_ALERTS = 63; public static final int OMNIPOD_POD_ALERTS = 63;
public static final int OMNIPOD_UNCERTAIN_TBR = 64;
public static final int OMNIPOD_POD_FAULT = 66;
public static final int OMNIPOD_UNCERTAIN_SMB = 67;
public static final int OMNIPOD_UNKNOWN_TBR = 68;
public static final int OMNIPOD_STARTUP_STATUS_REFRESH_FAILED = 69;
public static final int IMPORTANCE_HIGH = 2; public static final int IMPORTANCE_HIGH = 2;

View file

@ -21,7 +21,7 @@
app:srcCompat="@drawable/ic_error_red_48dp" /> app:srcCompat="@drawable/ic_error_red_48dp" />
<TextView <TextView
android:id="@+id/error_title" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
@ -42,27 +42,43 @@
android:padding="5dp" /> android:padding="5dp" />
<TextView <TextView
android:id="@+id/overview_error_status" android:id="@+id/status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingLeft="10dp" android:paddingLeft="10dp"
android:paddingRight="10dp" android:paddingRight="10dp"
android:layout_gravity="center_horizontal" /> android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<Button <Button
android:id="@+id/overview_error_mute" android:id="@+id/mute_5min"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="15dp" android:layout_marginTop="15dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:text="@string/mute5min" />
<Button
android:id="@+id/mute"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="15dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:text="@string/mute" /> android:text="@string/mute" />
<Button <Button
android:id="@+id/overview_error_ok" android:id="@+id/ok"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp" android:layout_marginTop="15dp"
android:layout_marginBottom="30dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:text="@string/ok" /> android:text="@string/ok" />
</LinearLayout> </LinearLayout>

View file

@ -262,6 +262,7 @@
<string name="history_group_prime">Prime</string> <string name="history_group_prime">Prime</string>
<string name="history_group_alarm">Alarms</string> <string name="history_group_alarm">Alarms</string>
<string name="history_group_glucose">Glucose</string> <string name="history_group_glucose">Glucose</string>
<string name="mute5min">Mute for 5 minutes</string>
<!-- <string name="medtronic_pump_status_never_contacted">Never contacted</string>--> <!-- <string name="medtronic_pump_status_never_contacted">Never contacted</string>-->
<!-- <string name="medtronic_pump_status_waking_up">Waking up</string>--> <!-- <string name="medtronic_pump_status_waking_up">Waking up</string>-->

View file

@ -54,6 +54,7 @@ import info.nightscout.androidaps.plugins.common.ManufacturerType;
import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction;
import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType;
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification;
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification;
import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair;
@ -382,12 +383,14 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
} else { } else {
// Not sure what's going on. Notify the user // Not sure what's going on. Notify the user
aapsLogger.error(LTag.PUMP, "Unknown TBR in both Pod state and AAPS"); aapsLogger.error(LTag.PUMP, "Unknown TBR in both Pod state and AAPS");
rxBus.send(new EventNewNotification(new Notification(Notification.OMNIPOD_PUMP_ALARM, resourceHelper.gs(R.string.omnipod_error_tbr_running_but_aaps_not_aware), Notification.NORMAL).sound(R.raw.boluserror))); rxBus.send(new EventNewNotification(new Notification(Notification.OMNIPOD_UNKNOWN_TBR, resourceHelper.gs(R.string.omnipod_error_tbr_running_but_aaps_not_aware), Notification.NORMAL).sound(R.raw.boluserror)));
} }
} else if (!podStateManager.isTempBasalRunning() && tempBasal != null) { } else if (!podStateManager.isTempBasalRunning() && tempBasal != null) {
aapsLogger.warn(LTag.PUMP, "Removing AAPS TBR that actually hadn't succeeded"); aapsLogger.warn(LTag.PUMP, "Removing AAPS TBR that actually hadn't succeeded");
activePlugin.getActiveTreatments().removeTempBasal(tempBasal); activePlugin.getActiveTreatments().removeTempBasal(tempBasal);
} }
rxBus.send(new EventDismissNotification(Notification.OMNIPOD_UNCERTAIN_TBR));
} }
private void handleActivePodAlerts() { private void handleActivePodAlerts() {
@ -1030,7 +1033,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
} }
if (!success) { if (!success) {
aapsLogger.warn(LTag.PUMP, "Failed to retrieve Pod status on startup"); aapsLogger.warn(LTag.PUMP, "Failed to retrieve Pod status on startup");
rxBus.send(new EventNewNotification(new Notification(Notification.OMNIPOD_PUMP_ALARM, resourceHelper.gs(R.string.omnipod_error_failed_to_refresh_status_on_startup), Notification.NORMAL))); rxBus.send(new EventNewNotification(new Notification(Notification.OMNIPOD_STARTUP_STATUS_REFRESH_FAILED, resourceHelper.gs(R.string.omnipod_error_failed_to_refresh_status_on_startup), Notification.NORMAL)));
} }
} else { } else {
aapsLogger.debug(LTag.PUMP, "Not retrieving Pod status on startup: no Pod running"); aapsLogger.debug(LTag.PUMP, "Not retrieving Pod status on startup: no Pod running");

View file

@ -105,14 +105,14 @@ public abstract class PodStateManager {
/** /**
* @return true if the Pod's activation time has been exceeded * @return true if the Pod's activation time has been exceeded
*/ */
public boolean isPodActivationTimeExceeded() { public final boolean isPodActivationTimeExceeded() {
return isPodInitialized() && getPodProgressStatus() == PodProgressStatus.ACTIVATION_TIME_EXCEEDED; return isPodInitialized() && getPodProgressStatus() == PodProgressStatus.ACTIVATION_TIME_EXCEEDED;
} }
/** /**
* @return true if we have a Pod state and the Pod is dead, meaning it is either in a fault state or activation time has been exceeded or it is deactivated * @return true if we have a Pod state and the Pod is dead, meaning it is either in a fault state or activation time has been exceeded or it is deactivated
*/ */
public boolean isPodDead() { public final boolean isPodDead() {
return isPodInitialized() && getPodProgressStatus().isDead(); return isPodInitialized() && getPodProgressStatus().isDead();
} }
@ -603,6 +603,8 @@ public abstract class PodStateManager {
podState.setLastUpdatedFromResponse(DateTime.now()); podState.setLastUpdatedFromResponse(DateTime.now());
}); });
onUpdatedFromResponse();
} }
protected void onTbrChanged() { protected void onTbrChanged() {
@ -625,6 +627,11 @@ public abstract class PodStateManager {
// Can be overridden in subclasses // Can be overridden in subclasses
} }
protected void onUpdatedFromResponse() {
// Deliberately left empty
// Can be overridden in subclasses
}
private void setAndStore(Runnable runnable) { private void setAndStore(Runnable runnable) {
setSafe(runnable); setSafe(runnable);
storePodState(); storePodState();

View file

@ -222,7 +222,7 @@ public class AapsOmnipodManager {
uploadCareportalEvent(System.currentTimeMillis() - 1000, CareportalEvent.INSULINCHANGE); uploadCareportalEvent(System.currentTimeMillis() - 1000, CareportalEvent.INSULINCHANGE);
uploadCareportalEvent(System.currentTimeMillis(), CareportalEvent.SITECHANGE); uploadCareportalEvent(System.currentTimeMillis(), CareportalEvent.SITECHANGE);
sendEvent(new EventDismissNotification(Notification.OMNIPOD_POD_NOT_ATTACHED)); dismissNotification(Notification.OMNIPOD_POD_NOT_ATTACHED);
cancelSuspendedFakeTbrIfExists(); cancelSuspendedFakeTbrIfExists();
} }
@ -269,6 +269,7 @@ public class AapsOmnipodManager {
} }
addSuccessToHistory(PodHistoryEntryType.GET_POD_STATUS, statusResponse); addSuccessToHistory(PodHistoryEntryType.GET_POD_STATUS, statusResponse);
return new PumpEnactResult(injector).success(true).enacted(false); return new PumpEnactResult(injector).success(true).enacted(false);
} }
@ -284,13 +285,17 @@ public class AapsOmnipodManager {
addSuccessToHistory(PodHistoryEntryType.DEACTIVATE_POD, null); addSuccessToHistory(PodHistoryEntryType.DEACTIVATE_POD, null);
createSuspendedFakeTbrIfNotExists(); createSuspendedFakeTbrIfNotExists();
dismissNotification(Notification.OMNIPOD_POD_FAULT);
return new PumpEnactResult(injector).success(true).enacted(true); return new PumpEnactResult(injector).success(true).enacted(true);
} }
public PumpEnactResult setBasalProfile(Profile profile, boolean showNotifications) { public PumpEnactResult setBasalProfile(Profile profile, boolean showNotifications) {
if (profile == null) { if (profile == null) {
String note = getStringResource(R.string.omnipod_error_failed_to_set_profile_empty_profile); String note = getStringResource(R.string.omnipod_error_failed_to_set_profile_empty_profile);
if (showNotifications) {
showNotification(Notification.FAILED_UDPATE_PROFILE, note, Notification.URGENT, R.raw.boluserror); showNotification(Notification.FAILED_UDPATE_PROFILE, note, Notification.URGENT, R.raw.boluserror);
}
return new PumpEnactResult(injector).success(false).enacted(false).comment(note); return new PumpEnactResult(injector).success(false).enacted(false).comment(note);
} }
@ -329,16 +334,20 @@ public class AapsOmnipodManager {
return new PumpEnactResult(injector).success(false).enacted(false).comment(errorMessage); return new PumpEnactResult(injector).success(false).enacted(false).comment(errorMessage);
} }
if (historyEntryType == PodHistoryEntryType.RESUME_DELIVERY) { if (historyEntryType == PodHistoryEntryType.RESUME_DELIVERY) {
cancelSuspendedFakeTbrIfExists(); cancelSuspendedFakeTbrIfExists();
sendEvent(new EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED));
} }
addSuccessToHistory(historyEntryType, profile.getBasalValues()); addSuccessToHistory(historyEntryType, profile.getBasalValues());
if (showNotifications) { if (showNotifications) {
showNotification(Notification.PROFILE_SET_OK, resourceHelper.gs(R.string.profile_set_ok), Notification.INFO, null); showNotification(Notification.PROFILE_SET_OK, resourceHelper.gs(R.string.profile_set_ok), Notification.INFO, null);
} }
dismissNotification(Notification.FAILED_UDPATE_PROFILE);
dismissNotification(Notification.OMNIPOD_POD_SUSPENDED);
return new PumpEnactResult(injector).success(true).enacted(true); return new PumpEnactResult(injector).success(true).enacted(true);
} }
@ -349,8 +358,9 @@ public class AapsOmnipodManager {
createSuspendedFakeTbrIfNotExists(); createSuspendedFakeTbrIfNotExists();
dismissNotification(Notification.OMNIPOD_POD_FAULT);
sendEvent(new EventOmnipodPumpValuesChanged()); sendEvent(new EventOmnipodPumpValuesChanged());
rxBus.send(new EventRefreshOverview("Omnipod command: " + OmnipodCommandType.DISCARD_POD, false)); sendEvent(new EventRefreshOverview("Omnipod command: " + OmnipodCommandType.DISCARD_POD, false));
return new PumpEnactResult(injector).success(true).enacted(true); return new PumpEnactResult(injector).success(true).enacted(true);
} }
@ -380,7 +390,7 @@ public class AapsOmnipodManager {
if (OmnipodManager.CommandDeliveryStatus.UNCERTAIN_FAILURE.equals(bolusCommandResult.getCommandDeliveryStatus())) { if (OmnipodManager.CommandDeliveryStatus.UNCERTAIN_FAILURE.equals(bolusCommandResult.getCommandDeliveryStatus())) {
// For safety reasons, we treat this as a bolus that has successfully been delivered, in order to prevent insulin overdose // For safety reasons, we treat this as a bolus that has successfully been delivered, in order to prevent insulin overdose
if (detailedBolusInfo.isSMB) { if (detailedBolusInfo.isSMB) {
showNotification(getStringResource(R.string.omnipod_error_bolus_failed_uncertain_smb, detailedBolusInfo.insulin), Notification.URGENT, isNotificationUncertainSmbSoundEnabled() ? R.raw.boluserror : null); showNotification(Notification.OMNIPOD_UNCERTAIN_SMB, getStringResource(R.string.omnipod_error_bolus_failed_uncertain_smb, detailedBolusInfo.insulin), Notification.URGENT, isNotificationUncertainSmbSoundEnabled() ? R.raw.boluserror : null);
} else { } else {
showErrorDialog(getStringResource(R.string.omnipod_error_bolus_failed_uncertain), isNotificationUncertainBolusSoundEnabled() ? R.raw.boluserror : null); showErrorDialog(getStringResource(R.string.omnipod_error_bolus_failed_uncertain), isNotificationUncertainBolusSoundEnabled() ? R.raw.boluserror : null);
} }
@ -483,7 +493,7 @@ public class AapsOmnipodManager {
String errorMessage = translateException(ex.getCause()); String errorMessage = translateException(ex.getCause());
addFailureToHistory(PodHistoryEntryType.SET_TEMPORARY_BASAL, errorMessage); addFailureToHistory(PodHistoryEntryType.SET_TEMPORARY_BASAL, errorMessage);
showNotification(getStringResource(R.string.omnipod_error_set_temp_basal_failed_old_tbr_might_be_cancelled), Notification.URGENT, isNotificationUncertainTbrSoundEnabled() ? R.raw.boluserror : null); showNotification(Notification.OMNIPOD_UNCERTAIN_TBR, getStringResource(R.string.omnipod_error_set_temp_basal_failed_old_tbr_might_be_cancelled), Notification.URGENT, isNotificationUncertainTbrSoundEnabled() ? R.raw.boluserror : null);
splitActiveTbr(); // Split any active TBR so when we recover from the uncertain TBR status,we only cancel the part after the cancellation splitActiveTbr(); // Split any active TBR so when we recover from the uncertain TBR status,we only cancel the part after the cancellation
@ -493,7 +503,7 @@ public class AapsOmnipodManager {
long pumpId = addFailureToHistory(PodHistoryEntryType.SET_TEMPORARY_BASAL, errorMessage); long pumpId = addFailureToHistory(PodHistoryEntryType.SET_TEMPORARY_BASAL, errorMessage);
if (!OmnipodManager.isCertainFailure(ex)) { if (!OmnipodManager.isCertainFailure(ex)) {
showNotification(getStringResource(R.string.omnipod_error_set_temp_basal_failed_old_tbr_cancelled_new_might_have_failed), Notification.URGENT, isNotificationUncertainTbrSoundEnabled() ? R.raw.boluserror : null); showNotification(Notification.OMNIPOD_UNCERTAIN_TBR, getStringResource(R.string.omnipod_error_set_temp_basal_failed_old_tbr_cancelled_new_might_have_failed), Notification.URGENT, isNotificationUncertainTbrSoundEnabled() ? R.raw.boluserror : null);
// Assume that setting the temp basal succeeded here, because in case it didn't succeed, // Assume that setting the temp basal succeeded here, because in case it didn't succeed,
// The next StatusResponse that we receive will allow us to recover from the wrong state // The next StatusResponse that we receive will allow us to recover from the wrong state
@ -523,7 +533,7 @@ public class AapsOmnipodManager {
executeCommand(() -> delegate.cancelTemporaryBasal(isTbrBeepsEnabled())); executeCommand(() -> delegate.cancelTemporaryBasal(isTbrBeepsEnabled()));
} catch (Exception ex) { } catch (Exception ex) {
if (OmnipodManager.isCertainFailure(ex)) { if (OmnipodManager.isCertainFailure(ex)) {
showNotification(getStringResource(R.string.omnipod_error_cancel_temp_basal_failed_uncertain), Notification.URGENT, isNotificationUncertainTbrSoundEnabled() ? R.raw.boluserror : null); showNotification(Notification.OMNIPOD_UNCERTAIN_TBR, getStringResource(R.string.omnipod_error_cancel_temp_basal_failed_uncertain), Notification.URGENT, isNotificationUncertainTbrSoundEnabled() ? R.raw.boluserror : null);
} else { } else {
splitActiveTbr(); // Split any active TBR so when we recover from the uncertain TBR status,we only cancel the part after the cancellation splitActiveTbr(); // Split any active TBR so when we recover from the uncertain TBR status,we only cancel the part after the cancellation
} }
@ -580,21 +590,21 @@ public class AapsOmnipodManager {
} catch (CommandFailedAfterChangingDeliveryStatusException ex) { } catch (CommandFailedAfterChangingDeliveryStatusException ex) {
createSuspendedFakeTbrIfNotExists(); createSuspendedFakeTbrIfNotExists();
if (showNotifications) { if (showNotifications) {
showNotification(getStringResource(R.string.omnipod_error_set_time_failed_delivery_suspended), Notification.URGENT, R.raw.boluserror); showNotification(Notification.FAILED_UDPATE_PROFILE, getStringResource(R.string.omnipod_error_set_time_failed_delivery_suspended), Notification.URGENT, R.raw.boluserror);
} }
String errorMessage = translateException(ex.getCause()); String errorMessage = translateException(ex.getCause());
addFailureToHistory(PodHistoryEntryType.SET_TIME, errorMessage); addFailureToHistory(PodHistoryEntryType.SET_TIME, errorMessage);
return new PumpEnactResult(injector).success(false).enacted(false).comment(errorMessage); return new PumpEnactResult(injector).success(false).enacted(false).comment(errorMessage);
} catch (PrecedingCommandFailedUncertainlyException ex) { } catch (PrecedingCommandFailedUncertainlyException ex) {
if (showNotifications) { if (showNotifications) {
showNotification(getStringResource(R.string.omnipod_error_set_time_failed_delivery_might_be_suspended), Notification.URGENT, R.raw.boluserror); showNotification(Notification.FAILED_UDPATE_PROFILE, getStringResource(R.string.omnipod_error_set_time_failed_delivery_might_be_suspended), Notification.URGENT, R.raw.boluserror);
} }
String errorMessage = translateException(ex.getCause()); String errorMessage = translateException(ex.getCause());
addFailureToHistory(PodHistoryEntryType.SET_TIME, errorMessage); addFailureToHistory(PodHistoryEntryType.SET_TIME, errorMessage);
return new PumpEnactResult(injector).success(false).enacted(false).comment(errorMessage); return new PumpEnactResult(injector).success(false).enacted(false).comment(errorMessage);
} catch (Exception ex) { } catch (Exception ex) {
if (showNotifications) { if (showNotifications) {
showNotification(getStringResource(R.string.omnipod_error_set_time_failed_delivery_might_be_suspended), Notification.URGENT, R.raw.boluserror); showNotification(Notification.FAILED_UDPATE_PROFILE, getStringResource(R.string.omnipod_error_set_time_failed_delivery_might_be_suspended), Notification.URGENT, R.raw.boluserror);
} }
String errorMessage = translateException(ex); String errorMessage = translateException(ex);
addFailureToHistory(PodHistoryEntryType.SET_TIME, errorMessage); addFailureToHistory(PodHistoryEntryType.SET_TIME, errorMessage);
@ -602,6 +612,10 @@ public class AapsOmnipodManager {
} }
addSuccessToHistory(PodHistoryEntryType.SET_TIME, null); addSuccessToHistory(PodHistoryEntryType.SET_TIME, null);
dismissNotification(Notification.FAILED_UDPATE_PROFILE);
dismissNotification(Notification.OMNIPOD_POD_SUSPENDED);
return new PumpEnactResult(injector).success(true).enacted(true); return new PumpEnactResult(injector).success(true).enacted(true);
} }
@ -937,11 +951,7 @@ public class AapsOmnipodManager {
} }
private void showPodFaultNotification(FaultEventCode faultEventCode, Integer sound) { private void showPodFaultNotification(FaultEventCode faultEventCode, Integer sound) {
showNotification(createPodFaultErrorMessage(faultEventCode), Notification.URGENT, sound); showNotification(Notification.OMNIPOD_POD_FAULT, createPodFaultErrorMessage(faultEventCode), Notification.URGENT, sound);
}
private void showNotification(String message, int urgency, Integer sound) {
showNotification(Notification.OMNIPOD_PUMP_ALARM, message, urgency, sound);
} }
private void showNotification(int id, String message, int urgency, Integer sound) { private void showNotification(int id, String message, int urgency, Integer sound) {
@ -955,6 +965,10 @@ public class AapsOmnipodManager {
sendEvent(new EventNewNotification(notification)); sendEvent(new EventNewNotification(notification));
} }
private void dismissNotification(int id) {
sendEvent(new EventDismissNotification(id));
}
private String getStringResource(int id, Object... args) { private String getStringResource(int id, Object... args) {
return resourceHelper.gs(id, args); return resourceHelper.gs(id, args);
} }

View file

@ -5,6 +5,8 @@ import javax.inject.Singleton;
import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification;
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification;
import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodStorageKeys; import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodStorageKeys;
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager; import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager;
import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodActiveAlertsChanged; import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodActiveAlertsChanged;
@ -50,4 +52,8 @@ public class AapsPodStateManager extends PodStateManager {
@Override protected void onFaultEventChanged() { @Override protected void onFaultEventChanged() {
rxBus.send(new EventOmnipodFaultEventChanged()); rxBus.send(new EventOmnipodFaultEventChanged());
} }
@Override protected void onUpdatedFromResponse() {
rxBus.send(new EventDismissNotification(Notification.OMNIPOD_STARTUP_STATUS_REFRESH_FAILED));
}
} }

View file

@ -16,6 +16,8 @@ import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.CommandQueueProvider import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice
@ -126,7 +128,8 @@ class OmnipodOverviewFragment : DaggerFragment() {
disablePodActionButtons() disablePodActionButtons()
commandQueue.customCommand(CommandAcknowledgeAlerts(), commandQueue.customCommand(CommandAcknowledgeAlerts(),
DisplayResultDialogCallback(resourceHelper.gs(R.string.omnipod_error_failed_to_acknowledge_alerts), false) DisplayResultDialogCallback(resourceHelper.gs(R.string.omnipod_error_failed_to_acknowledge_alerts), false)
.messageOnSuccess(resourceHelper.gs(R.string.omnipod_confirmation_acknowledged_alerts))) .messageOnSuccess(resourceHelper.gs(R.string.omnipod_confirmation_acknowledged_alerts))
.actionOnSuccess { rxBus.send(EventDismissNotification(Notification.OMNIPOD_POD_ALERTS)) })
} }
omnipod_overview_button_suspend_delivery.setOnClickListener { omnipod_overview_button_suspend_delivery.setOnClickListener {
@ -599,6 +602,7 @@ class OmnipodOverviewFragment : DaggerFragment() {
inner class DisplayResultDialogCallback(private val errorMessagePrefix: String, private val withSoundOnError: Boolean) : Callback() { inner class DisplayResultDialogCallback(private val errorMessagePrefix: String, private val withSoundOnError: Boolean) : Callback() {
private var messageOnSuccess: String? = null private var messageOnSuccess: String? = null
private var actionOnSuccess: Runnable? = null
override fun run() { override fun run() {
if (result.success) { if (result.success) {
@ -606,6 +610,7 @@ class OmnipodOverviewFragment : DaggerFragment() {
if (messageOnSuccess != null) { if (messageOnSuccess != null) {
displayOkDialog(resourceHelper.gs(R.string.omnipod_confirmation), messageOnSuccess) displayOkDialog(resourceHelper.gs(R.string.omnipod_confirmation), messageOnSuccess)
} }
actionOnSuccess?.run()
} else { } else {
displayErrorDialog(resourceHelper.gs(R.string.omnipod_warning), resourceHelper.gs(R.string.omnipod_two_strings_concatenated_by_colon, errorMessagePrefix, result.comment), withSoundOnError) displayErrorDialog(resourceHelper.gs(R.string.omnipod_warning), resourceHelper.gs(R.string.omnipod_two_strings_concatenated_by_colon, errorMessagePrefix, result.comment), withSoundOnError)
} }
@ -615,6 +620,11 @@ class OmnipodOverviewFragment : DaggerFragment() {
messageOnSuccess = message messageOnSuccess = message
return this return this
} }
fun actionOnSuccess(action: Runnable): DisplayResultDialogCallback {
actionOnSuccess = action
return this
}
} }
} }