AUTO: user actions & new layout

This commit is contained in:
Milos Kozak 2021-10-28 20:21:15 +02:00
parent 04a104a9b0
commit a56bdcc7c8
54 changed files with 1063 additions and 709 deletions

View file

@ -12,11 +12,6 @@ import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesFragm
import info.nightscout.androidaps.plugins.constraints.objectives.activities.ObjectivesExamDialog
import info.nightscout.androidaps.plugins.general.actions.ActionsFragment
import info.nightscout.androidaps.plugins.general.automation.AutomationFragment
import info.nightscout.androidaps.plugins.general.automation.dialogs.ChooseActionDialog
import info.nightscout.androidaps.plugins.general.automation.dialogs.ChooseTriggerDialog
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditActionDialog
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditTriggerDialog
import info.nightscout.androidaps.plugins.general.food.FoodFragment
import info.nightscout.androidaps.plugins.general.maintenance.MaintenanceFragment
import info.nightscout.androidaps.plugins.general.nsclient.NSClientFragment
@ -30,6 +25,7 @@ import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpFragment
import info.nightscout.androidaps.plugins.source.BGSourceFragment
import info.nightscout.androidaps.activities.fragments.*
import info.nightscout.androidaps.plugins.general.automation.dialogs.*
import info.nightscout.androidaps.utils.protection.PasswordCheck
@Module
@ -81,6 +77,7 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesFillDialog(): FillDialog
@ContributesAndroidInjector abstract fun contributesChooseActionDialog(): ChooseActionDialog
@ContributesAndroidInjector abstract fun contributesChooseTriggerDialog(): ChooseTriggerDialog
@ContributesAndroidInjector abstract fun contributesChooseOperationDialog(): ChooseOperationDialog
@ContributesAndroidInjector abstract fun contributesInsulinDialog(): InsulinDialog
@ContributesAndroidInjector abstract fun contributesLoopDialog(): LoopDialog
@ContributesAndroidInjector abstract fun contributesObjectivesExamDialog(): ObjectivesExamDialog

View file

@ -12,6 +12,7 @@ import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnLongClickListener
@ -49,6 +50,7 @@ import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview
@ -72,6 +74,7 @@ import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP
import info.nightscout.androidaps.utils.ui.SingleClickButton
import info.nightscout.androidaps.utils.ui.UIRunnable
import info.nightscout.androidaps.utils.wizard.QuickWizard
import io.reactivex.disposables.CompositeDisposable
@ -118,6 +121,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider
@Inject lateinit var overviewData: OverviewData
@Inject lateinit var overviewPlugin: OverviewPlugin
@Inject lateinit var automationPlugin: AutomationPlugin
private val disposable = CompositeDisposable()
@ -459,6 +463,32 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.buttonsLayout.calibrationButton.visibility = ((xDripIsBgSource || dexcomIsSource) && actualBG != null && sp.getBoolean(R.string.key_show_calibration_button, true)).toVisibility()
binding.buttonsLayout.cgmButton.visibility = (sp.getBoolean(R.string.key_show_cgm_button, false) && (xDripIsBgSource || dexcomIsSource)).toVisibility()
// Automation buttons
binding.buttonsLayout.userButtonsLayout.removeAllViews()
val events = automationPlugin.userEvents()
for (event in events)
if (event.isEnabled && event.trigger.shouldRun())
context?.let { context ->
SingleClickButton(context).also {
it.setTextColor(resourceHelper.gc(R.color.colorTreatmentButton))
it.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10f)
it.layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 0.5f).also { l ->
l.setMargins(0, 0, resourceHelper.dpToPx(-4), 0)
}
it.setCompoundDrawablesWithIntrinsicBounds(null, resourceHelper.gd(R.drawable.ic_danar_useropt), null, null)
it.text = event.title
it.setOnClickListener {
OKDialog.showConfirmation(
context,
resourceHelper.gs(R.string.run_question, event.title),
{ handler.post { automationPlugin.processEvent(event, true) } }
)
}
binding.buttonsLayout.userButtonsLayout.addView(it)
}
}
binding.buttonsLayout.userButtonsLayout.visibility = events.isNotEmpty().toVisibility()
}
private fun processAps() {

View file

@ -18,6 +18,16 @@
android:textColor="@color/colorAcceptTempButton"
android:visibility="gone" />
<LinearLayout
android:id="@+id/user_buttons_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="0dp"
android:paddingEnd="5dp" >
</LinearLayout>
<LinearLayout
android:id="@+id/buttons_layout"
android:layout_width="match_parent"

View file

@ -20,9 +20,6 @@
<color name="error_background">#66FC0000</color>
<color name="ok_background">#323232</color>
<color name="defaultbackground">#424242</color>
<color name="defaulttextcolor">#B3FFFFFF</color>
<color name="tempTargetBackground">#77dd77</color>
<color name="exercise">#67DFE8</color>

View file

@ -1124,5 +1124,6 @@
<string name="error_in_basal_values">Error in basal values</string>
<string name="error_in_target_values">Error in target values</string>
<string name="error_in_isf_values">Error in ISF values</string>
<string name="run_question">Run %s?</string>
</resources>

View file

@ -4,7 +4,6 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.logging.AAPSLogger
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.triggers.Trigger
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerDummy
import info.nightscout.androidaps.utils.DateUtil
@ -24,8 +23,9 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
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 userAction: Boolean = false // shows button on Overview
var trigger: Trigger = TriggerConnector(injector)
var trigger: TriggerConnector = TriggerConnector(injector)
val actions: MutableList<Action> = ArrayList()
var lastRun: Long = 0
@ -44,7 +44,7 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
fun addAction(action: Action) = actions.add(action)
fun areActionsValid() : Boolean {
fun areActionsValid(): Boolean {
var result = true
for (action in actions) result = result && action.isValid()
return result
@ -59,6 +59,7 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
.put("systemAction", systemAction)
.put("readOnly", readOnly)
.put("autoRemove", autoRemove)
.put("userAction", userAction)
.put("trigger", trigger.toJSON())
.put("actions", array)
.toString()
@ -71,7 +72,8 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
systemAction = d.optBoolean("systemAction", false)
readOnly = d.optBoolean("readOnly", false)
autoRemove = d.optBoolean("autoRemove", false)
trigger = TriggerDummy(injector).instantiate(JSONObject(d.getString("trigger")))
userAction = d.optBoolean("userAction", false)
trigger = TriggerDummy(injector).instantiate(JSONObject(d.getString("trigger"))) as TriggerConnector
val array = d.getJSONArray("actions")
actions.clear()
for (i in 0 until array.length()) {

View file

@ -100,14 +100,14 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
.toObservable(EventAutomationUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
updateGui()
}, fabricPrivacy::logException)
updateGui()
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventAutomationDataChanged::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
eventListAdapter.notifyDataSetChanged()
}, fabricPrivacy::logException)
eventListAdapter.notifyDataSetChanged()
}, fabricPrivacy::logException)
updateGui()
}
@ -167,14 +167,19 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val event = automationPlugin.at(position)
holder.binding.rootLayout.setBackgroundColor(resourceHelper.gc(if (event.areActionsValid()) R.color.ribbonDefault else R.color.errorAlertBackground))
holder.binding.rootLayout.setBackgroundColor(resourceHelper.gc(
if (event.userAction) R.color.mdtp_line_dark
else if (event.areActionsValid()) R.color.ribbonDefault
else R.color.errorAlertBackground)
)
holder.binding.eventTitle.text = event.title
holder.binding.enabled.isChecked = event.isEnabled
holder.binding.enabled.isEnabled = !event.readOnly
holder.binding.iconLayout.removeAllViews()
// trigger icons
val triggerIcons = HashSet<Int>()
fillIconSet(event.trigger as TriggerConnector, triggerIcons)
if (event.userAction) triggerIcons.add(R.drawable.ic_danar_useropt)
fillIconSet(event.trigger, triggerIcons)
for (res in triggerIcons) {
addImage(res, holder.context, holder.binding.iconLayout)
}
@ -217,13 +222,13 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
// remove event
holder.binding.iconTrash.setOnClickListener {
OKDialog.showConfirmation(requireContext(), resourceHelper.gs(R.string.removerecord) + " " + automationPlugin.at(position).title,
{
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, automationPlugin.at(position).title)
automationPlugin.removeAt(position)
notifyItemRemoved(position)
}, {
rxBus.send(EventAutomationUpdateGui())
})
{
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, automationPlugin.at(position).title)
automationPlugin.removeAt(position)
notifyItemRemoved(position)
}, {
rxBus.send(EventAutomationUpdateGui())
})
}
holder.binding.iconTrash.visibility = (!event.readOnly).toVisibility()
holder.binding.aapsLogo.visibility = (event.systemAction).toVisibility()
@ -239,13 +244,15 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
override fun onItemDismiss(position: Int) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.removerecord) + " " + automationPlugin.at(position).title,
Runnable {
OKDialog.showConfirmation(
activity,
resourceHelper.gs(R.string.removerecord) + " " + automationPlugin.at(position).title,
{
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, automationPlugin.at(position).title)
automationPlugin.removeAt(position)
notifyItemRemoved(position)
rxBus.send(EventAutomationDataChanged())
}, Runnable { rxBus.send(EventAutomationUpdateGui()) })
}, { rxBus.send(EventAutomationUpdateGui()) })
}
}

View file

@ -55,17 +55,18 @@ class AutomationPlugin @Inject constructor(
private val config: Config,
private val locationServiceHelper: LocationServiceHelper,
private val dateUtil: DateUtil
) : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(AutomationFragment::class.qualifiedName)
.pluginIcon(R.drawable.ic_automation)
.pluginName(R.string.automation)
.shortName(R.string.automation_short)
.showInList(config.APS)
.neverVisible(!config.APS)
.alwaysEnabled(!config.APS)
.preferencesId(R.xml.pref_automation)
.description(R.string.automation_description),
) : PluginBase(
PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(AutomationFragment::class.qualifiedName)
.pluginIcon(R.drawable.ic_automation)
.pluginName(R.string.automation)
.shortName(R.string.automation_short)
.showInList(config.APS)
.neverVisible(!config.APS)
.alwaysEnabled(!config.APS)
.preferencesId(R.xml.pref_automation)
.description(R.string.automation_description),
aapsLogger, resourceHelper, injector
) {
@ -82,7 +83,8 @@ class AutomationPlugin @Inject constructor(
companion object {
const val event = "{\"title\":\"Low\",\"enabled\":true,\"trigger\":\"{\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\\\",\\\"data\\\":{\\\"connectorType\\\":\\\"AND\\\",\\\"triggerList\\\":[\\\"{\\\\\\\"type\\\\\\\":\\\\\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerBg\\\\\\\",\\\\\\\"data\\\\\\\":{\\\\\\\"bg\\\\\\\":4,\\\\\\\"comparator\\\\\\\":\\\\\\\"IS_LESSER\\\\\\\",\\\\\\\"units\\\\\\\":\\\\\\\"mmol\\\\\\\"}}\\\",\\\"{\\\\\\\"type\\\\\\\":\\\\\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerDelta\\\\\\\",\\\\\\\"data\\\\\\\":{\\\\\\\"value\\\\\\\":-0.1,\\\\\\\"units\\\\\\\":\\\\\\\"mmol\\\\\\\",\\\\\\\"deltaType\\\\\\\":\\\\\\\"DELTA\\\\\\\",\\\\\\\"comparator\\\\\\\":\\\\\\\"IS_LESSER\\\\\\\"}}\\\"]}}\",\"actions\":[\"{\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.actions.ActionStartTempTarget\\\",\\\"data\\\":{\\\"value\\\":8,\\\"units\\\":\\\"mmol\\\",\\\"durationInMinutes\\\":60}}\"]}"
const val event =
"{\"title\":\"Low\",\"enabled\":true,\"trigger\":\"{\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\\\",\\\"data\\\":{\\\"connectorType\\\":\\\"AND\\\",\\\"triggerList\\\":[\\\"{\\\\\\\"type\\\\\\\":\\\\\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerBg\\\\\\\",\\\\\\\"data\\\\\\\":{\\\\\\\"bg\\\\\\\":4,\\\\\\\"comparator\\\\\\\":\\\\\\\"IS_LESSER\\\\\\\",\\\\\\\"units\\\\\\\":\\\\\\\"mmol\\\\\\\"}}\\\",\\\"{\\\\\\\"type\\\\\\\":\\\\\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerDelta\\\\\\\",\\\\\\\"data\\\\\\\":{\\\\\\\"value\\\\\\\":-0.1,\\\\\\\"units\\\\\\\":\\\\\\\"mmol\\\\\\\",\\\\\\\"deltaType\\\\\\\":\\\\\\\"DELTA\\\\\\\",\\\\\\\"comparator\\\\\\\":\\\\\\\"IS_LESSER\\\\\\\"}}\\\"]}}\",\"actions\":[\"{\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.actions.ActionStartTempTarget\\\",\\\"data\\\":{\\\"value\\\":8,\\\"units\\\":\\\"mmol\\\",\\\"durationInMinutes\\\":60}}\"]}"
}
init {
@ -103,11 +105,11 @@ class AutomationPlugin @Inject constructor(
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ e ->
if (e.isChanged(resourceHelper, R.string.key_location)) {
locationServiceHelper.stopService(context)
locationServiceHelper.startService(context)
}
}, fabricPrivacy::logException)
if (e.isChanged(resourceHelper, R.string.key_location)) {
locationServiceHelper.stopService(context)
locationServiceHelper.startService(context)
}
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventAutomationDataChanged::class.java)
.observeOn(aapsSchedulers.io)
@ -116,11 +118,11 @@ class AutomationPlugin @Inject constructor(
.toObservable(EventLocationChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ e ->
e?.let {
aapsLogger.debug(LTag.AUTOMATION, "Grabbed location: $it.location.latitude $it.location.longitude Provider: $it.location.provider")
processActions()
}
}, fabricPrivacy::logException)
e?.let {
aapsLogger.debug(LTag.AUTOMATION, "Grabbed location: $it.location.latitude $it.location.longitude Provider: $it.location.provider")
processActions()
}
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventChargingState::class.java)
.observeOn(aapsSchedulers.io)
@ -137,10 +139,10 @@ class AutomationPlugin @Inject constructor(
.toObservable(EventBTChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
aapsLogger.debug(LTag.AUTOMATION, "Grabbed new BT event: $it")
btConnects.add(it)
processActions()
}, fabricPrivacy::logException)
aapsLogger.debug(LTag.AUTOMATION, "Grabbed new BT event: $it")
btConnects.add(it)
processActions()
}, fabricPrivacy::logException)
}
override fun onStop() {
@ -201,40 +203,8 @@ class AutomationPlugin @Inject constructor(
val iterator: MutableIterator<AutomationEvent> = automationEvents.iterator()
while (iterator.hasNext()) {
val event = iterator.next()
if (event.isEnabled && event.shouldRun() && event.trigger.shouldRun() && event.getPreconditions().shouldRun()) {
if (event.systemAction || userEventsEnabled) {
val actions = event.actions
for (action in actions) {
action.title = event.title
if (action.isValid())
action.doAction(object : Callback() {
override fun run() {
val sb = StringBuilder()
sb.append(dateUtil.timeString(dateUtil.now()))
sb.append(" ")
sb.append(if (result.success) "" else "")
sb.append(" <b>")
sb.append(event.title)
sb.append(":</b> ")
sb.append(action.shortDescription())
sb.append(": ")
sb.append(result.comment)
executionLog.add(sb.toString())
aapsLogger.debug(LTag.AUTOMATION, "Executed: $sb")
rxBus.send(EventAutomationUpdateGui())
}
})
else {
executionLog.add("Invalid action: ${action.shortDescription()}")
aapsLogger.debug(LTag.AUTOMATION, "Invalid action: ${action.shortDescription()}")
rxBus.send(EventAutomationUpdateGui())
}
}
SystemClock.sleep(1100)
event.lastRun = dateUtil.now()
if (event.autoRemove) automationEvents.remove(event)
}
}
if (event.isEnabled && !event.userAction && event.shouldRun())
processEvent(event, userEventsEnabled)
}
// we cannot detect connected BT devices
// so let's collect all connection/disconnections between 2 runs of processActions()
@ -245,6 +215,43 @@ class AutomationPlugin @Inject constructor(
storeToSP() // save last run time
}
fun processEvent(event: AutomationEvent, userEventsEnabled: Boolean) {
if (event.trigger.shouldRun() && event.getPreconditions().shouldRun()) {
if (event.systemAction || userEventsEnabled) {
val actions = event.actions
for (action in actions) {
action.title = event.title
if (action.isValid())
action.doAction(object : Callback() {
override fun run() {
val sb = StringBuilder()
sb.append(dateUtil.timeString(dateUtil.now()))
sb.append(" ")
sb.append(if (result.success) "" else "")
sb.append(" <b>")
sb.append(event.title)
sb.append(":</b> ")
sb.append(action.shortDescription())
sb.append(": ")
sb.append(result.comment)
executionLog.add(sb.toString())
aapsLogger.debug(LTag.AUTOMATION, "Executed: $sb")
rxBus.send(EventAutomationUpdateGui())
}
})
else {
executionLog.add("Invalid action: ${action.shortDescription()}")
aapsLogger.debug(LTag.AUTOMATION, "Invalid action: ${action.shortDescription()}")
rxBus.send(EventAutomationUpdateGui())
}
}
SystemClock.sleep(1100)
event.lastRun = dateUtil.now()
if (event.autoRemove) automationEvents.remove(event)
}
}
}
@Synchronized
fun add(event: AutomationEvent) {
automationEvents.add(event)
@ -283,6 +290,16 @@ class AutomationPlugin @Inject constructor(
rxBus.send(EventAutomationDataChanged())
}
fun userEvents(): List<AutomationEvent> {
val list = mutableListOf<AutomationEvent>()
val iterator: MutableIterator<AutomationEvent> = automationEvents.iterator()
while (iterator.hasNext()) {
val event = iterator.next()
if (event.userAction) list.add(event)
}
return list
}
fun getActionDummyObjects(): List<Action> {
return listOf(
//ActionLoopDisable(injector),
@ -318,7 +335,7 @@ class AutomationPlugin @Inject constructor(
TriggerAutosensValue(injector),
TriggerBolusAgo(injector),
TriggerPumpLastConnection(injector),
TriggerBTDevice(injector)
TriggerBTDevice(injector),
)
}
}

View file

@ -0,0 +1,96 @@
package info.nightscout.androidaps.plugins.general.automation.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioButton
import info.nightscout.androidaps.automation.databinding.AutomationDialogChooseOperationBinding
import info.nightscout.androidaps.dialogs.DialogFragmentWithDate
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector
import info.nightscout.androidaps.utils.resources.ResourceHelper
import javax.inject.Inject
class ChooseOperationDialog : DialogFragmentWithDate() {
@Inject lateinit var resourceHelper: ResourceHelper
private var checkedIndex = -1
private var _binding: AutomationDialogChooseOperationBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
abstract class Callback : Runnable {
var result: Int? = null
fun result(result: Int?): Callback {
this.result = result
return this
}
}
private var callback: Callback? = null
fun setCallback(callback: Callback): ChooseOperationDialog {
this.callback = callback
return this
}
fun setCheckedIndex(checkedIndex: Int): ChooseOperationDialog {
this.checkedIndex = checkedIndex
return this
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// restore checked radio button
savedInstanceState?.let { bundle ->
checkedIndex = bundle.getInt("checkedIndex")
}
onCreateViewGeneral()
_binding = AutomationDialogChooseOperationBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
for (t in TriggerConnector.Type.labels(resourceHelper)) {
val radioButton = RadioButton(context)
radioButton.text = t
binding.chooseOperationRadioGroup.addView(radioButton)
}
if (checkedIndex != -1)
(binding.chooseOperationRadioGroup.getChildAt(checkedIndex) as RadioButton).isChecked = true
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun submit(): Boolean {
callback?.result(determineCheckedIndex())?.run()
return true
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putInt("checkedIndex", determineCheckedIndex())
}
private fun determineCheckedIndex(): Int {
for (i in 0 until binding.chooseOperationRadioGroup.childCount) {
if ((binding.chooseOperationRadioGroup.getChildAt(i) as RadioButton).isChecked)
return i
}
return -1
}
}

View file

@ -13,6 +13,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.automation.databinding.AutomationDialogEventBinding
import info.nightscout.androidaps.dialogs.DialogFragmentWithDate
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.AutomationEvent
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
@ -22,13 +23,11 @@ import info.nightscout.androidaps.plugins.general.automation.events.EventAutomat
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateAction
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateTrigger
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.ToastUtils
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import javax.inject.Inject
class EditEventDialog : DialogFragmentWithDate() {
@ -73,6 +72,8 @@ class EditEventDialog : DialogFragmentWithDate() {
binding.inputEventTitle.setText(event.title)
binding.inputEventTitle.isFocusable = !event.readOnly
binding.triggerDescription.text = event.trigger.friendlyDescription()
binding.userAction.isChecked = event.userAction
binding.enabled.isChecked = event.isEnabled
binding.editTrigger.visibility = (!event.readOnly).toVisibility()
binding.editTrigger.setOnClickListener {
@ -131,9 +132,11 @@ class EditEventDialog : DialogFragmentWithDate() {
return false
}
event.title = title
event.userAction = binding.userAction.isChecked
event.isEnabled = binding.enabled.isChecked
// check for at least one trigger
val con = event.trigger as TriggerConnector
if (con.size() == 0) {
val con = event.trigger
if (con.size() == 0 && !event.userAction) {
ToastUtils.showToastInUiThread(context, R.string.automation_missing_trigger)
return false
}

View file

@ -16,9 +16,9 @@ import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerDummy
import info.nightscout.androidaps.utils.FabricPrivacy
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import org.json.JSONObject
import javax.inject.Inject
@ -31,7 +31,7 @@ class EditTriggerDialog : DialogFragmentWithDate() {
private var disposable: CompositeDisposable = CompositeDisposable()
private var triggers: Trigger? = null
private var triggers: TriggerConnector? = null
private var _binding: AutomationDialogEditTriggerBinding? = null
@ -39,11 +39,13 @@ class EditTriggerDialog : DialogFragmentWithDate() {
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// load data from bundle
(savedInstanceState ?: arguments)?.let { bundle ->
bundle.getString("trigger")?.let { triggers = TriggerDummy(injector).instantiate(JSONObject(it)) }
bundle.getString("trigger")?.let { triggers = TriggerDummy(injector).instantiate(JSONObject(it)) as TriggerConnector }
}
onCreateViewGeneral()
@ -58,26 +60,26 @@ class EditTriggerDialog : DialogFragmentWithDate() {
.toObservable(EventTriggerChanged::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
binding.layoutTrigger.removeAllViews()
triggers?.generateDialog(binding.layoutTrigger)
}, fabricPrivacy::logException)
binding.layoutTrigger.removeAllViews()
triggers?.generateDialog(binding.layoutTrigger)
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTriggerRemove::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
findParent(triggers, it.trigger)?.list?.remove(it.trigger)
binding.layoutTrigger.removeAllViews()
triggers?.generateDialog(binding.layoutTrigger)
}, fabricPrivacy::logException)
findParent(triggers, it.trigger)?.list?.remove(it.trigger)
binding.layoutTrigger.removeAllViews()
triggers?.generateDialog(binding.layoutTrigger)
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTriggerClone::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
findParent(triggers, it.trigger)?.list?.add(it.trigger.duplicate())
binding.layoutTrigger.removeAllViews()
triggers?.generateDialog(binding.layoutTrigger)
}, fabricPrivacy::logException)
findParent(triggers, it.trigger)?.list?.add(it.trigger.duplicate())
binding.layoutTrigger.removeAllViews()
triggers?.generateDialog(binding.layoutTrigger)
}, fabricPrivacy::logException)
// display root trigger
triggers?.generateDialog(binding.layoutTrigger)

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
@ -42,6 +43,7 @@ class Comparator(private val resourceHelper: ResourceHelper) : Element() {
}
companion object {
fun labels(resourceHelper: ResourceHelper): List<String> {
val list: MutableList<String> = ArrayList()
for (c in values()) {
@ -59,25 +61,24 @@ class Comparator(private val resourceHelper: ResourceHelper) : Element() {
var value = Compare.IS_EQUAL
override fun addToLayout(root: LinearLayout) {
val spinner = Spinner(root.context)
val spinnerArrayAdapter = ArrayAdapter(root.context, R.layout.spinner_centered, Compare.labels(resourceHelper))
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerArrayAdapter
val spinnerParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
spinnerParams.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
spinner.layoutParams = spinnerParams
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = Compare.values()[position]
}
root.addView(
Spinner(root.context).apply {
adapter = ArrayAdapter(root.context, R.layout.spinner_centered, Compare.labels(resourceHelper)).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setMargins(0, resourceHelper.dpToPx(1), 0, resourceHelper.dpToPx(1))
}
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = Compare.values()[position]
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
spinner.setSelection(value.ordinal)
root.addView(spinner)
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
setSelection(value.ordinal)
gravity = Gravity.CENTER_HORIZONTAL
})
}
fun setValue(compare: Compare): Comparator {

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
@ -16,11 +17,12 @@ class ComparatorConnect(private val resourceHelper: ResourceHelper) : Element()
@get:StringRes val stringRes: Int
get() = when (this) {
ON_CONNECT -> R.string.onconnect
ON_CONNECT -> R.string.onconnect
ON_DISCONNECT -> R.string.ondisconnect
}
companion object {
fun labels(resourceHelper: ResourceHelper): List<String> {
val list: MutableList<String> = ArrayList()
for (c in values()) list.add(resourceHelper.gs(c.stringRes))
@ -36,21 +38,24 @@ class ComparatorConnect(private val resourceHelper: ResourceHelper) : Element()
var value = Compare.ON_CONNECT
override fun addToLayout(root: LinearLayout) {
val spinner = Spinner(root.context)
val spinnerArrayAdapter = ArrayAdapter(root.context, R.layout.spinner_centered, Compare.labels(resourceHelper))
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerArrayAdapter
val spinnerParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
spinnerParams.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
spinner.layoutParams = spinnerParams
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = Compare.values()[position]
}
root.addView(
Spinner(root.context).apply {
adapter = ArrayAdapter(root.context, R.layout.spinner_centered, Compare.labels(resourceHelper)).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
spinner.setSelection(value.ordinal)
root.addView(spinner)
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
}
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = Compare.values()[position]
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
setSelection(value.ordinal)
gravity = Gravity.CENTER_HORIZONTAL
})
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
@ -16,11 +17,12 @@ class ComparatorExists(private val resourceHelper: ResourceHelper, var value: Co
@get:StringRes val stringRes: Int
get() = when (this) {
EXISTS -> R.string.exists
EXISTS -> R.string.exists
NOT_EXISTS -> R.string.notexists
}
companion object {
fun labels(resourceHelper: ResourceHelper): List<String> {
val list: MutableList<String> = ArrayList()
for (c in values()) list.add(resourceHelper.gs(c.stringRes))
@ -30,21 +32,24 @@ class ComparatorExists(private val resourceHelper: ResourceHelper, var value: Co
}
override fun addToLayout(root: LinearLayout) {
val spinner = Spinner(root.context)
val spinnerArrayAdapter = ArrayAdapter(root.context, R.layout.spinner_centered, Compare.labels(resourceHelper))
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerArrayAdapter
val spinnerParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
spinnerParams.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
spinner.layoutParams = spinnerParams
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = Compare.values()[position]
}
root.addView(
Spinner(root.context).apply {
adapter = ArrayAdapter(root.context, R.layout.spinner_centered, Compare.labels(resourceHelper)).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
spinner.setSelection(value.ordinal)
root.addView(spinner)
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = Compare.values()[position]
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
setSelection(value.ordinal)
gravity = Gravity.CENTER_HORIZONTAL
})
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.widget.LinearLayout
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.interfaces.GlucoseUnit
@ -26,13 +27,15 @@ class InputBg(profileFunction: ProfileFunction) : Element() {
}
override fun addToLayout(root: LinearLayout) {
val numberPicker = NumberPicker(root.context, null)
numberPicker.setParams(value, minValue, maxValue, step, decimalFormat, false, root.findViewById(R.id.ok))
numberPicker.setOnValueChangedListener { value: Double -> this.value = value }
root.addView(numberPicker)
root.addView(
NumberPicker(root.context, null).apply {
setParams(value, minValue, maxValue, step, decimalFormat, false, root.findViewById(R.id.ok))
setOnValueChangedListener { value: Double -> this.value = value }
gravity = Gravity.CENTER_HORIZONTAL
})
}
fun setValue(value: Double) : InputBg {
fun setValue(value: Double): InputBg {
this.value = value
return this
}
@ -54,6 +57,7 @@ class InputBg(profileFunction: ProfileFunction) : Element() {
}
companion object {
const val MMOL_MIN = 3.0
const val MMOL_MAX = 20.0
const val MGDL_MIN = 54.0

View file

@ -1,9 +1,11 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.widget.Button
import android.widget.LinearLayout
class InputButton() : Element() {
var text: String? = null
var runnable: Runnable? = null
@ -13,9 +15,11 @@ class InputButton() : Element() {
}
override fun addToLayout(root: LinearLayout) {
val button = Button(root.context)
button.text = text
button.setOnClickListener { runnable?.run() }
root.addView(button)
root.addView(
Button(root.context).apply {
text = text
setOnClickListener { runnable?.run() }
gravity = Gravity.CENTER_HORIZONTAL
})
}
}

View file

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

View file

@ -15,71 +15,71 @@ import java.util.*
class InputDateTime(private val resourceHelper: ResourceHelper, private val dateUtil: DateUtil, var value: Long = dateUtil.now()) : Element() {
override fun addToLayout(root: LinearLayout) {
val label = TextView(root.context)
val dateButton = TextView(root.context)
val timeButton = TextView(root.context)
dateButton.text = dateUtil.dateString(value)
timeButton.text = dateUtil.timeString(value)
// create an OnDateSetListener
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
val cal = Calendar.getInstance()
cal.timeInMillis = value
cal.set(Calendar.YEAR, year)
cal.set(Calendar.MONTH, monthOfYear)
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth)
value = cal.timeInMillis
dateButton.text = dateUtil.dateString(value)
}
dateButton.setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = value
DatePickerDialog(it, dateSetListener,
cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH)
).show()
}
}
// create an OnTimeSetListener
val timeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute ->
val cal = Calendar.getInstance()
cal.timeInMillis = value
cal.set(Calendar.HOUR_OF_DAY, hour)
cal.set(Calendar.MINUTE, minute)
cal.set(Calendar.SECOND, 0) // randomize seconds to prevent creating record of the same time, if user choose time manually
value = cal.timeInMillis
timeButton.text = dateUtil.timeString(value)
}
timeButton.setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = value
TimePickerDialog(it, timeSetListener,
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
DateFormat.is24HourFormat(it)
).show()
}
}
val px = resourceHelper.dpToPx(10)
label.text = resourceHelper.gs(R.string.atspecifiedtime, "")
label.setTypeface(label.typeface, Typeface.BOLD)
label.setPadding(px, px, px, px)
dateButton.setPadding(px, px, px, px)
timeButton.setPadding(px, px, px, px)
val l = LinearLayout(root.context)
l.orientation = LinearLayout.HORIZONTAL
l.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
l.addView(label)
l.addView(dateButton)
l.addView(timeButton)
root.addView(l)
}
root.addView(
LinearLayout(root.context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
addView(
TextView(root.context).apply {
text = resourceHelper.gs(R.string.atspecifiedtime, "")
setTypeface(typeface, Typeface.BOLD)
setPadding(px, px, px, px)
})
addView(
TextView(root.context).apply {
text = dateUtil.dateString(value)
setPadding(px, px, px, px)
setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = value
DatePickerDialog(
it,
{ _, year, monthOfYear, dayOfMonth ->
value = Calendar.getInstance().apply {
timeInMillis = value
set(Calendar.YEAR, year)
set(Calendar.MONTH, monthOfYear)
set(Calendar.DAY_OF_MONTH, dayOfMonth)
}.timeInMillis
text = dateUtil.dateString(value)
},
cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH)
).show()
}
}
})
addView(
TextView(root.context).apply {
text = dateUtil.timeString(value)
setPadding(px, px, px, px)
setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = value
TimePickerDialog(
it,
{ _, hour, minute ->
value = Calendar.getInstance().apply {
timeInMillis = value
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0) // randomize seconds to prevent creating record of the same time, if user choose time manually
}.timeInMillis
text = dateUtil.timeString(value)
},
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
DateFormat.is24HourFormat(it)
).show()
}
}
}
)
})
}
}

View file

@ -1,15 +1,15 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import android.widget.Spinner
import androidx.annotation.StringRes
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.utils.ui.NumberPicker
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.ui.NumberPicker
import java.text.DecimalFormat
class InputDelta(private val resourceHelper: ResourceHelper) : Element() {
@ -19,12 +19,13 @@ class InputDelta(private val resourceHelper: ResourceHelper) : Element() {
@get:StringRes val stringRes: Int
get() = when (this) {
DELTA -> R.string.delta
DELTA -> R.string.delta
SHORT_AVERAGE -> R.string.short_avgdelta
LONG_AVERAGE -> R.string.long_avgdelta
LONG_AVERAGE -> R.string.long_avgdelta
}
companion object {
fun labels(resourceHelper: ResourceHelper): List<String> {
val list: MutableList<String> = ArrayList()
for (d in values()) {
@ -61,32 +62,29 @@ class InputDelta(private val resourceHelper: ResourceHelper) : Element() {
}
override fun addToLayout(root: LinearLayout) {
val spinner = Spinner(root.context)
val spinnerArrayAdapter = ArrayAdapter(root.context, R.layout.spinner_centered, DeltaType.labels(resourceHelper))
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerArrayAdapter
val spinnerParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
spinnerParams.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
spinner.layoutParams = spinnerParams
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
deltaType = DeltaType.values()[position]
}
root.addView(
Spinner(root.context).apply {
adapter = ArrayAdapter(root.context, R.layout.spinner_centered, DeltaType.labels(resourceHelper)).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
}
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
deltaType = DeltaType.values()[position]
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
spinner.setSelection(deltaType.ordinal)
val numberPicker = NumberPicker(root.context, null)
numberPicker.setParams(value, minValue, maxValue, step, decimalFormat, true, null, null)
numberPicker.setOnValueChangedListener { value: Double -> this.value = value }
val l = LinearLayout(root.context)
l.orientation = LinearLayout.VERTICAL
l.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
l.addView(spinner)
l.addView(numberPicker)
root.addView(l)
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
setSelection(deltaType.ordinal)
gravity = Gravity.CENTER_HORIZONTAL
})
root.addView(
NumberPicker(root.context, null).apply {
setParams(value, minValue, maxValue, step, decimalFormat, true, null, null)
setOnValueChangedListener { value: Double -> this.value = value }
gravity = Gravity.CENTER_HORIZONTAL
})
}
}

View file

@ -1,11 +1,13 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.widget.LinearLayout
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.utils.ui.NumberPicker
import java.text.DecimalFormat
class InputDouble() : Element() {
var value = 0.0
private var minValue = 0.0
private var maxValue = 0.0
@ -30,9 +32,11 @@ class InputDouble() : Element() {
}
override fun addToLayout(root: LinearLayout) {
numberPicker = NumberPicker(root.context, null)
numberPicker?.setParams(value, minValue, maxValue, step, decimalFormat, true, root.findViewById(R.id.ok))
numberPicker?.setOnValueChangedListener { value: Double -> this.value = value }
numberPicker = NumberPicker(root.context, null).apply {
setParams(value, minValue, maxValue, step, decimalFormat, true, root.findViewById(R.id.ok))
setOnValueChangedListener { value: Double -> this.value = value }
gravity = Gravity.CENTER_HORIZONTAL
}
root.addView(numberPicker)
}

View file

@ -1,7 +1,7 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.LinearLayout
@ -25,31 +25,25 @@ class InputDropdownMenu(private val resourceHelper: ResourceHelper) : Element()
}
override fun addToLayout(root: LinearLayout) {
val spinner = Spinner(root.context)
spinner.adapter = ArrayAdapter(
root.context,
R.layout.spinner_centered, itemList
).also {
it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
spinner.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).also { it.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4)) }
root.addView(
Spinner(root.context).apply {
adapter = ArrayAdapter(root.context, R.layout.spinner_centered, itemList).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).also {
it.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
}
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
setValue(itemList[position].toString())
}
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
setValue(itemList[position].toString())
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
for (i in 0 until itemList.size) if (itemList[i] == value) spinner.setSelection(i)
root.addView(LinearLayout(root.context).also {
it.orientation = LinearLayout.VERTICAL
it.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
it.addView(spinner)
})
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
gravity = Gravity.CENTER_HORIZONTAL
for (i in 0 until itemList.size) if (itemList[i] == value) setSelection(i)
})
}
fun setValue(name: String): InputDropdownMenu {

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.widget.LinearLayout
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.utils.ui.MinutesNumberPicker
@ -25,6 +26,7 @@ class InputDuration(
numberPicker.setParams(value.toDouble(), 1.0, 24.0, 1.0, DecimalFormat("0"), false, root.findViewById(R.id.ok))
}
numberPicker.setOnValueChangedListener { value: Double -> this.value = value.toInt() }
numberPicker.gravity = Gravity.CENTER_HORIZONTAL
root.addView(numberPicker)
}

View file

@ -1,11 +1,13 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.widget.LinearLayout
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.utils.ui.NumberPicker
import java.text.DecimalFormat
class InputInsulin() : Element() {
var value = 0.0
constructor(another: InputInsulin) : this() {
@ -13,10 +15,12 @@ class InputInsulin() : Element() {
}
override fun addToLayout(root: LinearLayout) {
val numberPicker = NumberPicker(root.context, null)
numberPicker.setParams(0.0, -20.0, 20.0, 0.1, DecimalFormat("0.0"), true, root.findViewById(R.id.ok))
numberPicker.value = value
numberPicker.setOnValueChangedListener { value: Double -> this.value = value }
root.addView(numberPicker)
root.addView(
NumberPicker(root.context, null).apply {
setParams(0.0, -20.0, 20.0, 0.1, DecimalFormat("0.0"), true, root.findViewById(R.id.ok))
value = value
setOnValueChangedListener { value: Double -> this.value = value }
gravity = Gravity.CENTER_HORIZONTAL
})
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
@ -30,6 +31,7 @@ class InputLocationMode(private val resourceHelper: ResourceHelper) : Element()
}
companion object {
fun labels(resourceHelper: ResourceHelper): List<String> {
val list: MutableList<String> = ArrayList()
for (c in values()) {
@ -47,24 +49,24 @@ class InputLocationMode(private val resourceHelper: ResourceHelper) : Element()
}
override fun addToLayout(root: LinearLayout) {
val adapter = ArrayAdapter(root.context, R.layout.spinner_centered, Mode.labels(resourceHelper))
val spinner = Spinner(root.context)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter
val spinnerParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
spinnerParams.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
spinner.layoutParams = spinnerParams
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = Mode.values()[position]
}
root.addView(
Spinner(root.context).apply {
adapter = ArrayAdapter(root.context, R.layout.spinner_centered, Mode.labels(resourceHelper)).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
val spinnerParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
}
layoutParams = spinnerParams
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = Mode.values()[position]
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
spinner.setSelection(value.ordinal)
root.addView(spinner)
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
setSelection(value.ordinal)
gravity = Gravity.CENTER_HORIZONTAL
})
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.widget.LinearLayout
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.utils.ui.NumberPicker
@ -14,11 +15,13 @@ class InputPercent() : Element() {
}
override fun addToLayout(root: LinearLayout) {
val numberPicker = NumberPicker(root.context, null)
numberPicker.setParams(100.0, MIN, MAX, 5.0, DecimalFormat("0"), true, root.findViewById(R.id.ok))
numberPicker.value = value
numberPicker.setOnValueChangedListener { value: Double -> this.value = value }
root.addView(numberPicker)
root.addView(
NumberPicker(root.context, null).apply {
setParams(100.0, MIN, MAX, 5.0, DecimalFormat("0"), true, root.findViewById(R.id.ok))
value = value
setOnValueChangedListener { value: Double -> this.value = value }
gravity = Gravity.CENTER_HORIZONTAL
})
}
companion object {

View file

@ -1,7 +1,7 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.LinearLayout
@ -17,28 +17,24 @@ class InputProfileName(private val resourceHelper: ResourceHelper, private val a
override fun addToLayout(root: LinearLayout) {
val profileStore = activePlugin.activeProfileSource.profile ?: return
val profileList = profileStore.getProfileList()
val adapter = ArrayAdapter(root.context, R.layout.spinner_centered, profileList)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
val spinner = Spinner(root.context)
spinner.adapter = adapter
val spinnerParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
spinnerParams.setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
spinner.layoutParams = spinnerParams
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = profileList[position].toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
for (i in 0 until profileList.size) if (profileList[i] == value) spinner.setSelection(i)
val l = LinearLayout(root.context)
l.orientation = LinearLayout.VERTICAL
l.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
l.addView(spinner)
root.addView(l)
root.addView(
Spinner(root.context).apply {
adapter = ArrayAdapter(root.context, R.layout.spinner_centered, profileList).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setMargins(0, resourceHelper.dpToPx(4), 0, resourceHelper.dpToPx(4))
}
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
value = profileList[position].toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
for (i in 0 until profileList.size) if (profileList[i] == value) setSelection(i)
gravity = Gravity.CENTER_HORIZONTAL
})
}
}

View file

@ -2,11 +2,13 @@ package info.nightscout.androidaps.plugins.general.automation.elements
import android.text.Editable
import android.text.TextWatcher
import android.view.Gravity
import android.view.ViewGroup
import android.widget.EditText
import android.widget.LinearLayout
class InputString(var value: String = "") : Element() {
private val textWatcher: TextWatcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
@ -16,10 +18,12 @@ class InputString(var value: String = "") : Element() {
}
override fun addToLayout(root: LinearLayout) {
val editText = EditText(root.context)
editText.setText(value)
editText.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
editText.addTextChangedListener(textWatcher)
root.addView(editText)
root.addView(
EditText(root.context).apply {
setText(value)
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
addTextChangedListener(textWatcher)
gravity = Gravity.CENTER_HORIZONTAL
})
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.view.Gravity
import android.widget.LinearLayout
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.automation.R
@ -9,6 +10,7 @@ import info.nightscout.androidaps.utils.ui.NumberPicker
import java.text.DecimalFormat
class InputTempTarget(profileFunction: ProfileFunction) : Element() {
var units: GlucoseUnit = GlucoseUnit.MGDL
var value = 0.0
@ -38,9 +40,12 @@ class InputTempTarget(profileFunction: ProfileFunction) : Element() {
step = 1.0
decimalFormat = DecimalFormat("0")
}
val numberPicker = NumberPicker(root.context, null)
numberPicker.setParams(value, minValue, maxValue, step, decimalFormat, true, root.findViewById(R.id.ok))
numberPicker.setOnValueChangedListener { value: Double -> this.value = value }
root.addView(numberPicker)
root.addView(
NumberPicker(root.context, null).apply {
setParams(value, minValue, maxValue, step, decimalFormat, true, root.findViewById(R.id.ok))
setOnValueChangedListener { value: Double -> this.value = value }
gravity = Gravity.CENTER_HORIZONTAL
})
}
}

View file

@ -3,6 +3,7 @@ package info.nightscout.androidaps.plugins.general.automation.elements
import android.app.TimePickerDialog
import android.graphics.Typeface
import android.text.format.DateFormat
import android.view.Gravity
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
@ -18,37 +19,38 @@ class InputTime(private val resourceHelper: ResourceHelper, private val dateUtil
var value: Int = getMinSinceMidnight(dateUtil.now())
override fun addToLayout(root: LinearLayout) {
val label = TextView(root.context)
val startButton = TextView(root.context)
startButton.text = dateUtil.timeString(toMills(value))
val startTimeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute ->
value = 60 * hour + minute
startButton.text = dateUtil.timeString(toMills(value))
}
startButton.setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = toMills(value)
TimePickerDialog(it, startTimeSetListener,
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
DateFormat.is24HourFormat(it)
).show()
}
}
val px = resourceHelper.dpToPx(10)
label.text = resourceHelper.gs(R.string.atspecifiedtime, "")
label.setTypeface(label.typeface, Typeface.BOLD)
startButton.setPadding(px, px, px, px)
val l = LinearLayout(root.context)
l.orientation = LinearLayout.HORIZONTAL
l.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
l.addView(label)
l.addView(startButton)
root.addView(l)
root.addView(
LinearLayout(root.context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
addView(
TextView(root.context).apply {
text = resourceHelper.gs(R.string.atspecifiedtime, "")
setTypeface(typeface, Typeface.BOLD)
})
addView(
TextView(root.context).apply {
text = dateUtil.timeString(toMills(value))
val px = resourceHelper.dpToPx(10)
setPadding(px, px, px, px)
setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = toMills(value)
TimePickerDialog(
it,
{ _, hour, minute ->
value = 60 * hour + minute
text = dateUtil.timeString(toMills(value))
},
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
DateFormat.is24HourFormat(it)
).show()
}
}
})
})
}
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight)

View file

@ -3,6 +3,7 @@ package info.nightscout.androidaps.plugins.general.automation.elements
import android.app.TimePickerDialog
import android.graphics.Typeface
import android.text.format.DateFormat
import android.view.Gravity
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
@ -19,59 +20,62 @@ class InputTimeRange(private val resourceHelper: ResourceHelper, private val dat
var end: Int = getMinSinceMidnight(dateUtil.now())
override fun addToLayout(root: LinearLayout) {
val label = TextView(root.context)
val startButton = TextView(root.context)
val endButton = TextView(root.context)
startButton.text = dateUtil.timeString(toMills(start))
@Suppress("SetTextI18n")
endButton.text = resourceHelper.gs(R.string.and) + " " + dateUtil.timeString(toMills(end))
val startTimeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute ->
start = 60 * hour + minute
startButton.text = dateUtil.timeString(toMills(start))
}
startButton.setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = toMills(start)
TimePickerDialog(it, startTimeSetListener,
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
DateFormat.is24HourFormat(it)
).show()
}
}
val endTimeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute ->
end = 60 * hour + minute
endButton.text = dateUtil.timeString(toMills(end))
}
endButton.setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = toMills(end)
TimePickerDialog(it, endTimeSetListener,
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
DateFormat.is24HourFormat(it)
).show()
}
}
val px = resourceHelper.dpToPx(10)
label.text = resourceHelper.gs(R.string.between)
label.setTypeface(label.typeface, Typeface.BOLD)
startButton.setPadding(px, px, px, px)
endButton.setPadding(px, px, px, px)
val l = LinearLayout(root.context)
l.orientation = LinearLayout.HORIZONTAL
l.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
l.addView(label)
l.addView(startButton)
l.addView(endButton)
root.addView(l)
root.addView(
TextView(root.context).apply {
text = resourceHelper.gs(R.string.between)
setTypeface(typeface, Typeface.BOLD)
gravity = Gravity.CENTER_HORIZONTAL
})
root.addView(
LinearLayout(root.context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
gravity = Gravity.CENTER_HORIZONTAL
addView(
TextView(root.context).apply {
text = dateUtil.timeString(toMills(start))
setPadding(px, px, px, px)
setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = toMills(start)
TimePickerDialog(
it,
{ _, hour, minute ->
start = 60 * hour + minute
text = dateUtil.timeString(toMills(start))
},
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
DateFormat.is24HourFormat(it)
).show()
}
}
})
addView(TextView(root.context).apply {
@Suppress("SetTextI18n")
text = resourceHelper.gs(R.string.and) + " " + dateUtil.timeString(toMills(end))
setPadding(px, px, px, px)
setOnClickListener {
root.context?.let {
val cal = Calendar.getInstance()
cal.timeInMillis = toMills(end)
TimePickerDialog(
it,
{ _, hour, minute ->
end = 60 * hour + minute
text = dateUtil.timeString(toMills(end))
},
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
DateFormat.is24HourFormat(it)
).show()
}
}
})
})
}
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight)

View file

@ -2,7 +2,6 @@ package info.nightscout.androidaps.plugins.general.automation.elements
import android.widget.LinearLayout
import androidx.annotation.StringRes
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.utils.ui.WeekdayPicker
import java.util.*
@ -20,6 +19,7 @@ class InputWeekDay : Element() {
get() = shortNames[ordinal]
companion object {
private val calendarInts = intArrayOf(
Calendar.MONDAY,
Calendar.TUESDAY,
@ -76,10 +76,11 @@ class InputWeekDay : Element() {
}
override fun addToLayout(root: LinearLayout) {
WeekdayPicker(root.context).apply {
setSelectedDays(getSelectedDays())
setOnWeekdaysChangeListener { i: Int, selected: Boolean -> set(DayOfWeek.fromCalendarInt(i), selected) }
root.addView(this)
}
root.addView(
WeekdayPicker(root.context).apply {
setSelectedDays(getSelectedDays())
setOnWeekdaysChangeListener { i: Int, selected: Boolean -> set(DayOfWeek.fromCalendarInt(i), selected) }
}
)
}
}

View file

@ -1,9 +1,8 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.graphics.Typeface
import android.view.ViewGroup
import android.view.Gravity
import android.widget.LinearLayout
import android.widget.TableLayout
import android.widget.TextView
import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -15,35 +14,27 @@ class LabelWithElement(
) : Element() {
override fun addToLayout(root: LinearLayout) { // container layout
val layout = LinearLayout(root.context)
layout.orientation = LinearLayout.HORIZONTAL
layout.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
// text view pre element
var px = resourceHelper.dpToPx(10)
val textViewPre = TextView(root.context)
textViewPre.text = textPre
textViewPre.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
textViewPre.setPadding(px, px, px, px)
textViewPre.setTypeface(textViewPre.typeface, Typeface.BOLD)
layout.addView(textViewPre)
val spacer = TextView(root.context)
spacer.layoutParams = TableLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
layout.addView(spacer)
// add element to layout
element?.addToLayout(layout)
// text view post element
px = resourceHelper.dpToPx(5)
val textViewPost = TextView(root.context)
textViewPost.text = textPost
textViewPost.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
textViewPost.setPadding(px, px, px, px)
textViewPost.setTypeface(textViewPost.typeface, Typeface.BOLD)
layout.addView(textViewPost)
// add layout to root layout
root.addView(layout)
}
val px = resourceHelper.dpToPx(1)
root.addView(
TextView(root.context).apply {
text = textPre
setPadding(px, px, px, px)
setTypeface(typeface, Typeface.BOLD)
gravity = Gravity.CENTER
}
)
// add element to layout
element?.addToLayout(root)
// text view post element
root.addView(
TextView(root.context).apply {
text = textPost
setPadding(px, px, px, px)
setTypeface(typeface, Typeface.BOLD)
gravity = Gravity.CENTER
})
}
}

View file

@ -1,6 +1,10 @@
package info.nightscout.androidaps.plugins.general.automation.elements
import android.content.Context
import android.view.Gravity
import android.view.ViewGroup
import android.widget.LinearLayout
import info.nightscout.androidaps.automation.R
class LayoutBuilder {
@ -16,8 +20,21 @@ class LayoutBuilder {
}
fun build(layout: LinearLayout) {
for (e in mElements) {
e.addToLayout(layout)
val elementLayout = LinearLayout(layout.context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
setMargins(0, 0, dpToPx(layout.context, 2), dpToPx(layout.context, 2))
}
setBackgroundColor(layout.context.getColor(R.color.mdtp_line_dark))
}
for (e in mElements) {
e.addToLayout(elementLayout)
}
layout.addView(elementLayout)
}
fun dpToPx(context: Context, dp: Int): Int {
val scale = context.resources.displayMetrics.density
return (dp * scale + 0.5f).toInt()
}
}

View file

@ -4,10 +4,8 @@ import android.graphics.Typeface
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger
import info.nightscout.androidaps.utils.resources.ResourceHelper
import javax.inject.Inject
class StaticLabel(private val resourceHelper: ResourceHelper) : Element() {
@ -25,24 +23,25 @@ class StaticLabel(private val resourceHelper: ResourceHelper) : Element() {
}
override fun addToLayout(root: LinearLayout) {
val headerLayout = LinearLayout(root.context)
headerLayout.orientation = LinearLayout.HORIZONTAL
headerLayout.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
headerLayout.setBackgroundColor(resourceHelper.gc(android.R.color.black))
// text
val px = resourceHelper.dpToPx(10)
val textView = TextView(root.context)
textView.text = label
val params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.weight = 1.0f
textView.layoutParams = params
textView.setPadding(px, px, px, px)
textView.setTypeface(textView.typeface, Typeface.BOLD)
headerLayout.addView(textView)
trigger?.let {
headerLayout.addView(it.createDeleteButton(root.context, it))
headerLayout.addView(it.createCloneButton(root.context, it))
}
root.addView(headerLayout)
root.addView(
LinearLayout(root.context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
setBackgroundColor(resourceHelper.gc(android.R.color.black))
addView(
TextView(root.context).apply {
text = label
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
weight = 1.0f
}
setPadding(px, px, px, px)
setTypeface(typeface, Typeface.BOLD)
})
trigger?.let {
addView(it.createDeleteButton(root.context, it))
addView(it.createCloneButton(root.context, it))
}
})
}
}

View file

@ -1,6 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.events
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector
class EventAutomationUpdateTrigger(val trigger: Trigger) : Event()
class EventAutomationUpdateTrigger(val trigger: TriggerConnector) : Event()

View file

@ -56,7 +56,7 @@ abstract class Trigger(val injector: HasAndroidInjector) {
abstract fun icon(): Optional<Int?>
abstract fun duplicate(): Trigger
private fun scanForActivity(cont: Context?): AppCompatActivity? {
fun scanForActivity(cont: Context?): AppCompatActivity? {
return when (cont) {
null -> null
is AppCompatActivity -> cont
@ -84,27 +84,19 @@ abstract class Trigger(val injector: HasAndroidInjector) {
//return (clazz.primaryConstructor?.call(injector) as Trigger).fromJSON(data?.toString() ?: "")
return when (type) {
TriggerAutosensValue::class.java.name, // backward compatibility
TriggerAutosensValue::class.java.simpleName -> TriggerAutosensValue(injector).fromJSON(
data.toString()
)
TriggerAutosensValue::class.java.simpleName -> TriggerAutosensValue(injector).fromJSON(data.toString())
TriggerBg::class.java.name,
TriggerBg::class.java.simpleName -> TriggerBg(injector).fromJSON(data.toString())
TriggerBolusAgo::class.java.name,
TriggerBolusAgo::class.java.simpleName -> TriggerBolusAgo(injector).fromJSON(
data.toString()
)
TriggerBolusAgo::class.java.simpleName -> TriggerBolusAgo(injector).fromJSON(data.toString())
TriggerBTDevice::class.java.name,
TriggerBTDevice::class.java.simpleName -> TriggerBTDevice(injector).fromJSON(
data.toString()
)
TriggerBTDevice::class.java.simpleName -> TriggerBTDevice(injector).fromJSON(data.toString())
TriggerIob::class.java.name,
TriggerIob::class.java.simpleName -> TriggerIob(injector).fromJSON(data.toString())
TriggerCOB::class.java.name,
TriggerCOB::class.java.simpleName -> TriggerCOB(injector).fromJSON(data.toString())
TriggerConnector::class.java.name,
TriggerConnector::class.java.simpleName -> TriggerConnector(injector).fromJSON(
data.toString()
)
TriggerConnector::class.java.simpleName -> TriggerConnector(injector).fromJSON(data.toString())
TriggerDelta::class.java.name,
TriggerDelta::class.java.simpleName -> TriggerDelta(injector).fromJSON(data.toString())
TriggerDummy::class.java.name,
@ -112,100 +104,73 @@ abstract class Trigger(val injector: HasAndroidInjector) {
TriggerIob::class.java.name,
TriggerIob::class.java.simpleName -> TriggerIob(injector).fromJSON(data.toString())
TriggerLocation::class.java.name,
TriggerLocation::class.java.simpleName -> TriggerLocation(injector).fromJSON(
data.toString()
)
TriggerLocation::class.java.simpleName -> TriggerLocation(injector).fromJSON(data.toString())
TriggerProfilePercent::class.java.name,
TriggerProfilePercent::class.java.simpleName -> TriggerProfilePercent(injector).fromJSON(
data.toString()
)
TriggerProfilePercent::class.java.simpleName -> TriggerProfilePercent(injector).fromJSON(data.toString())
TriggerPumpLastConnection::class.java.name,
TriggerPumpLastConnection::class.java.simpleName -> TriggerPumpLastConnection(injector).fromJSON(
data.toString()
)
TriggerPumpLastConnection::class.java.simpleName -> TriggerPumpLastConnection(injector).fromJSON(data.toString())
TriggerRecurringTime::class.java.name,
TriggerRecurringTime::class.java.simpleName -> TriggerRecurringTime(injector).fromJSON(
data.toString()
)
TriggerRecurringTime::class.java.simpleName -> TriggerRecurringTime(injector).fromJSON(data.toString())
TriggerTempTarget::class.java.name,
TriggerTempTarget::class.java.simpleName -> TriggerTempTarget(injector).fromJSON(
data.toString()
)
TriggerTempTarget::class.java.simpleName -> TriggerTempTarget(injector).fromJSON(data.toString())
TriggerTempTargetValue::class.java.name,
TriggerTempTargetValue::class.java.simpleName -> TriggerTempTargetValue(injector).fromJSON(
data.toString()
)
TriggerTempTargetValue::class.java.simpleName -> TriggerTempTargetValue(injector).fromJSON(data.toString())
TriggerTime::class.java.name,
TriggerTime::class.java.simpleName -> TriggerTime(injector).fromJSON(data.toString())
TriggerTimeRange::class.java.name,
TriggerTimeRange::class.java.simpleName -> TriggerTimeRange(injector).fromJSON(
data.toString()
)
TriggerTimeRange::class.java.simpleName -> TriggerTimeRange(injector).fromJSON(data.toString())
TriggerWifiSsid::class.java.name,
TriggerWifiSsid::class.java.simpleName -> TriggerWifiSsid(injector).fromJSON(
data.toString()
)
TriggerWifiSsid::class.java.simpleName -> TriggerWifiSsid(injector).fromJSON(data.toString())
else -> TriggerConnector(injector)
}
}
fun createAddButton(context: Context, trigger: TriggerConnector): ImageButton {
fun createAddButton(context: Context, trigger: TriggerConnector): ImageButton =
// Button [+]
val buttonAdd = ImageButton(context)
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
params.gravity = Gravity.CENTER
buttonAdd.layoutParams = params
buttonAdd.setImageResource(R.drawable.ic_add)
buttonAdd.contentDescription = resourceHelper.gs(R.string.add_short)
buttonAdd.setOnClickListener {
scanForActivity(context)?.supportFragmentManager?.let {
val dialog = ChooseTriggerDialog()
dialog.show(it, "ChooseTriggerDialog")
dialog.setOnClickListener(object : ChooseTriggerDialog.OnClickListener {
override fun onClick(newTriggerObject: Trigger) {
trigger.list.add(newTriggerObject)
rxBus.send(EventTriggerChanged())
}
})
ImageButton(context).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
gravity = Gravity.CENTER
}
setImageResource(R.drawable.ic_add)
contentDescription = resourceHelper.gs(R.string.add_short)
setOnClickListener {
scanForActivity(context)?.supportFragmentManager?.let {
val dialog = ChooseTriggerDialog()
dialog.show(it, "ChooseTriggerDialog")
dialog.setOnClickListener(object : ChooseTriggerDialog.OnClickListener {
override fun onClick(newTriggerObject: Trigger) {
trigger.list.add(newTriggerObject)
rxBus.send(EventTriggerChanged())
}
})
}
}
}
return buttonAdd
}
fun createDeleteButton(context: Context, trigger: Trigger): ImageButton {
fun createDeleteButton(context: Context, trigger: Trigger): ImageButton =
// Button [-]
val buttonRemove = ImageButton(context)
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
params.gravity = Gravity.CENTER
buttonRemove.layoutParams = params
buttonRemove.setImageResource(R.drawable.ic_remove)
buttonRemove.contentDescription = resourceHelper.gs(R.string.delete_short)
buttonRemove.setOnClickListener {
rxBus.send(EventTriggerRemove(trigger))
ImageButton(context).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
gravity = Gravity.CENTER
}
setImageResource(R.drawable.ic_remove)
contentDescription = resourceHelper.gs(R.string.delete_short)
setOnClickListener {
rxBus.send(EventTriggerRemove(trigger))
}
}
return buttonRemove
}
fun createCloneButton(context: Context, trigger: Trigger): ImageButton {
fun createCloneButton(context: Context, trigger: Trigger): ImageButton =
// Button [*]
val buttonClone = ImageButton(context)
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
params.gravity = Gravity.CENTER
buttonClone.layoutParams = params
buttonClone.setImageResource(R.drawable.ic_clone)
buttonClone.contentDescription = resourceHelper.gs(R.string.copy_short)
buttonClone.setOnClickListener {
rxBus.send(EventTriggerClone(trigger))
ImageButton(context).apply {
val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
gravity = Gravity.CENTER
}
layoutParams = params
setImageResource(R.drawable.ic_clone)
contentDescription = resourceHelper.gs(R.string.copy_short)
setOnClickListener {
rxBus.send(EventTriggerClone(trigger))
}
}
return buttonClone
}
}

View file

@ -4,6 +4,7 @@ import android.widget.LinearLayout
import com.google.common.base.Optional
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator
@ -36,7 +37,8 @@ class TriggerBolusAgo(injector: HasAndroidInjector) : Trigger(injector) {
}
override fun shouldRun(): Boolean {
val lastBolusTime = repository.getLastBolusRecordOfType(Bolus.Type.NORMAL)?.timestamp ?: 0L
val lastBolus = repository.getLastBolusRecordOfTypeWrapped(Bolus.Type.NORMAL).blockingGet()
val lastBolusTime = if (lastBolus is ValueWrapper.Existing) lastBolus.value.timestamp else 0L
if (lastBolusTime == 0L)
return if (comparator.value == Comparator.Compare.IS_NOT_AVAILABLE) {
aapsLogger.debug(LTag.AUTOMATION, "Ready for execution: " + friendlyDescription())
@ -81,7 +83,7 @@ class TriggerBolusAgo(injector: HasAndroidInjector) : Trigger(injector) {
LayoutBuilder()
.add(StaticLabel(resourceHelper, R.string.lastboluslabel, this))
.add(comparator)
.add(LabelWithElement(resourceHelper, resourceHelper.gs(R.string.lastboluslabel) + ": ", "", minutesAgo))
.add(LabelWithElement(resourceHelper, resourceHelper.gs(R.string.lastboluslabel) + ": ", resourceHelper.gs(R.string.unit_minutes), minutesAgo))
.build(root)
}
}

View file

@ -1,19 +1,20 @@
package info.nightscout.androidaps.plugins.general.automation.triggers
import android.content.Context
import android.view.View
import android.graphics.Typeface
import android.view.Gravity
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import android.widget.Spinner
import android.widget.TextView
import androidx.annotation.StringRes
import com.google.common.base.Optional
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.general.automation.dialogs.ChooseOperationDialog
import info.nightscout.androidaps.utils.JsonHelper.safeGetString
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.ui.VerticalTextView
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
@ -35,7 +36,7 @@ class TriggerConnector(injector: HasAndroidInjector) : Trigger(injector) {
@get:StringRes val stringRes: Int
get() = when (this) {
OR -> R.string.or
OR -> R.string.or
XOR -> R.string.xor
AND -> R.string.and
}
@ -119,51 +120,75 @@ class TriggerConnector(injector: HasAndroidInjector) : Trigger(injector) {
override fun duplicate(): Trigger = TriggerConnector(injector, connectorType)
override fun generateDialog(root: LinearLayout) {
val padding = resourceHelper.dpToPx(5)
root.setPadding(padding, padding, padding, padding)
root.setBackgroundResource(R.drawable.border_automation_unit)
// Header with spinner
val headerLayout = LinearLayout(root.context)
headerLayout.orientation = LinearLayout.HORIZONTAL
headerLayout.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
headerLayout.addView(createSpinner(root.context))
headerLayout.addView(createAddButton(root.context, this))
headerLayout.addView(createDeleteButton(root.context, this))
root.addView(headerLayout)
// Child triggers
val listLayout = LinearLayout(root.context)
listLayout.orientation = LinearLayout.VERTICAL
listLayout.setBackgroundColor(resourceHelper.gc(R.color.mdtp_line_dark))
//listLayout.setPadding(resourceHelper.dpToPx(5), resourceHelper.dpToPx(5), resourceHelper.dpToPx(5), 0)
val params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.setMargins(resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(5), resourceHelper.dpToPx(4))
listLayout.layoutParams = params
for (t in list) t.generateDialog(listLayout)
root.addView(listLayout)
}
private fun createSpinner(context: Context): Spinner {
val initialPosition = connectorType.ordinal
val spinner = Spinner(context)
val spinnerArrayAdapter = ArrayAdapter(context, R.layout.spinner_centered, Type.labels(resourceHelper))
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerArrayAdapter
spinner.setSelection(initialPosition)
spinner.setBackgroundColor(resourceHelper.gc(R.color.black_overlay))
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
params.setMargins(0, resourceHelper.dpToPx(8), 0, resourceHelper.dpToPx(8))
params.weight = 1.0f
spinner.layoutParams = params
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
setType(Type.values()[position])
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
val mainLayout = LinearLayout(root.context).also {
it.orientation = LinearLayout.HORIZONTAL
it.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
return spinner
val padding = resourceHelper.dpToPx(3)
mainLayout.setPadding(padding, padding, padding, padding)
mainLayout.setBackgroundResource(R.drawable.border_automation_unit)
val buttonLayout = LinearLayout(root.context).also {
it.orientation = LinearLayout.HORIZONTAL
it.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
buttonLayout.addView(createAddButton(root.context, this))
buttonLayout.addView(createDeleteButton(root.context, this))
val rightSideLayout = LinearLayout(root.context).also {
it.orientation = LinearLayout.VERTICAL
it.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
}
rightSideLayout.addView(buttonLayout)
// Child triggers
val listLayout = LinearLayout(root.context).also {
it.orientation = LinearLayout.VERTICAL
it.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { params ->
params.setMargins(resourceHelper.dpToPx(1), 0, resourceHelper.dpToPx(1), resourceHelper.dpToPx(2))
}
}
for (t in list) {
t.generateDialog(listLayout)
listLayout.addView(
TextView(root.context).also {
it.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
it.setPadding(0, resourceHelper.dpToPx(0.3f), 0, 0)
})
}
rightSideLayout.addView(listLayout)
// Header with spinner
mainLayout.addView(createVerticalView(root.context))
mainLayout.addView(rightSideLayout)
root.addView(mainLayout)
}
private fun createVerticalView(context: Context): VerticalTextView =
VerticalTextView(context).apply {
text = resourceHelper.gs(connectorType.stringRes)
gravity = gravity or Gravity.CENTER_VERTICAL
setTypeface(typeface, Typeface.BOLD)
setBackgroundColor(resourceHelper.gc(R.color.black_overlay))
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT).also { ll ->
ll.setMargins(resourceHelper.dpToPx(3), resourceHelper.dpToPx(3), resourceHelper.dpToPx(3), resourceHelper.dpToPx(3))
}
setOnClickListener {
scanForActivity(context)?.supportFragmentManager?.let {
ChooseOperationDialog().also { dialog ->
dialog.setCallback(object : ChooseOperationDialog.Callback() {
override fun run() {
result?.let { result ->
setType(Type.values()[result])
text = resourceHelper.gs(connectorType.stringRes)
}
}
})
dialog.setCheckedIndex(connectorType.ordinal)
dialog.show(it, "TriggerConnector")
}
}
}
}
}

View file

@ -55,6 +55,12 @@ class TriggerProfilePercent(injector: HasAndroidInjector) : Trigger(injector) {
return true
}
}
if (profile is ProfileSealed.Pure) {
if (comparator.value.check(100.0, pct.value)) {
aapsLogger.debug(LTag.AUTOMATION, "Ready for execution: " + friendlyDescription())
return true
}
}
aapsLogger.debug(LTag.AUTOMATION, "NOT ready for execution: " + friendlyDescription())
return false
}

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true"
android:minWidth="300dp"
android:orientation="vertical"
tools:context="info.nightscout.androidaps.plugins.general.automation.dialogs.ChooseOperationDialog">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/dialog_title_background"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/please_choose_a_operation_type"
app:srcCompat="@drawable/ic_trigger_green_48dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="@string/please_choose_a_operation_type"
android:textAlignment="center"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
<RadioGroup
android:id="@+id/chooseOperationRadioGroup"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"/>
<include layout="@layout/okcancel" />
</LinearLayout>
</ScrollView>

View file

@ -7,7 +7,7 @@
android:focusableInTouchMode="true"
android:minWidth="300dp"
android:orientation="vertical"
tools:context=".plugins.general.automation.dialogs.EditEventDialog">
tools:context="info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog">
<LinearLayout
android:layout_width="match_parent"
@ -45,7 +45,7 @@
android:id="@+id/layoutTrigger"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:padding="0dp"
android:orientation="vertical" />
<include layout="@layout/okcancel" />

View file

@ -7,14 +7,14 @@
android:focusableInTouchMode="true"
android:minWidth="300dp"
android:orientation="vertical"
tools:context=".plugins.general.automation.dialogs.EditEventDialog">
tools:context="info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -29,17 +29,23 @@
app:srcCompat="@drawable/ic_action_orange_48dp" />
<TextView
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="@string/automation_event"
android:textAlignment="center"
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
<CheckBox
android:id="@+id/enabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
</LinearLayout>
<LinearLayout
android:id="@+id/spacer"
@ -54,6 +60,27 @@
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageButton
android:id="@+id/imageButton"
android:layout_width="27dp"
android:layout_height="match_parent"
android:scaleX="0.7"
android:scaleY="0.7"
android:src="@drawable/ic_danar_useropt" />
<CheckBox
android:id="@+id/user_action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/user_action" />
</LinearLayout>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputEventTitle"
android:layout_width="match_parent"

View file

@ -107,12 +107,13 @@
<string name="edit_short">EDIT</string>
<string name="please_choose_an_action_type">Choose an action type</string>
<string name="please_choose_a_trigger_type">Choose a trigger type</string>
<string name="please_choose_a_operation_type">Choose a operation type</string>
<string name="triggers">Triggers:</string>
<string name="remove_label">REMOVE</string>
<string name="preconditions">Preconditions:</string>
<string name="automation_event">Automation event</string>
<string name="reorder_label">Reorder</string>
<string name="key_automation_settings" translatable="false">automation_settings</string>
<string name="user_action">User action</string>
</resources>

View file

@ -8,6 +8,7 @@ import info.nightscout.androidaps.interfaces.Loop
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.actions.Action
import info.nightscout.androidaps.plugins.general.automation.actions.ActionLoopEnable
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnectorTest
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerDummy
import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -44,11 +45,12 @@ class AutomationEventTest : TestBase() {
// create test object
val event = AutomationEvent(injector)
event.title = "Test"
event.trigger = TriggerDummy(injector).instantiate(JSONObject(TriggerConnectorTest.oneItem))
event.trigger = TriggerDummy(injector).instantiate(JSONObject(TriggerConnectorTest.oneItem)) as TriggerConnector
event.addAction(ActionLoopEnable(injector))
// export to json
val eventJsonExpected = "{\"autoRemove\":false,\"readOnly\":false,\"trigger\":\"{\\\"data\\\":{\\\"connectorType\\\":\\\"AND\\\",\\\"triggerList\\\":[\\\"{\\\\\\\"data\\\\\\\":{\\\\\\\"connectorType\\\\\\\":\\\\\\\"AND\\\\\\\",\\\\\\\"triggerList\\\\\\\":[]},\\\\\\\"type\\\\\\\":\\\\\\\"TriggerConnector\\\\\\\"}\\\"]},\\\"type\\\":\\\"TriggerConnector\\\"}\",\"title\":\"Test\",\"systemAction\":false,\"actions\":[\"{\\\"type\\\":\\\"ActionLoopEnable\\\"}\"],\"enabled\":true}"
val eventJsonExpected =
"{\"userAction\":false,\"autoRemove\":false,\"readOnly\":false,\"trigger\":\"{\\\"data\\\":{\\\"connectorType\\\":\\\"AND\\\",\\\"triggerList\\\":[\\\"{\\\\\\\"data\\\\\\\":{\\\\\\\"connectorType\\\\\\\":\\\\\\\"AND\\\\\\\",\\\\\\\"triggerList\\\\\\\":[]},\\\\\\\"type\\\\\\\":\\\\\\\"TriggerConnector\\\\\\\"}\\\"]},\\\"type\\\":\\\"TriggerConnector\\\"}\",\"title\":\"Test\",\"systemAction\":false,\"actions\":[\"{\\\"type\\\":\\\"ActionLoopEnable\\\"}\"],\"enabled\":true}"
Assert.assertEquals(eventJsonExpected, event.toJSON())
// clone

View file

@ -2,8 +2,10 @@ package info.nightscout.androidaps.plugins.general.automation.triggers
import com.google.common.base.Optional
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator
import io.reactivex.Single
import org.json.JSONException
import org.json.JSONObject
import org.junit.Assert
@ -22,13 +24,18 @@ class TriggerBolusAgoTest : TriggerTestBase() {
@Test
fun shouldRunTest() {
`when`(repository.getLastBolusRecordOfType(Bolus.Type.NORMAL)).thenReturn(
Bolus(
timestamp = now,
amount = 0.0,
type = Bolus.Type.NORMAL
// Set last bolus time to now
`when`(repository.getLastBolusRecordOfTypeWrapped(Bolus.Type.NORMAL)).thenReturn(
Single.just(
ValueWrapper.Existing(
Bolus(
timestamp = now,
amount = 0.0,
type = Bolus.Type.NORMAL
)
)
)
) // Set last bolus time to now
)
`when`(dateUtil.now()).thenReturn(now + 10 * 60 * 1000) // set current time to now + 10 min
var t = TriggerBolusAgo(injector).setValue(110).comparator(Comparator.Compare.IS_EQUAL)
Assert.assertEquals(110, t.minutesAgo.value)
@ -51,13 +58,18 @@ class TriggerBolusAgoTest : TriggerTestBase() {
Assert.assertTrue(t.shouldRun())
t = TriggerBolusAgo(injector).setValue(390).comparator(Comparator.Compare.IS_EQUAL_OR_LESSER)
Assert.assertTrue(t.shouldRun())
`when`(repository.getLastBolusRecordOfType(Bolus.Type.NORMAL)).thenReturn(
Bolus(
timestamp = 0L,
amount = 0.0,
type = Bolus.Type.NORMAL
// Set last bolus time to 0
`when`(repository.getLastBolusRecordOfTypeWrapped(Bolus.Type.NORMAL)).thenReturn(
Single.just(
ValueWrapper.Existing(
Bolus(
timestamp = 0,
amount = 0.0,
type = Bolus.Type.NORMAL
)
)
)
) // Set last bolus time to 0
)
t = TriggerBolusAgo(injector).comparator(Comparator.Compare.IS_NOT_AVAILABLE)
Assert.assertTrue(t.shouldRun())
}

View file

@ -53,7 +53,7 @@ class TriggerProfilePercentTest : TriggerTestBase() {
Assert.assertEquals(bgJson, t.toJSON())
}
@Test @Throws(JSONException::class) fun fromJSONTest() {
@Test fun fromJSONTest() {
val t: TriggerProfilePercent = TriggerProfilePercent(injector).setValue(120.0).comparator(Comparator.Compare.IS_EQUAL)
val t2 = TriggerDummy(injector).instantiate(JSONObject(t.toJSON())) as TriggerProfilePercent
Assert.assertEquals(Comparator.Compare.IS_EQUAL, t2.comparator.value)

View file

@ -14,11 +14,11 @@ object OKDialog {
@SuppressLint("InflateParams")
fun show(context: Context, title: String, message: String, runnable: Runnable? = null) {
var okClicked = false
var notEmptytitle = title
if (notEmptytitle.isEmpty()) notEmptytitle = context.getString(R.string.message)
var notEmptyTitle = title
if (notEmptyTitle.isEmpty()) notEmptyTitle = context.getString(R.string.message)
AlertDialogHelper.Builder(context)
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, notEmptytitle))
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, notEmptyTitle))
.setMessage(message)
.setPositiveButton(context.getString(R.string.ok)) { dialog: DialogInterface, _: Int ->
if (okClicked) return@setPositiveButton
@ -36,11 +36,11 @@ object OKDialog {
@SuppressLint("InflateParams")
fun show(activity: FragmentActivity, title: String, message: Spanned, runnable: Runnable? = null) {
var okClicked = false
var notEmptytitle = title
if (notEmptytitle.isEmpty()) notEmptytitle = activity.getString(R.string.message)
var notEmptyTitle = title
if (notEmptyTitle.isEmpty()) notEmptyTitle = activity.getString(R.string.message)
AlertDialogHelper.Builder(activity)
.setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, notEmptytitle))
.setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, notEmptyTitle))
.setMessage(message)
.setPositiveButton(activity.getString(R.string.ok)) { dialog: DialogInterface, _: Int ->
if (okClicked) return@setPositiveButton

View file

@ -21,5 +21,6 @@ interface ResourceHelper {
fun decodeResource(id : Int) : Bitmap
fun getDisplayMetrics(): DisplayMetrics
fun dpToPx(dp: Int): Int
fun dpToPx(dp: Float): Int
fun shortTextMode(): Boolean
}

View file

@ -26,7 +26,7 @@ class ResourceHelperImplementation @Inject constructor(private val context: Cont
override fun gq(@PluralsRes id: Int, quantity: Int, vararg args: Any?): String =
context.resources.getQuantityString(id, quantity, *args)
override fun gsNotLocalised(@StringRes id: Int, vararg args: Any?) : String =
override fun gsNotLocalised(@StringRes id: Int, vararg args: Any?): String =
with(Configuration(context.resources.configuration)) {
setLocale(Locale.ENGLISH)
context.createConfigurationContext(this).getString(id, args)
@ -51,7 +51,7 @@ class ResourceHelperImplementation @Inject constructor(private val context: Cont
override fun decodeResource(id: Int): Bitmap =
BitmapFactory.decodeResource(context.resources, id)
override fun getDisplayMetrics():DisplayMetrics =
override fun getDisplayMetrics(): DisplayMetrics =
context.resources.displayMetrics
override fun dpToPx(dp: Int): Int {
@ -59,5 +59,10 @@ class ResourceHelperImplementation @Inject constructor(private val context: Cont
return (dp * scale + 0.5f).toInt()
}
override fun shortTextMode() : Boolean = !gb(R.bool.isTablet)
override fun dpToPx(dp: Float): Int {
val scale = context.resources.displayMetrics.density
return (dp * scale + 0.5f).toInt()
}
override fun shortTextMode(): Boolean = !gb(R.bool.isTablet)
}

View file

@ -0,0 +1,44 @@
package info.nightscout.androidaps.utils.ui
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.Gravity
import androidx.appcompat.widget.AppCompatTextView
class VerticalTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs) {
private var topDown = false
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec)
setMeasuredDimension(measuredHeight, measuredWidth)
}
override fun onDraw(canvas: Canvas) {
val textPaint = paint
textPaint.color = currentTextColor
textPaint.drawableState = drawableState
canvas.save()
if (topDown) {
canvas.translate(width.toFloat(), 0f)
canvas.rotate(90f)
} else {
canvas.translate(0f, height.toFloat())
canvas.rotate(-90f)
}
canvas.translate(compoundPaddingLeft.toFloat(), extendedPaddingTop.toFloat())
layout.draw(canvas)
canvas.restore()
}
init {
val gravity = gravity
topDown = if (Gravity.isVertical(gravity) && gravity and Gravity.VERTICAL_GRAVITY_MASK == Gravity.BOTTOM) {
setGravity(gravity and Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.TOP)
false
} else {
true
}
}
}

View file

@ -3,6 +3,7 @@
Please use AlertDialogHelper or wrap inflater context with ContextThemeWrapper(context, R.style.AppTheme)
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@ -19,7 +20,7 @@
android:id="@+id/alertdialog_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="?dialogTitleIconTint" />
app:tint="?dialogTitleIconTint" />
<TextView
android:id="@+id/alertdialog_title"

View file

@ -11,6 +11,9 @@
<color name="cardColorBackground">#121212</color>
<color name="black_overlay">#66000000</color>
<color name="defaultbackground">#424242</color>
<color name="defaulttextcolor">#B3FFFFFF</color>
<!-- Fragments-->
<color name="pumpStatusBackground">#505050</color>

View file

@ -436,8 +436,10 @@ import kotlin.math.roundToInt
.subscribeOn(Schedulers.io())
.toWrappedSingle()
fun getLastBolusRecordOfType(type: Bolus.Type): Bolus? =
fun getLastBolusRecordOfTypeWrapped(type: Bolus.Type): Single<ValueWrapper<Bolus>> =
database.bolusDao.getLastBolusRecordOfType(type)
.subscribeOn(Schedulers.io())
.toWrappedSingle()
fun getOldestBolusRecord(): Bolus? =
database.bolusDao.getOldestBolusRecord()

View file

@ -3,10 +3,8 @@ package info.nightscout.androidaps.database.daos
import androidx.room.Dao
import androidx.room.Query
import info.nightscout.androidaps.database.TABLE_BOLUSES
import info.nightscout.androidaps.database.TABLE_BOLUS_CALCULATOR_RESULTS
import info.nightscout.androidaps.database.embedments.InterfaceIDs
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.BolusCalculatorResult
import io.reactivex.Maybe
import io.reactivex.Single
@ -42,7 +40,7 @@ internal interface BolusDao : TraceableDao<Bolus> {
fun getLastBolusRecordMaybe(exclude: Bolus.Type = Bolus.Type.PRIMING): Maybe<Bolus>
@Query("SELECT * FROM $TABLE_BOLUSES WHERE isValid = 1 AND type == :only AND referenceId IS NULL ORDER BY timestamp DESC LIMIT 1")
fun getLastBolusRecordOfType(only: Bolus.Type): Bolus?
fun getLastBolusRecordOfType(only: Bolus.Type): Maybe<Bolus>
@Query("SELECT * FROM $TABLE_BOLUSES WHERE isValid = 1 AND type <> :exclude AND referenceId IS NULL ORDER BY timestamp ASC LIMIT 1")
fun getOldestBolusRecord(exclude: Bolus.Type = Bolus.Type.PRIMING): Bolus?