Merge pull request #3 from nightscout/dev

Dev
This commit is contained in:
Tim Street 2022-02-20 15:26:56 +00:00 committed by GitHub
commit 5627f38f97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
124 changed files with 4316 additions and 875 deletions

View file

@ -109,7 +109,7 @@ android {
defaultConfig {
multiDexEnabled true
versionCode 1500
version "3.0.0.1-dev"
version "3.0.0.1-dev-a"
buildConfigField "String", "VERSION", '"' + version + '"'
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"'

File diff suppressed because it is too large Load diff

View file

@ -239,6 +239,10 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() {
}
ToastUtils.showToastInUiThread(this, R.string.invalidinput)
}
binding.age.editText?.id?.let { binding.ageLabel.labelFor = it }
binding.tdd.editText?.id?.let { binding.tddLabel.labelFor = it }
binding.weight.editText?.id?.let { binding.weightLabel.labelFor = it }
binding.basalpctfromtdd.editText?.id?.let { binding.basalpctfromtddLabel.labelFor = it }
switchTab(0, typeSelected[0], false)
}

View file

@ -7,6 +7,7 @@ import info.nightscout.androidaps.plugins.aps.openAPSAMA.DetermineBasalAdapterAM
import info.nightscout.androidaps.plugins.aps.openAPSAMA.DetermineBasalResultAMA
import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalAdapterSMBJS
import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB
import info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF.DetermineBasalAdapterSMBDynamicISFJS
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobOref1Thread
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobThread
@ -19,6 +20,7 @@ abstract class APSModule {
@ContributesAndroidInjector abstract fun determineBasalResultAMAInjector(): DetermineBasalResultAMA
@ContributesAndroidInjector abstract fun determineBasalAdapterAMAJSInjector(): DetermineBasalAdapterAMAJS
@ContributesAndroidInjector abstract fun determineBasalAdapterSMBJSInjector(): DetermineBasalAdapterSMBJS
@ContributesAndroidInjector abstract fun determineBasalAdapterSMBAutoISFJSInjector(): DetermineBasalAdapterSMBDynamicISFJS
@ContributesAndroidInjector abstract fun iobCobThreadInjector(): IobCobThread
@ContributesAndroidInjector abstract fun iobCobOref1ThreadInjector(): IobCobOref1Thread
}

View file

@ -14,6 +14,7 @@ import info.nightscout.androidaps.plugin.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF.OpenAPSSMBDynamicISFPlugin
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.constraints.bgQualityCheck.BgQualityCheckPlugin
import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin
@ -212,6 +213,12 @@ abstract class PluginsModule {
@IntKey(220)
abstract fun bindOpenAPSSMBPlugin(plugin: OpenAPSSMBPlugin): PluginBase
@Binds
@APS
@IntoMap
@IntKey(222)
abstract fun bindOpenAPSSMBAutoISFPlugin(plugin: OpenAPSSMBDynamicISFPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap

View file

@ -64,6 +64,7 @@ class CalibrationDialog : DialogFragmentWithDate() {
binding.bg.setParams(savedInstanceState?.getDouble("bg")
?: bg, 36.0, 500.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok)
binding.units.text = if (units == GlucoseUnit.MMOL) rh.gs(R.string.mmol) else rh.gs(R.string.mgdl)
binding.bg.editText?.id?.let { binding.bgLabel.labelFor = it }
}
override fun onDestroyView() {

View file

@ -137,32 +137,39 @@ class CarbsDialog : DialogFragmentWithDate() {
savedInstanceState?.getDouble("carbs")
?: 0.0, 0.0, maxCarbs, 1.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher
)
binding.plus1.text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT))
val plus1text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT))
binding.plus1.text = plus1text
binding.plus1.contentDescription = rh.gs(R.string.treatments_wizard_carbs_label) + " " + plus1text
binding.plus1.setOnClickListener {
binding.carbs.value = max(
0.0, binding.carbs.value
+ sp.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT)
)
validateInputs()
binding.carbs.announceValue()
}
binding.plus2.text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT))
val plus2text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT))
binding.plus2.text = plus2text
binding.plus2.contentDescription = rh.gs(R.string.treatments_wizard_carbs_label) + " " + plus2text
binding.plus2.setOnClickListener {
binding.carbs.value = max(
0.0, binding.carbs.value
+ sp.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT)
)
validateInputs()
binding.carbs.announceValue()
}
binding.plus3.text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT))
val plus3text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT))
binding.plus3.text = plus3text
binding.plus2.contentDescription = rh.gs(R.string.treatments_wizard_carbs_label) + " " + plus3text
binding.plus3.setOnClickListener {
binding.carbs.value = max(
0.0, binding.carbs.value
+ sp.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT)
)
validateInputs()
binding.carbs.announceValue()
}
setOnValueChangedListener { eventTime: Long ->
@ -188,6 +195,9 @@ class CarbsDialog : DialogFragmentWithDate() {
binding.hypoTt.isChecked = false
binding.activityTt.isChecked = false
}
binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it }
binding.time.editText?.id?.let { binding.timeLabel.labelFor = it }
binding.carbs.editText?.id?.let { binding.carbsLabel.labelFor = it }
}
override fun onDestroyView() {

View file

@ -165,6 +165,8 @@ class CareDialog : DialogFragmentWithDate() {
?: 0.0, 0.0, Constants.MAX_PROFILE_SWITCH_DURATION, 10.0, DecimalFormat("0"), false, binding.okcancel.ok)
if (options == EventType.NOTE || options == EventType.QUESTION || options == EventType.ANNOUNCEMENT || options == EventType.EXERCISE)
binding.notesLayout.root.visibility = View.VISIBLE // independent to preferences
binding.bg.editText?.id?.let { binding.bgLabel.labelFor = it }
binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it }
}
override fun onDestroyView() {

View file

@ -70,6 +70,8 @@ class ExtendedBolusDialog : DialogFragmentWithDate() {
val extendedMaxDuration = pumpDescription.extendedBolusMaxDuration
binding.duration.setParams(savedInstanceState?.getDouble("duration")
?: extendedDurationStep, extendedDurationStep, extendedMaxDuration, extendedDurationStep, DecimalFormat("0"), false, binding.okcancel.ok)
binding.insulin.editText?.id?.let { binding.insulinLabel.labelFor = it }
binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it }
}
override fun onDestroyView() {

View file

@ -96,7 +96,7 @@ class FillDialog : DialogFragmentWithDate() {
} else {
binding.fillPresetButton3.visibility = View.GONE
}
binding.fillInsulinamount.editText?.id?.let { binding.fillLabel.labelFor = it }
}
override fun onDestroyView() {

View file

@ -116,29 +116,40 @@ class InsulinDialog : DialogFragmentWithDate() {
binding.amount.setParams(savedInstanceState?.getDouble("amount")
?: 0.0, 0.0, maxInsulin, activePlugin.activePump.pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher)
binding.plus05.text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT).toSignedString(activePlugin.activePump)
val plus05Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus05.text = plus05Text
binding.plus05.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus05Text
binding.plus05.setOnClickListener {
binding.amount.value = max(0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT))
validateInputs()
binding.amount.announceValue()
}
binding.plus10.text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT).toSignedString(activePlugin.activePump)
val plus10Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus10.text = plus10Text
binding.plus10.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus10Text
binding.plus10.setOnClickListener {
binding.amount.value = max(0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT))
validateInputs()
binding.amount.announceValue()
}
binding.plus20.text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT).toSignedString(activePlugin.activePump)
val plus20Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus20.text = plus20Text
binding.plus20.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus20Text
binding.plus20.setOnClickListener {
binding.amount.value = max(0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT))
validateInputs()
binding.amount.announceValue()
}
binding.timeLayout.visibility = View.GONE
binding.recordOnly.setOnCheckedChangeListener { _, isChecked: Boolean ->
binding.timeLayout.visibility = isChecked.toVisibility()
}
binding.amount.editText?.id?.let { binding.insulinLabel.labelFor = it }
binding.time.editText?.id?.let { binding.timeLabel.labelFor = it }
}
override fun onDestroyView() {

View file

@ -148,6 +148,9 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
}
}
binding.ttLayout.visibility = View.GONE
binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it }
binding.percentage.editText?.id?.let { binding.percentageLabel.labelFor = it }
binding.timeshift.editText?.id?.let { binding.timeshiftLabel.labelFor = it }
}
override fun onDestroyView() {

View file

@ -86,6 +86,9 @@ class TempBasalDialog : DialogFragmentWithDate() {
binding.percentLayout.visibility = View.GONE
binding.absoluteLayout.visibility = View.VISIBLE
}
binding.basalPercentInput.editText?.id?.let { binding.basalPercentLabel.labelFor = it }
binding.basalAbsoluteInput.editText?.id?.let { binding.basalAbsoluteLabel.labelFor = it }
binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it }
}
override fun onDestroyView() {

View file

@ -120,6 +120,8 @@ class TempTargetDialog : DialogFragmentWithDate() {
longClick(it)
return@setOnLongClickListener true
}
binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it }
binding.temptarget.editText?.id?.let { binding.temptargetLabel.labelFor = it }
}
}

View file

@ -106,6 +106,8 @@ class TreatmentDialog : DialogFragmentWithDate() {
binding.insulin.setParams(savedInstanceState?.getDouble("insulin")
?: 0.0, 0.0, maxInsulin, pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher)
binding.recordOnlyLayout.visibility = View.GONE
binding.insulin.editText?.id?.let { binding.insulinLabel.labelFor = it }
binding.carbs.editText?.id?.let { binding.carbsLabel.labelFor = it }
}
override fun onDestroyView() {

View file

@ -70,7 +70,7 @@ class WizardInfoDialog : DaggerDialogFragment() {
val units = profileFunction.getUnits()
val bgString = Profile.toUnitsString(data.glucoseValue, data.glucoseValue * Constants.MGDL_TO_MMOLL, units)
val isf = Profile.toUnits(data.isf, data.isf * Constants.MGDL_TO_MMOLL, units)
val trend = Profile.toUnitsString(data.glucoseTrend * 3, data.glucoseTrend * 3 * Constants.MGDL_TO_MMOLL, profileFunction.getUnits())
val trend = Profile.toUnitsString(data.glucoseTrend * 3, data.glucoseTrend * 3 * Constants.MGDL_TO_MMOLL, units)
// BG
binding.bg.text = rh.gs(R.string.format_bg_isf, bgString, isf)
binding.bgInsulin.text = rh.gs(R.string.formatinsulinunits, data.glucoseInsulin)

View file

@ -7,6 +7,7 @@ import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.Profile
@ -14,6 +15,7 @@ import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
import info.nightscout.androidaps.plugins.aps.loop.APSResult
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
@ -30,7 +32,7 @@ import java.nio.charset.StandardCharsets
import javax.inject.Inject
import kotlin.math.min
class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader, injector: HasAndroidInjector) {
class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader, injector: HasAndroidInjector) : DetermineBasalAdapterInterface {
private val injector: HasAndroidInjector
@ -48,21 +50,15 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
private var currentTemp = JSONObject()
private var autosensData = JSONObject()
var currentTempParam: String? = null
private set
var iobDataParam: String? = null
private set
var glucoseStatusParam: String? = null
private set
var profileParam: String? = null
private set
var mealDataParam: String? = null
private set
var scriptDebug = ""
private set
override var currentTempParam: String? = null
override var iobDataParam: String? = null
override var glucoseStatusParam: String? = null
override var profileParam: String? = null
override var mealDataParam: String? = null
override var scriptDebug = ""
@Suppress("SpellCheckingInspection")
operator fun invoke(): DetermineBasalResultAMA? {
override operator fun invoke(): APSResult? {
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
aapsLogger.debug(LTag.APS, "Glucose status: " + glucoseStatus.toString().also { glucoseStatusParam = it })
aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it })
@ -143,18 +139,25 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
}
@Suppress("SpellCheckingInspection")
@Throws(JSONException::class) fun setData(profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean) {
@Throws(JSONException::class)
override fun setData(
profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean,
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
) {
this.profile = JSONObject()
this.profile.put("max_iob", maxIob)
this.profile.put("dia", min(profile.dia, 3.0))

View file

@ -96,7 +96,7 @@ class OpenAPSAMAFragment : DaggerFragment() {
binding.result.text = jsonFormatter.format(lastAPSResult.json)
binding.request.text = lastAPSResult.toSpanned()
}
openAPSAMAPlugin.lastDetermineBasalAdapterAMAJS?.let { determineBasalAdapterAMAJS ->
openAPSAMAPlugin.lastDetermineBasalAdapter?.let { determineBasalAdapterAMAJS ->
binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterAMAJS.glucoseStatusParam)
binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterAMAJS.currentTempParam)
try {

View file

@ -60,8 +60,8 @@ class OpenAPSAMAPlugin @Inject constructor(
// last values
override var lastAPSRun: Long = 0
override var lastAPSResult: DetermineBasalResultAMA? = null
var lastDetermineBasalAdapterAMAJS: DetermineBasalAdapterAMAJS? = null
var lastAutosensResult: AutosensResult = AutosensResult()
override var lastDetermineBasalAdapter: DetermineBasalAdapterInterface? = null
override var lastAutosensResult: AutosensResult = AutosensResult()
override fun specialEnableCondition(): Boolean {
return try {
@ -158,7 +158,7 @@ class OpenAPSAMAPlugin @Inject constructor(
// Fix bug determine basal
if (determineBasalResultAMA == null) {
aapsLogger.error(LTag.APS, "SMB calculation returned null")
lastDetermineBasalAdapterAMAJS = null
lastDetermineBasalAdapter = null
lastAPSResult = null
lastAPSRun = 0
} else {
@ -167,8 +167,8 @@ class OpenAPSAMAPlugin @Inject constructor(
val now = System.currentTimeMillis()
determineBasalResultAMA.json?.put("timestamp", dateUtil.toISOString(now))
determineBasalResultAMA.inputConstraints = inputConstraints
lastDetermineBasalAdapterAMAJS = determineBasalAdapterAMAJS
lastAPSResult = determineBasalResultAMA
lastDetermineBasalAdapter = determineBasalAdapterAMAJS
lastAPSResult = determineBasalResultAMA as DetermineBasalResultAMA
lastAPSRun = now
}
rxBus.send(EventOpenAPSUpdateGui())

View file

@ -7,14 +7,11 @@ import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
import info.nightscout.androidaps.plugins.aps.loop.APSResult
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
@ -31,7 +28,7 @@ import java.lang.reflect.InvocationTargetException
import java.nio.charset.StandardCharsets
import javax.inject.Inject
class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) {
class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) : DetermineBasalAdapterInterface {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var constraintChecker: ConstraintChecker
@ -51,21 +48,16 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
private var smbAlwaysAllowed = false
private var currentTime: Long = 0
private var saveCgmSource = false
var currentTempParam: String? = null
private set
var iobDataParam: String? = null
private set
var glucoseStatusParam: String? = null
private set
var profileParam: String? = null
private set
var mealDataParam: String? = null
private set
var scriptDebug = ""
private set
override var currentTempParam: String? = null
override var iobDataParam: String? = null
override var glucoseStatusParam: String? = null
override var profileParam: String? = null
override var mealDataParam: String? = null
override var scriptDebug = ""
@Suppress("SpellCheckingInspection")
operator fun invoke(): DetermineBasalResultSMB? {
override operator fun invoke(): APSResult? {
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
aapsLogger.debug(LTag.APS, "Glucose status: " + mGlucoseStatus.toString().also { glucoseStatusParam = it })
aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it })
@ -155,22 +147,24 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
return determineBasalResultSMB
}
@Suppress("SpellCheckingInspection") fun setData(profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean,
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
@Suppress("SpellCheckingInspection")
override fun setData(
profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean,
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
) {
val pump = activePlugin.activePump
val pumpBolusStep = pump.pumpDescription.bolusStep

View file

@ -10,6 +10,7 @@ class DetermineBasalResultSMB private constructor(injector: HasAndroidInjector)
private var eventualBG = 0.0
private var snoozeBG = 0.0
var variableSens: Double? = null
internal constructor(injector: HasAndroidInjector, result: JSONObject) : this(injector) {
date = dateUtil.now()
@ -50,6 +51,7 @@ class DetermineBasalResultSMB private constructor(injector: HasAndroidInjector)
aapsLogger.error(LTag.APS, "Error parsing 'deliverAt' date: $date", e)
}
}
if (result.has("variable_sens")) variableSens = result.getDouble("variable_sens");
} catch (e: JSONException) {
aapsLogger.error(LTag.APS, "Error parsing determine-basal result JSON", e)
}

View file

@ -9,8 +9,7 @@ import android.view.ViewGroup
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.OpenapsamaFragmentBinding
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui
import info.nightscout.androidaps.plugins.bus.RxBus
@ -19,6 +18,8 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.JSONFormatter
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import org.json.JSONArray
@ -34,7 +35,7 @@ class OpenAPSSMBFragment : DaggerFragment() {
@Inject lateinit var rxBus: RxBus
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var openAPSSMBPlugin: OpenAPSSMBPlugin
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var jsonFormatter: JSONFormatter
@ -44,8 +45,7 @@ class OpenAPSSMBFragment : DaggerFragment() {
// 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 {
_binding = OpenapsamaFragmentBinding.inflate(inflater, container, false)
return binding.root
}
@ -54,7 +54,7 @@ class OpenAPSSMBFragment : DaggerFragment() {
super.onViewCreated(view, savedInstanceState)
binding.run.setOnClickListener {
openAPSSMBPlugin.invoke("OpenAPSSMB button", false)
activePlugin.activeAPS.invoke("OpenAPSSMB button", false)
}
}
@ -92,11 +92,12 @@ class OpenAPSSMBFragment : DaggerFragment() {
@Synchronized
fun updateGUI() {
if (_binding == null) return
val openAPSSMBPlugin = activePlugin.activeAPS
openAPSSMBPlugin.lastAPSResult?.let { lastAPSResult ->
binding.result.text = jsonFormatter.format(lastAPSResult.json)
binding.request.text = lastAPSResult.toSpanned()
}
openAPSSMBPlugin.lastDetermineBasalAdapterSMBJS?.let { determineBasalAdapterSMBJS ->
openAPSSMBPlugin.lastDetermineBasalAdapter?.let { determineBasalAdapterSMBJS ->
binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterSMBJS.glucoseStatusParam)
binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterSMBJS.currentTempParam)
try {

View file

@ -10,8 +10,6 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
@ -24,6 +22,8 @@ import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@ -37,7 +37,7 @@ class OpenAPSSMBPlugin @Inject constructor(
private val constraintChecker: ConstraintChecker,
rh: ResourceHelper,
private val profileFunction: ProfileFunction,
private val context: Context,
val context: Context,
private val activePlugin: ActivePlugin,
private val iobCobCalculator: IobCobCalculator,
private val hardLimits: HardLimits,
@ -46,23 +46,24 @@ class OpenAPSSMBPlugin @Inject constructor(
private val dateUtil: DateUtil,
private val repository: AppRepository,
private val glucoseStatusProvider: GlucoseStatusProvider
) : PluginBase(PluginDescription()
.mainType(PluginType.APS)
.fragmentClass(OpenAPSSMBFragment::class.java.name)
.pluginIcon(R.drawable.ic_generic_icon)
.pluginName(R.string.openapssmb)
.shortName(R.string.smb_shortname)
.preferencesId(R.xml.pref_openapssmb)
.description(R.string.description_smb)
.setDefault(),
) : PluginBase(
PluginDescription()
.mainType(PluginType.APS)
.fragmentClass(OpenAPSSMBFragment::class.java.name)
.pluginIcon(R.drawable.ic_generic_icon)
.pluginName(R.string.openapssmb)
.shortName(R.string.smb_shortname)
.preferencesId(R.xml.pref_openapssmb)
.description(R.string.description_smb)
.setDefault(),
aapsLogger, rh, injector
), APS, Constraints {
// last values
override var lastAPSRun: Long = 0
override var lastAPSResult: DetermineBasalResultSMB? = null
var lastDetermineBasalAdapterSMBJS: DetermineBasalAdapterSMBJS? = null
var lastAutosensResult = AutosensResult()
override var lastDetermineBasalAdapter: DetermineBasalAdapterInterface? = null
override var lastAutosensResult = AutosensResult()
override fun specialEnableCondition(): Boolean {
return try {
@ -120,15 +121,34 @@ class OpenAPSSMBPlugin @Inject constructor(
}.value()
var minBg = hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetLowMgdl(), 0.1), R.string.profile_low_target, HardLimits.VERY_HARD_LIMIT_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_MIN_BG[1])
var maxBg = hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetHighMgdl(), 0.1), R.string.profile_high_target, HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1])
var maxBg =
hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetHighMgdl(), 0.1), R.string.profile_high_target, HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1])
var targetBg = hardLimits.verifyHardLimits(profile.getTargetMgdl(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TARGET_BG[1])
var isTempTarget = false
val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
if (tempTarget is ValueWrapper.Existing) {
isTempTarget = true
minBg = hardLimits.verifyHardLimits(tempTarget.value.lowTarget, R.string.temp_target_low_target, HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble())
maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble())
targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble())
minBg =
hardLimits.verifyHardLimits(
tempTarget.value.lowTarget,
R.string.temp_target_low_target,
HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(),
HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble()
)
maxBg =
hardLimits.verifyHardLimits(
tempTarget.value.highTarget,
R.string.temp_target_high_target,
HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(),
HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble()
)
targetBg =
hardLimits.verifyHardLimits(
tempTarget.value.target(),
R.string.temp_target_value,
HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(),
HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble()
)
}
if (!hardLimits.checkHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return
if (!hardLimits.checkHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return
@ -165,8 +185,9 @@ class OpenAPSSMBPlugin @Inject constructor(
profiler.log(LTag.APS, "SMB data gathering", start)
start = System.currentTimeMillis()
DetermineBasalAdapterSMBJS(ScriptReader(context), injector).also { determineBasalAdapterSMBJS ->
determineBasalAdapterSMBJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg,
provideDetermineBasalAdapter().also { determineBasalAdapterSMBJS ->
determineBasalAdapterSMBJS.setData(
profile, maxIob, maxBasal, minBg, maxBg, targetBg,
activePlugin.activePump.baseBasalRate,
iobArray,
glucoseStatus,
@ -176,24 +197,26 @@ class OpenAPSSMBPlugin @Inject constructor(
smbAllowed.value(),
uam.value(),
advancedFiltering.value(),
activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin")
activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin"
)
val now = System.currentTimeMillis()
val determineBasalResultSMB = determineBasalAdapterSMBJS.invoke()
profiler.log(LTag.APS, "SMB calculation", start)
if (determineBasalResultSMB == null) {
aapsLogger.error(LTag.APS, "SMB calculation returned null")
lastDetermineBasalAdapterSMBJS = null
lastDetermineBasalAdapter = null
lastAPSResult = null
lastAPSRun = 0
} else {
// TODO still needed with oref1?
// Fix bug determine basal
if (determineBasalResultSMB.rate == 0.0 && determineBasalResultSMB.duration == 0 && iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) == null) determineBasalResultSMB.tempBasalRequested = false
if (determineBasalResultSMB.rate == 0.0 && determineBasalResultSMB.duration == 0 && iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) == null) determineBasalResultSMB.tempBasalRequested =
false
determineBasalResultSMB.iob = iobArray[0]
determineBasalResultSMB.json?.put("timestamp", dateUtil.toISOString(now))
determineBasalResultSMB.inputConstraints = inputConstraints
lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS
lastAPSResult = determineBasalResultSMB
lastDetermineBasalAdapter = determineBasalAdapterSMBJS
lastAPSResult = determineBasalResultSMB as DetermineBasalResultSMB
lastAPSRun = now
}
}
@ -204,4 +227,6 @@ class OpenAPSSMBPlugin @Inject constructor(
value.set(aapsLogger, false)
return value
}
fun provideDetermineBasalAdapter(): DetermineBasalAdapterInterface = DetermineBasalAdapterSMBJS(ScriptReader(context), injector)
}

View file

@ -0,0 +1,296 @@
package info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface
import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.stats.TddCalculator
import info.nightscout.shared.SafeParse
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.mozilla.javascript.*
import org.mozilla.javascript.Function
import java.io.IOException
import java.lang.reflect.InvocationTargetException
import java.nio.charset.StandardCharsets
import javax.inject.Inject
class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) : DetermineBasalAdapterInterface {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var constraintChecker: ConstraintChecker
@Inject lateinit var sp: SP
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var repository: AppRepository
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var tddCalculator: TddCalculator
private var profile = JSONObject()
private var mGlucoseStatus = JSONObject()
private var iobData: JSONArray? = null
private var mealData = JSONObject()
private var currentTemp = JSONObject()
private var autosensData = JSONObject()
private var microBolusAllowed = false
private var smbAlwaysAllowed = false
private var currentTime: Long = 0
private var saveCgmSource = false
override var currentTempParam: String? = null
override var iobDataParam: String? = null
override var glucoseStatusParam: String? = null
override var profileParam: String? = null
override var mealDataParam: String? = null
override var scriptDebug = ""
@Suppress("SpellCheckingInspection")
override operator fun invoke(): DetermineBasalResultSMB? {
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
aapsLogger.debug(LTag.APS, "Glucose status: " + mGlucoseStatus.toString().also { glucoseStatusParam = it })
aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it })
aapsLogger.debug(LTag.APS, "Current temp: " + currentTemp.toString().also { currentTempParam = it })
aapsLogger.debug(LTag.APS, "Profile: " + profile.toString().also { profileParam = it })
aapsLogger.debug(LTag.APS, "Meal data: " + mealData.toString().also { mealDataParam = it })
aapsLogger.debug(LTag.APS, "Autosens data: $autosensData")
aapsLogger.debug(LTag.APS, "Reservoir data: " + "undefined")
aapsLogger.debug(LTag.APS, "MicroBolusAllowed: $microBolusAllowed")
aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: $smbAlwaysAllowed")
aapsLogger.debug(LTag.APS, "CurrentTime: $currentTime")
aapsLogger.debug(LTag.APS, "isSaveCgmSource: $saveCgmSource")
var determineBasalResultSMB: DetermineBasalResultSMB? = null
val rhino = Context.enter()
val scope: Scriptable = rhino.initStandardObjects()
// Turn off optimization to make Rhino Android compatible
rhino.optimizationLevel = -1
try {
//register logger callback for console.log and console.error
ScriptableObject.defineClass(scope, LoggerCallback::class.java)
val myLogger = rhino.newObject(scope, "LoggerCallback", null)
scope.put("console2", scope, myLogger)
rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null)
//set module parent
rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null)
rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null)
rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null)
//generate functions "determine_basal" and "setTempBasal"
rhino.evaluateString(scope, readFile("OpenAPSSMBDynamicISF/determine-basal.js"), "JavaScript", 0, null)
rhino.evaluateString(scope, readFile("OpenAPSSMB/basal-set-temp.js"), "setTempBasal.js", 0, null)
val determineBasalObj = scope["determine_basal", scope]
val setTempBasalFunctionsObj = scope["tempBasalFunctions", scope]
//call determine-basal
if (determineBasalObj is Function && setTempBasalFunctionsObj is NativeObject) {
//prepare parameters
val params = arrayOf(
makeParam(mGlucoseStatus, rhino, scope),
makeParam(currentTemp, rhino, scope),
makeParamArray(iobData, rhino, scope),
makeParam(profile, rhino, scope),
makeParam(autosensData, rhino, scope),
makeParam(mealData, rhino, scope),
setTempBasalFunctionsObj,
java.lang.Boolean.valueOf(microBolusAllowed),
makeParam(null, rhino, scope), // reservoir data as undefined
java.lang.Long.valueOf(currentTime),
java.lang.Boolean.valueOf(saveCgmSource)
)
val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject
scriptDebug = LoggerCallback.scriptDebug
// Parse the jsResult object to a JSON-String
val result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString()
aapsLogger.debug(LTag.APS, "Result: $result")
try {
val resultJson = JSONObject(result)
determineBasalResultSMB = DetermineBasalResultSMB(injector, resultJson)
} catch (e: JSONException) {
aapsLogger.error(LTag.APS, "Unhandled exception", e)
}
} else {
aapsLogger.error(LTag.APS, "Problem loading JS Functions")
}
} catch (e: IOException) {
aapsLogger.error(LTag.APS, "IOException")
} catch (e: RhinoException) {
aapsLogger.error(LTag.APS, "RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString())
} catch (e: IllegalAccessException) {
aapsLogger.error(LTag.APS, e.toString())
} catch (e: InstantiationException) {
aapsLogger.error(LTag.APS, e.toString())
} catch (e: InvocationTargetException) {
aapsLogger.error(LTag.APS, e.toString())
} finally {
Context.exit()
}
glucoseStatusParam = mGlucoseStatus.toString()
iobDataParam = iobData.toString()
currentTempParam = currentTemp.toString()
profileParam = profile.toString()
mealDataParam = mealData.toString()
return determineBasalResultSMB
}
@Suppress("SpellCheckingInspection")
override fun setData(
profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean,
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
) {
val pump = activePlugin.activePump
val pumpBolusStep = pump.pumpDescription.bolusStep
this.profile.put("max_iob", maxIob)
//mProfile.put("dia", profile.getDia());
this.profile.put("type", "current")
this.profile.put("max_daily_basal", profile.getMaxDailyBasal())
this.profile.put("max_basal", maxBasal)
this.profile.put("min_bg", minBg)
this.profile.put("max_bg", maxBg)
this.profile.put("target_bg", targetBg)
this.profile.put("carb_ratio", profile.getIc())
this.profile.put("sens", profile.getIsfMgdl())
this.profile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3))
this.profile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0))
//mProfile.put("high_temptarget_raises_sensitivity", SP.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity));
this.profile.put("high_temptarget_raises_sensitivity", false)
//mProfile.put("low_temptarget_lowers_sensitivity", SP.getBoolean(R.string.key_low_temptarget_lowers_sensitivity, SMBDefaults.low_temptarget_lowers_sensitivity));
this.profile.put("low_temptarget_lowers_sensitivity", false)
this.profile.put("sensitivity_raises_target", sp.getBoolean(R.string.key_sensitivity_raises_target, SMBDefaults.sensitivity_raises_target))
this.profile.put("resistance_lowers_target", sp.getBoolean(R.string.key_resistance_lowers_target, SMBDefaults.resistance_lowers_target))
this.profile.put("adv_target_adjustments", SMBDefaults.adv_target_adjustments)
this.profile.put("exercise_mode", SMBDefaults.exercise_mode)
this.profile.put("half_basal_exercise_target", SMBDefaults.half_basal_exercise_target)
this.profile.put("maxCOB", SMBDefaults.maxCOB)
this.profile.put("skip_neutral_temps", pump.setNeutralTempAtFullHour())
// min_5m_carbimpact is not used within SMB determinebasal
//if (mealData.usedMinCarbsImpact > 0) {
// mProfile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact);
//} else {
// mProfile.put("min_5m_carbimpact", SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact));
//}
this.profile.put("remainingCarbsCap", SMBDefaults.remainingCarbsCap)
this.profile.put("enableUAM", uamAllowed)
this.profile.put("A52_risk_enable", SMBDefaults.A52_risk_enable)
val smbEnabled = sp.getBoolean(R.string.key_use_smb, false)
this.profile.put("SMBInterval", sp.getInt(R.string.key_smbinterval, SMBDefaults.SMBInterval))
this.profile.put("enableSMB_with_COB", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_COB, false))
this.profile.put("enableSMB_with_temptarget", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_temptarget, false))
this.profile.put("allowSMB_with_high_temptarget", smbEnabled && sp.getBoolean(R.string.key_allowSMB_with_high_temptarget, false))
this.profile.put("enableSMB_always", smbEnabled && sp.getBoolean(R.string.key_enableSMB_always, false) && advancedFiltering)
this.profile.put("enableSMB_after_carbs", smbEnabled && sp.getBoolean(R.string.key_enableSMB_after_carbs, false) && advancedFiltering)
this.profile.put("maxSMBBasalMinutes", sp.getInt(R.string.key_smbmaxminutes, SMBDefaults.maxSMBBasalMinutes))
this.profile.put("maxUAMSMBBasalMinutes", sp.getInt(R.string.key_uamsmbmaxminutes, SMBDefaults.maxUAMSMBBasalMinutes))
//set the min SMB amount to be the amount set by the pump.
this.profile.put("bolus_increment", pumpBolusStep)
this.profile.put("carbsReqThreshold", sp.getInt(R.string.key_carbsReqThreshold, SMBDefaults.carbsReqThreshold))
this.profile.put("current_basal", basalRate)
this.profile.put("temptargetSet", tempTargetSet)
this.profile.put("autosens_max", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_max, "1.2")))
if (profileFunction.getUnits() == GlucoseUnit.MMOL) {
this.profile.put("out_units", "mmol/L")
}
val now = System.currentTimeMillis()
val tb = iobCobCalculator.getTempBasalIncludingConvertedExtended(now)
currentTemp.put("temp", "absolute")
currentTemp.put("duration", tb?.plannedRemainingMinutes ?: 0)
currentTemp.put("rate", tb?.convertedToAbsolute(now, profile) ?: 0.0)
// as we have non default temps longer than 30 mintues
if (tb != null) currentTemp.put("minutesrunning", tb.getPassedDurationToTimeInMinutes(now))
iobData = iobCobCalculator.convertToJSONArray(iobArray)
mGlucoseStatus.put("glucose", glucoseStatus.glucose)
mGlucoseStatus.put("noise", glucoseStatus.noise)
if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
mGlucoseStatus.put("delta", glucoseStatus.shortAvgDelta)
} else {
mGlucoseStatus.put("delta", glucoseStatus.delta)
}
mGlucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta)
mGlucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta)
mGlucoseStatus.put("date", glucoseStatus.date)
this.mealData.put("carbs", mealData.carbs)
this.mealData.put("mealCOB", mealData.mealCOB)
this.mealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation)
this.mealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation)
this.mealData.put("lastBolusTime", mealData.lastBolusTime)
this.mealData.put("lastCarbTime", mealData.lastCarbTime)
this.mealData.put("TDDAIMI7", tddCalculator.averageTDD(tddCalculator.calculate(7)).totalAmount)
this.mealData.put("TDDPUMP", tddCalculator.calculateDaily().totalAmount)
if (constraintChecker.isAutosensModeEnabled().value()) {
autosensData.put("ratio", autosensDataRatio)
} else {
autosensData.put("ratio", 1.0)
}
this.microBolusAllowed = microBolusAllowed
smbAlwaysAllowed = advancedFiltering
currentTime = now
saveCgmSource = isSaveCgmSource
}
private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any {
return if (jsonObject == null) Undefined.instance
else NativeJSON.parse(rhino, scope, jsonObject.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array<Any?> -> objects[1] }
}
private fun makeParamArray(jsonArray: JSONArray?, rhino: Context, scope: Scriptable): Any {
return NativeJSON.parse(rhino, scope, jsonArray.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array<Any?> -> objects[1] }
}
@Throws(IOException::class) private fun readFile(filename: String): String {
val bytes = scriptReader.readFile(filename)
var string = String(bytes, StandardCharsets.UTF_8)
if (string.startsWith("#!/usr/bin/env node")) {
string = string.substring(20)
}
return string
}
init {
injector.androidInjector().inject(this)
}
}

View file

@ -0,0 +1,74 @@
package info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF
import android.content.Context
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@OpenForTesting
@Singleton
class OpenAPSSMBDynamicISFPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
rxBus: RxBus,
constraintChecker: ConstraintChecker,
rh: ResourceHelper,
profileFunction: ProfileFunction,
context: Context,
activePlugin: ActivePlugin,
iobCobCalculator: IobCobCalculator,
hardLimits: HardLimits,
profiler: Profiler,
sp: SP,
dateUtil: DateUtil,
repository: AppRepository,
glucoseStatusProvider: GlucoseStatusProvider,
private val buildHelper: BuildHelper
) : OpenAPSSMBPlugin(
injector,
aapsLogger,
rxBus,
constraintChecker,
rh,
profileFunction,
context,
activePlugin,
iobCobCalculator,
hardLimits,
profiler,
sp,
dateUtil,
repository,
glucoseStatusProvider
) {
init {
pluginDescription
.pluginName(R.string.openaps_smb_dynamic_isf)
.description(R.string.description_smb_dynamic_isf)
.setDefault(false)
}
override fun specialEnableCondition(): Boolean = buildHelper.isEngineeringMode() && buildHelper.isDev()
override fun provideDetermineBasalAdapter(): DetermineBasalAdapterInterface = DetermineBasalAdapterSMBDynamicISFJS(ScriptReader(context), injector)
}

View file

@ -48,6 +48,7 @@ import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification
import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.constraints.bgQualityCheck.BgQualityCheckPlugin
@ -603,7 +604,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
binding.infoLayout.apsMode.stateDescription = rh.gs(stringRes)
} else {
binding.infoLayout.apsMode.contentDescription = rh.gs(R.string.apsmode_title) + " " + rh.gs(stringRes)
binding.infoLayout.apsMode.contentDescription = rh.gs(R.string.apsmode_title) + " " + rh.gs(stringRes)
}
}
@ -671,6 +672,21 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.infoLayout.apsModeText.visibility = View.GONE
}
}
// Show variable sensitivity
val request = loop.lastRun?.request
if (request is DetermineBasalResultSMB) {
val isfMgdl = profileFunction.getProfile()?.getIsfMgdl()
val variableSens = request.variableSens
if (variableSens != isfMgdl && variableSens != null && isfMgdl != null) {
binding.infoLayout.variableSensitivity.text =
String.format(
Locale.getDefault(), "%1$.1f→%2$.1f",
Profile.toUnits(isfMgdl, isfMgdl * Constants.MGDL_TO_MMOLL, profileFunction.getUnits()),
Profile.toUnits(variableSens, variableSens * Constants.MGDL_TO_MMOLL, profileFunction.getUnits())
)
binding.infoLayout.variableSensitivity.visibility = View.VISIBLE
} else binding.infoLayout.variableSensitivity.visibility = View.GONE
} else binding.infoLayout.variableSensitivity.visibility = View.GONE
} else {
//nsclient
binding.infoLayout.apsMode.visibility = View.GONE
@ -774,7 +790,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
val outDate = (if (!overviewData.isActualBg) rh.gs(R.string.a11y_bg_outdated) else "")
binding.infoLayout.bg.contentDescription =
rh.gs(R.string.a11y_blood_glucose) + " " + binding.infoLayout.bg.text.toString() + " " + overviewData.lastBgDescription + " " + outDate
rh.gs(R.string.a11y_blood_glucose) + " " + binding.infoLayout.bg.text.toString() + " " + overviewData.lastBgDescription + " " + outDate
binding.infoLayout.timeAgo.text = dateUtil.minAgo(rh, overviewData.lastBg?.timestamp)
binding.infoLayout.timeAgo.contentDescription = dateUtil.minAgoLong(rh, overviewData.lastBg?.timestamp)

View file

@ -1,12 +1,18 @@
package info.nightscout.androidaps.plugins.general.overview.activities
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import info.nightscout.androidaps.R
@ -20,6 +26,8 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.androidaps.utils.wizard.QuickWizardEntry
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import javax.inject.Inject
@ -30,20 +38,95 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() {
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var quickWizard: QuickWizard
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var sp: SP
private var disposable: CompositeDisposable = CompositeDisposable()
private lateinit var binding: OverviewQuickwizardlistActivityBinding
private val itemTouchHelper by lazy {
val simpleItemTouchCallback = object : ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val adapter = recyclerView.adapter as RecyclerViewAdapter
val from = viewHolder.layoutPosition
val to = target.layoutPosition
adapter.moveItem(from, to)
adapter.notifyItemMoved(from, to)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
if (actionState == ACTION_STATE_DRAG) {
viewHolder?.itemView?.alpha = 0.5f
}
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = 1.0f
val adapter = recyclerView.adapter as RecyclerViewAdapter
adapter.onDrop()
}
}
ItemTouchHelper(simpleItemTouchCallback)
}
fun startDragging(viewHolder: RecyclerView.ViewHolder) {
itemTouchHelper.startDrag(viewHolder)
}
private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter<RecyclerViewAdapter.QuickWizardEntryViewHolder>() {
@SuppressLint("ClickableViewAccessibility")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuickWizardEntryViewHolder {
return QuickWizardEntryViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.overview_quickwizardlist_item, parent, false), fragmentManager)
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.overview_quickwizardlist_item, parent, false)
val viewHolder = QuickWizardEntryViewHolder(itemView, fragmentManager)
viewHolder.handleView.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
startDragging(viewHolder)
}
return@setOnTouchListener true
}
return viewHolder
}
override fun onBindViewHolder(holder: QuickWizardEntryViewHolder, position: Int) {
holder.from.text = dateUtil.timeString(quickWizard[position].validFromDate())
holder.to.text = dateUtil.timeString(quickWizard[position].validToDate())
val wearControl = sp.getBoolean(R.string.key_wear_control, false)
if (wearControl) {
holder.handleView.visibility = View.VISIBLE
} else {
holder.handleView.visibility = View.GONE
}
if (quickWizard[position].device() == QuickWizardEntry.DEVICE_ALL) {
holder.device.visibility = View.GONE
} else {
holder.device.visibility = View.VISIBLE
holder.device.setImageResource(
when (quickWizard[position].device()) {
QuickWizardEntry.DEVICE_WATCH -> R.drawable.ic_watch
else -> R.drawable.ic_smartphone
}
)
}
holder.buttonText.text = quickWizard[position].buttonText()
holder.carbs.text = rh.gs(R.string.format_carbs, quickWizard[position].carbs())
}
@ -55,6 +138,8 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() {
val buttonText: TextView = itemView.findViewById(R.id.overview_quickwizard_item_buttonText)
val carbs: TextView = itemView.findViewById(R.id.overview_quickwizard_item_carbs)
val from: TextView = itemView.findViewById(R.id.overview_quickwizard_item_from)
val handleView: ImageView = itemView.findViewById(R.id.handleView)
val device: ImageView = itemView.findViewById(R.id.overview_quickwizard_item_device)
val to: TextView = itemView.findViewById(R.id.overview_quickwizard_item_to)
private val editButton: Button = itemView.findViewById(R.id.overview_quickwizard_item_edit_button)
private val removeButton: Button = itemView.findViewById(R.id.overview_quickwizard_item_remove_button)
@ -74,6 +159,16 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() {
}
}
}
fun moveItem(from: Int, to: Int) {
Log.i("QuickWizard", "moveItem")
quickWizard.move(from, to)
}
fun onDrop() {
Log.i("QuickWizard", "onDrop")
rxBus.send(EventQuickWizardChange())
}
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -84,6 +179,7 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() {
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(this)
binding.recyclerview.adapter = RecyclerViewAdapter(supportFragmentManager)
itemTouchHelper.attachToRecyclerView(binding.recyclerview)
binding.addButton.setOnClickListener {
val manager = supportFragmentManager
@ -98,13 +194,13 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() {
.toObservable(EventQuickWizardChange::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
val adapter = RecyclerViewAdapter(supportFragmentManager)
binding.recyclerview.swapAdapter(adapter, false)
}, fabricPrivacy::logException)
val adapter = RecyclerViewAdapter(supportFragmentManager)
binding.recyclerview.swapAdapter(adapter, false)
}, fabricPrivacy::logException)
}
override fun onPause() {
disposable.clear()
super.onPause()
}
}
}

View file

@ -9,6 +9,7 @@ import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import dagger.android.support.DaggerDialogFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.OverviewEditquickwizardDialogBinding
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
@ -21,6 +22,7 @@ import info.nightscout.androidaps.utils.extensions.setEnableForChildren
import info.nightscout.androidaps.utils.extensions.setSelection
import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.androidaps.utils.wizard.QuickWizardEntry
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
import javax.inject.Inject
@ -30,9 +32,9 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var quickWizard: QuickWizard
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var sp: SP
var position = -1
var fromSeconds: Int = 0
var toSeconds: Int = 0
@ -42,8 +44,10 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
// 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 {
dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE)
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
isCancelable = true
@ -57,6 +61,14 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
position = bundle.getInt("position", -1)
}
val entry = if (position == -1) quickWizard.newEmptyItem() else quickWizard[position]
if (sp.getBoolean(R.string.key_wear_control, false)) {
binding.deviceLabel.visibility = View.VISIBLE
binding.device.visibility = View.VISIBLE
} else {
binding.deviceLabel.visibility = View.GONE
binding.device.visibility = View.GONE
}
binding.okcancel.ok.setOnClickListener {
try {
entry.storage.put("buttonText", binding.buttonEdit.text.toString())
@ -66,10 +78,14 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
entry.storage.put("useBG", binding.useBg.selectedItemPosition)
entry.storage.put("useCOB", binding.useCob.selectedItemPosition)
entry.storage.put("useBolusIOB", binding.useBolusIob.selectedItemPosition)
entry.storage.put("device", binding.device.selectedItemPosition)
entry.storage.put("useBasalIOB", binding.useBasalIob.selectedItemPosition)
entry.storage.put("useTrend", binding.useTrend.selectedItemPosition)
entry.storage.put("useSuperBolus", binding.useSuperBolus.selectedItemPosition)
entry.storage.put("useTempTarget", binding.useTempTarget.selectedItemPosition)
entry.storage.put("usePercentage", binding.usePercentage.selectedItemPosition)
val percentage = SafeParse.stringToInt(binding.percentage.text.toString())
entry.storage.put("percentage", percentage)
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
@ -88,7 +104,8 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
binding.from.setOnClickListener {
context?.let {
TimePickerDialog(it, fromTimeSetListener,
TimePickerDialog(
it, fromTimeSetListener,
T.secs(fromSeconds.toLong()).hours().toInt(),
T.secs((fromSeconds % 3600).toLong()).mins().toInt(),
DateFormat.is24HourFormat(context)
@ -105,13 +122,29 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
binding.to.setOnClickListener {
context?.let {
TimePickerDialog(it, toTimeSetListener,
TimePickerDialog(
it, toTimeSetListener,
T.secs(toSeconds.toLong()).hours().toInt(),
T.secs((toSeconds % 3600).toLong()).mins().toInt(),
DateFormat.is24HourFormat(context)
).show()
}
}
fun usePercentage(custom: Boolean) {
if (custom) {
binding.percentageLabel.visibility = View.VISIBLE
binding.percentage.visibility = View.VISIBLE
} else {
binding.percentageLabel.visibility = View.GONE
binding.percentage.visibility = View.GONE
}
}
binding.usePercentage.setOnCheckedChangeListener { _, checkedId ->
usePercentage(checkedId == R.id.use_percentage_custom)
}
toSeconds = entry.validTo()
binding.to.text = dateUtil.timeString(dateUtil.secondsOfTheDayToMilliseconds(toSeconds))
@ -122,10 +155,13 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
binding.useCob.setSelection(entry.useCOB())
binding.useBolusIob.setSelection(entry.useBolusIOB())
binding.useBasalIob.setSelection(entry.useBasalIOB())
binding.device.setSelection(entry.device())
binding.useTrend.setSelection(entry.useTrend())
binding.useSuperBolus.setSelection(entry.useSuperBolus())
binding.useTempTarget.setSelection(entry.useTempTarget())
binding.usePercentage.setSelection(entry.usePercentage())
usePercentage(entry.usePercentage() == QuickWizardEntry.CUSTOM)
binding.percentage.setText(entry.percentage().toString())
binding.useCobYes.setOnClickListener(this)
binding.useCobNo.setOnClickListener(this)
processCob()

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.general.wear
import android.app.NotificationManager
import android.content.Context
import android.util.Log
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
@ -39,11 +40,11 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.androidaps.utils.wizard.BolusWizard
import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.shared.SafeParse
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import java.text.DateFormat
import java.text.DecimalFormat
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
@ -71,6 +72,7 @@ class ActionStringHandler @Inject constructor(
private val activePlugin: ActivePlugin,
private val iobCobCalculator: IobCobCalculator,
private val localInsightPlugin: LocalInsightPlugin,
private val quickWizard: QuickWizard,
private val danaRPlugin: DanaRPlugin,
private val danaRKoreanPlugin: DanaRKoreanPlugin,
private val danaRv2Plugin: DanaRv2Plugin,
@ -79,7 +81,8 @@ class ActionStringHandler @Inject constructor(
private val dateUtil: DateUtil,
private val config: Config,
private val repository: AppRepository,
private val uel: UserEntryLogger
private val uel: UserEntryLogger,
private val defaultValueHelper: DefaultValueHelper
) {
private val timeout = 65 * 1000
@ -107,9 +110,11 @@ class ActionStringHandler @Inject constructor(
@Synchronized
private fun handleInitiate(actionString: String) {
//TODO: i18n
Log.i("ActionStringHandler", "handleInitiate actionString=" + actionString)
if (!sp.getBoolean(R.string.key_wear_control, false)) return
lastBolusWizard = null
var rTitle = "CONFIRM" //TODO: i18n
var rTitle = rh.gs(R.string.confirm).uppercase()
var rMessage = ""
var rAction = ""
// do the parsing and check constraints
@ -136,6 +141,11 @@ class ActionStringHandler @Inject constructor(
val carbs = SafeParse.stringToInt(act[2])
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(insulin)).value()
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value()
val pump = activePlugin.activePump
if (insulinAfterConstraints > 0 && (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected)) {
sendError(rh.gs(R.string.wizard_pump_not_available))
return
}
rMessage += rh.gs(R.string.bolus) + ": " + insulinAfterConstraints + "U\n"
rMessage += rh.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g"
if (insulinAfterConstraints - insulin != 0.0 || carbsAfterConstraints - carbs != 0) {
@ -143,32 +153,72 @@ class ActionStringHandler @Inject constructor(
}
rAction += "bolus $insulinAfterConstraints $carbsAfterConstraints"
} else if ("temptarget" == act[0]) { ///////////////////////////////////////////////////////// TEMPTARGET
val isMGDL = java.lang.Boolean.parseBoolean(act[1])
if (profileFunction.getUnits() == GlucoseUnit.MGDL != isMGDL) {
sendError("Different units used on watch and phone!")
return
}
val duration = SafeParse.stringToInt(act[2])
if (duration == 0) {
rMessage += "Zero-Temp-Target - cancelling running Temp-Targets?"
aapsLogger.info(LTag.WEAR, "temptarget received: $act")
if ("cancel" == act[1]) {
rMessage += rh.gs(R.string.wear_action_tempt_cancel_message)
rAction = "temptarget true 0 0 0"
} else if ("preset" == act[1]) {
val presetIsMGDL = profileFunction.getUnits() == GlucoseUnit.MGDL
val preset = act[2]
when (preset) {
"activity" -> {
val activityTTDuration = defaultValueHelper.determineActivityTTDuration()
val activityTT = defaultValueHelper.determineActivityTT()
val reason = rh.gs(R.string.activity)
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, activityTT, activityTTDuration)
rAction = "temptarget $presetIsMGDL $activityTTDuration $activityTT $activityTT"
}
"hypo" -> {
val hypoTTDuration = defaultValueHelper.determineHypoTTDuration()
val hypoTT = defaultValueHelper.determineHypoTT()
val reason = rh.gs(R.string.hypo)
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, hypoTT, hypoTTDuration)
rAction = "temptarget $presetIsMGDL $hypoTTDuration $hypoTT $hypoTT"
}
"eating" -> {
val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration()
val eatingSoonTT = defaultValueHelper.determineEatingSoonTT()
val reason = rh.gs(R.string.eatingsoon)
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, eatingSoonTT, eatingSoonTTDuration)
rAction = "temptarget $presetIsMGDL $eatingSoonTTDuration $eatingSoonTT $eatingSoonTT"
}
else -> {
sendError(rh.gs(R.string.wear_action_tempt_preset_error, preset))
return
}
}
} else {
var low = SafeParse.stringToDouble(act[3])
var high = SafeParse.stringToDouble(act[4])
if (!isMGDL) {
low *= Constants.MMOLL_TO_MGDL
high *= Constants.MMOLL_TO_MGDL
}
if (low < HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0] || low > HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]) {
sendError("Min-BG out of range!")
val isMGDL = java.lang.Boolean.parseBoolean(act[1])
if (profileFunction.getUnits() == GlucoseUnit.MGDL != isMGDL) {
sendError(rh.gs(R.string.wear_action_tempt_unit_error))
return
}
if (high < HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0] || high > HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]) {
sendError("Max-BG out of range!")
return
val duration = SafeParse.stringToInt(act[2])
if (duration == 0) {
rMessage += rh.gs(R.string.wear_action_tempt_zero_message)
rAction = "temptarget true 0 0 0"
} else {
var low = SafeParse.stringToDouble(act[3])
var high = SafeParse.stringToDouble(act[4])
if (!isMGDL) {
low *= Constants.MMOLL_TO_MGDL
high *= Constants.MMOLL_TO_MGDL
}
if (low < HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0] || low > HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]) {
sendError(rh.gs(R.string.wear_action_tempt_min_bg_error))
return
}
if (high < HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0] || high > HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]) {
sendError(rh.gs(R.string.wear_action_tempt_max_bg_error))
return
}
rMessage += if (act[3] === act[4]) rh.gs(R.string.wear_action_tempt_manual_message, act[3], act[2])
else rh.gs(R.string.wear_action_tempt_manual_range_message, act[3], act[4], act[2])
rAction = actionString
}
rMessage += "Temptarget:\nMin: " + act[3] + "\nMax: " + act[4] + "\nDuration: " + act[2]
rAction = actionString
}
} else if ("status" == act[0]) { ////////////////////////////////////////////// STATUS
rTitle = "STATUS"
@ -186,10 +236,15 @@ class ActionStringHandler @Inject constructor(
sendError("Update APP on Watch!")
return
} else if ("wizard2" == act[0]) { ////////////////////////////////////////////// WIZARD
val pump = activePlugin.activePump
if (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected) {
sendError(rh.gs(R.string.wizard_pump_not_available))
return
}
val carbsBeforeConstraints = SafeParse.stringToInt(act[1])
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbsBeforeConstraints)).value()
if (carbsAfterConstraints - carbsBeforeConstraints != 0) {
sendError("Carb constraint violation!")
sendError(rh.gs(R.string.wizard_carbs_constraint))
return
}
val useBG = sp.getBoolean(R.string.key_wearwizard_bg, true)
@ -202,52 +257,94 @@ class ActionStringHandler @Inject constructor(
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
if (profile == null) {
sendError("No profile found!")
sendError(rh.gs(R.string.wizard_no_active_profile))
return
}
val bgReading = iobCobCalculator.ads.actualBg()
if (bgReading == null) {
sendError("No recent BG to base calculation on!")
sendError(rh.gs(R.string.wizard_no_actual_bg))
return
}
val cobInfo = iobCobCalculator.getCobInfo(false, "Wizard wear")
if (cobInfo.displayCob == null) {
sendError("Unknown COB! BG reading missing or recent app restart?")
sendError(rh.gs(R.string.wizard_no_cob))
return
}
val format = DecimalFormat("0.00")
val formatInt = DecimalFormat("0")
val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
val tempTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null
val bolusWizard = BolusWizard(injector).doCalc(profile, profileName, tempTarget,
carbsAfterConstraints, if (cobInfo.displayCob != null) cobInfo.displayCob!! else 0.0, bgReading.valueToUnits(profileFunction.getUnits()),
0.0, percentage, useBG, useCOB, useBolusIOB, useBasalIOB, false, useTT, useTrend, false)
if (abs(bolusWizard.insulinAfterConstraints - bolusWizard.calculatedTotalInsulin) >= 0.01) {
sendError("Insulin constraint violation!" +
"\nCannot deliver " + format.format(bolusWizard.calculatedTotalInsulin) + "!")
val bolusWizard = BolusWizard(injector).doCalc(
profile, profileName, tempTarget,
carbsAfterConstraints, cobInfo.displayCob!!, bgReading.valueToUnits(profileFunction.getUnits()),
0.0, percentage, useBG, useCOB, useBolusIOB, useBasalIOB, false, useTT, useTrend, false
)
val insulinAfterConstraints = bolusWizard.insulinAfterConstraints
val minStep = pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)
if (abs(insulinAfterConstraints - bolusWizard.calculatedTotalInsulin) >= minStep) {
sendError(rh.gs(R.string.wizard_constraint_bolus_size, bolusWizard.calculatedTotalInsulin))
return
}
if (bolusWizard.calculatedTotalInsulin <= 0 && bolusWizard.carbs <= 0) {
rAction = "info"
rTitle = "INFO"
rTitle = rh.gs(R.string.info)
} else {
rAction = actionString
}
rMessage += "Carbs: " + bolusWizard.carbs + "g"
rMessage += "\nBolus: " + format.format(bolusWizard.calculatedTotalInsulin) + "U"
rMessage += rh.gs(R.string.wizard_result, bolusWizard.calculatedTotalInsulin, bolusWizard.carbs)
rMessage += "\n_____________"
rMessage += "\nCalc (IC:" + DecimalFormatter.to1Decimal(bolusWizard.ic) + ", " + "ISF:" + DecimalFormatter.to1Decimal(bolusWizard.sens) + "): "
rMessage += "\nFrom Carbs: " + format.format(bolusWizard.insulinFromCarbs) + "U"
if (useCOB) rMessage += "\nFrom" + formatInt.format(cobInfo.displayCob) + "g COB : " + format.format(bolusWizard.insulinFromCOB) + "U"
if (useBG) rMessage += "\nFrom BG: " + format.format(bolusWizard.insulinFromBG) + "U"
if (useBolusIOB) rMessage += "\nBolus IOB: " + format.format(bolusWizard.insulinFromBolusIOB) + "U"
if (useBasalIOB) rMessage += "\nBasal IOB: " + format.format(bolusWizard.insulinFromBasalIOB) + "U"
if (useTrend) rMessage += "\nFrom 15' trend: " + format.format(bolusWizard.insulinFromTrend) + "U"
if (percentage != 100) {
rMessage += "\nPercentage: " + format.format(bolusWizard.totalBeforePercentageAdjustment) + "U * " + percentage + "% -> ~" + format.format(bolusWizard.calculatedTotalInsulin) + "U"
}
rMessage += "\n" + bolusWizard.explainShort()
lastBolusWizard = bolusWizard
} else if ("quick_wizard" == act[0]) {
val guid = act[1]
val actualBg = iobCobCalculator.ads.actualBg()
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
val quickWizardEntry = quickWizard.get(guid)
Log.i("QuickWizard", "handleInitiate: quick_wizard " + quickWizardEntry?.buttonText() + " c " + quickWizardEntry?.carbs())
if (quickWizardEntry == null) {
sendError(rh.gs(R.string.quick_wizard_not_available))
return
}
if (actualBg == null) {
sendError(rh.gs(R.string.wizard_no_actual_bg))
return
}
if (profile == null) {
sendError(rh.gs(R.string.wizard_no_active_profile))
return
}
val cobInfo = iobCobCalculator.getCobInfo(false, "QuickWizard wear")
if (cobInfo.displayCob == null) {
sendError(rh.gs(R.string.wizard_no_cob))
return
}
val pump = activePlugin.activePump
if (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected) {
sendError(rh.gs(R.string.wizard_pump_not_available))
return
}
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
if (carbsAfterConstraints != quickWizardEntry.carbs()) {
sendError(rh.gs(R.string.wizard_carbs_constraint))
return
}
val insulinAfterConstraints = wizard.insulinAfterConstraints
val minStep = pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)
if (abs(insulinAfterConstraints - wizard.calculatedTotalInsulin) >= minStep) {
sendError(rh.gs(R.string.wizard_constraint_bolus_size, wizard.calculatedTotalInsulin))
return
}
rMessage = rh.gs(R.string.quick_wizard_message, quickWizardEntry.buttonText(), wizard.calculatedTotalInsulin, quickWizardEntry.carbs())
rAction = "bolus $insulinAfterConstraints $carbsAfterConstraints"
Log.i("QuickWizard", "handleInitiate: quick_wizard action=$rAction")
rMessage += "\n_____________"
rMessage += "\n" + wizard.explainShort()
} else if ("opencpp" == act[0]) {
val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
if (activeProfileSwitch is ValueWrapper.Existing) { // read CPP values
@ -331,7 +428,10 @@ class ActionStringHandler @Inject constructor(
rAction = "cancelChangeRequest"
wearPlugin.requestNotificationCancel(rAction)
return
} else return
} else {
sendError(rh.gs(R.string.wear_unknown_action_string) + act[0])
return
}
// send result
wearPlugin.requestActionConfirmation(rTitle, rMessage, rAction)
lastSentTimestamp = System.currentTimeMillis()
@ -560,39 +660,45 @@ class ActionStringHandler @Inject constructor(
}
//send profile to pump
uel.log(Action.PROFILE_SWITCH, Sources.Wear,
ValueWithUnit.Percent(percentage),
ValueWithUnit.Hour(timeshift).takeIf { timeshift != 0 })
ValueWithUnit.Percent(percentage),
ValueWithUnit.Hour(timeshift).takeIf { timeshift != 0 })
profileFunction.createProfileSwitch(0, percentage, timeshift)
}
private fun generateTempTarget(duration: Int, low: Double, high: Double) {
if (duration != 0) {
disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
reason = TemporaryTarget.Reason.WEAR,
lowTarget = Profile.toMgdl(low, profileFunction.getUnits()),
highTarget = Profile.toMgdl(high, profileFunction.getUnits())
)).subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted temp target $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
uel.log(Action.TT, Sources.Wear,
disposable += repository.runTransactionForResult(
InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
reason = TemporaryTarget.Reason.WEAR,
lowTarget = Profile.toMgdl(low, profileFunction.getUnits()),
highTarget = Profile.toMgdl(high, profileFunction.getUnits())
)
).subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted temp target $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
uel.log(
Action.TT, Sources.Wear,
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.WEAR),
ValueWithUnit.fromGlucoseUnit(low, profileFunction.getUnits().asText),
ValueWithUnit.fromGlucoseUnit(high, profileFunction.getUnits().asText).takeIf { low != high },
ValueWithUnit.Minute(duration))
ValueWithUnit.Minute(duration)
)
} else {
disposable += repository.runTransactionForResult(CancelCurrentTemporaryTargetIfAnyTransaction(System.currentTimeMillis()))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
uel.log(Action.CANCEL_TT, Sources.Wear,
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.WEAR))
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
uel.log(
Action.CANCEL_TT, Sources.Wear,
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.WEAR)
)
}
}
@ -601,13 +707,15 @@ class ActionStringHandler @Inject constructor(
detailedBolusInfo.insulin = amount
detailedBolusInfo.bolusType = DetailedBolusInfo.BolusType.PRIMING
uel.log(Action.PRIME_BOLUS, Sources.Wear,
ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 })
ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 })
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (!result.success) {
sendError(rh.gs(R.string.treatmentdeliveryerror) +
"\n" +
result.comment)
sendError(
rh.gs(R.string.treatmentdeliveryerror) +
"\n" +
result.comment
)
}
}
})
@ -615,9 +723,9 @@ class ActionStringHandler @Inject constructor(
private fun doECarbs(carbs: Int, time: Long, duration: Int) {
uel.log(if (duration == 0) Action.CARBS else Action.EXTENDED_CARBS, Sources.Wear,
ValueWithUnit.Timestamp(time),
ValueWithUnit.Gram(carbs),
ValueWithUnit.Hour(duration).takeIf { duration != 0 })
ValueWithUnit.Timestamp(time),
ValueWithUnit.Gram(carbs),
ValueWithUnit.Hour(duration).takeIf { duration != 0 })
doBolus(0.0, carbs, time, duration)
}
@ -636,15 +744,17 @@ class ActionStringHandler @Inject constructor(
else -> Action.TREATMENT
}
uel.log(action, Sources.Wear,
ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 },
ValueWithUnit.Gram(carbs).takeIf { carbs != 0 },
ValueWithUnit.Hour(carbsDuration).takeIf { carbsDuration != 0 })
ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 },
ValueWithUnit.Gram(carbs).takeIf { carbs != 0 },
ValueWithUnit.Hour(carbsDuration).takeIf { carbsDuration != 0 })
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (!result.success) {
sendError(rh.gs(R.string.treatmentdeliveryerror) +
"\n" +
result.comment)
sendError(
rh.gs(R.string.treatmentdeliveryerror) +
"\n" +
result.comment
)
}
}
})
@ -665,4 +775,4 @@ class ActionStringHandler @Inject constructor(
lastConfirmActionString = null
lastBolusWizard = null
}
}
}

View file

@ -46,6 +46,7 @@ import info.nightscout.androidaps.interfaces.Loop;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.Profile;
import info.nightscout.androidaps.interfaces.ProfileFunction;
import info.nightscout.androidaps.utils.wizard.QuickWizardEntry;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
@ -62,6 +63,7 @@ import info.nightscout.androidaps.utils.DecimalFormatter;
import info.nightscout.androidaps.utils.DefaultValueHelper;
import info.nightscout.androidaps.utils.TrendCalculator;
import info.nightscout.androidaps.utils.resources.ResourceHelper;
import info.nightscout.androidaps.utils.wizard.QuickWizard;
import info.nightscout.shared.sharedPreferences.SP;
public class WatchUpdaterService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
@ -81,6 +83,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
@Inject ReceiverStatusStore receiverStatusStore;
@Inject Config config;
@Inject public TrendCalculator trendCalculator;
@Inject public QuickWizard quickWizard;
public static final String ACTION_RESEND = WatchUpdaterService.class.getName().concat(".Resend");
public static final String ACTION_OPEN_SETTINGS = WatchUpdaterService.class.getName().concat(".OpenSettings");
@ -101,12 +104,14 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
private static final String OPEN_SETTINGS_PATH = "/openwearsettings";
private static final String NEW_STATUS_PATH = "/sendstatustowear";
private static final String NEW_PREFERENCES_PATH = "/sendpreferencestowear";
private static final String QUICK_WIZARD_PATH = "/send_quick_wizard";
public static final String BASAL_DATA_PATH = "/nightscout_watch_basal";
public static final String BOLUS_PROGRESS_PATH = "/nightscout_watch_bolusprogress";
public static final String ACTION_CONFIRMATION_REQUEST_PATH = "/nightscout_watch_actionconfirmationrequest";
public static final String ACTION_CHANGECONFIRMATION_REQUEST_PATH = "/nightscout_watch_changeconfirmationrequest";
public static final String ACTION_CANCELNOTIFICATION_REQUEST_PATH = "/nightscout_watch_cancelnotificationrequest";
String TAG = "WatchUpdateService";
private static boolean lastLoopStatus;
@ -156,7 +161,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent != null ? intent.getAction() : null;
// Log.d(TAG, logPrefix + "onStartCommand: " + action);
// Log.d(TAG, "onStartCommand: " + action);
if (wearIntegration()) {
handler.post(() -> {
@ -235,7 +240,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
super.onPeerConnected(peer);
String id = peer.getId();
String name = peer.getDisplayName();
// Log.d(TAG, logPrefix + "onPeerConnected peer name & ID: " + name + "|" + id);
Log.d(TAG, "onPeerConnected peer name & ID: " + name + "|" + id);
}
@ -244,14 +249,14 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
super.onPeerDisconnected(peer);
String id = peer.getId();
String name = peer.getDisplayName();
// Log.d(TAG, logPrefix + "onPeerDisconnected peer name & ID: " + name + "|" + id);
Log.d(TAG, "onPeerDisconnected peer name & ID: " + name + "|" + id);
}
@Override
public void onMessageReceived(MessageEvent event) {
// Log.d(TAG, logPrefix + "onMessageRecieved: " + event);
// Log.d(TAG, "onMessageRecieved: " + event);
if (wearIntegration()) {
if (event != null && event.getPath().equals(WEARABLE_RESEND_PATH)) {
@ -283,7 +288,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
private void sendData() {
GlucoseValue lastBG = iobCobCalculator.getAds().lastBg();
// Log.d(TAG, logPrefix + "LastBg=" + lastBG);
// Log.d(TAG, "LastBg=" + lastBG);
if (lastBG != null) {
GlucoseStatus glucoseStatus = glucoseStatusProvider.getGlucoseStatusData();
@ -364,6 +369,10 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
googleApiConnect();
}
sendPreferences();
sendQuickWizard();
long startTime = System.currentTimeMillis() - (long) (60000 * 60 * 5.5);
GlucoseValue last_bg = iobCobCalculator.getAds().lastBg();
@ -382,7 +391,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
entries.putDataMapArrayList("entries", dataMaps);
(new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, entries);
}
sendPreferences();
sendBasals();
sendStatus();
}
@ -720,19 +728,62 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
private void sendPreferences() {
if (googleApiClient != null && googleApiClient.isConnected()) {
GlucoseUnit units = profileFunction.getUnits();
boolean wearcontrol = sp.getBoolean(R.string.key_wear_control, false);
boolean mgdl = units.equals(GlucoseUnit.MGDL);
int percentage = sp.getInt(R.string.key_boluswizard_percentage, 100);
int maxCarbs = sp.getInt(R.string.key_treatmentssafety_maxcarbs, 48);
double maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0);
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(NEW_PREFERENCES_PATH);
//unique content
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putBoolean(rh.gs(R.string.key_wear_control), wearcontrol);
dataMapRequest.getDataMap().putBoolean(rh.gs(R.string.key_units_mgdl), mgdl);
dataMapRequest.getDataMap().putInt(rh.gs(R.string.key_boluswizard_percentage), percentage);
dataMapRequest.getDataMap().putInt(rh.gs(R.string.key_treatmentssafety_maxcarbs), maxCarbs);
dataMapRequest.getDataMap().putDouble(rh.gs(R.string.key_treatmentssafety_maxbolus),maxBolus);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("SendStatus", "No connection to wearable available!");
Log.e("SendPreferences", "No connection to wearable available!");
}
}
private void sendQuickWizard() {
if (googleApiClient != null && googleApiClient.isConnected()) {
int size = quickWizard.size();
ArrayList<DataMap> entities = new ArrayList<>();
for(int i=0; i < size; i++) {
QuickWizardEntry q = quickWizard.get(i);
if (q.forDevice(QuickWizardEntry.DEVICE_WATCH)) {
entities.add(quickMap(q));
}
}
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(QUICK_WIZARD_PATH);
DataMap dm = dataMapRequest.getDataMap();
dm.putLong("timestamp", System.currentTimeMillis());
dm.putDataMapArrayList("quick_wizard", entities);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Log.i(TAG, "sendQuickWizard: " + putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("sendQuickWizard", "No connection to wearable available!");
}
}
private DataMap quickMap(QuickWizardEntry q) {
DataMap dm = new DataMap();
dm.putString("guid", q.guid());
dm.putString("button_text", q.buttonText());
dm.putInt("carbs", q.carbs());
dm.putInt("from", q.validFrom());
dm.putInt("to", q.validTo());
return dm;
}
@NonNull
private String generateStatusString(Profile profile, String currentBasal, String iobSum, String iobDetail, String bgiString) {

View file

@ -121,6 +121,7 @@ class LocalProfileFragment : DaggerFragment() {
processVisibilityOnClick(it)
binding.target.visibility = View.VISIBLE
}
binding.dia.editText?.id?.let { binding.diaLabel.labelFor = it }
}
fun build() {

View file

@ -58,13 +58,13 @@ class TddCalculator @Inject constructor(
val tbr = tempBasals[t]
val profile = profileFunction.getProfile(t) ?: continue
val absoluteRate = tbr?.convertedToAbsolute(t, profile) ?: profile.getBasal(t)
tdd.basalAmount += absoluteRate / 60.0 * 5.0
tdd.basalAmount += absoluteRate / T.mins(60).msecs().toDouble() * calculationStep.toDouble()
if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) {
// they are not included in TBRs
val eb = iobCobCalculator.getExtendedBolus(t)
val absoluteEbRate = eb?.rate ?: 0.0
tdd.bolusAmount += absoluteEbRate / 60.0 * 5.0
tdd.bolusAmount += absoluteEbRate / T.mins(60).msecs().toDouble() * calculationStep.toDouble()
}
result.put(midnight, tdd)
}
@ -76,7 +76,54 @@ class TddCalculator @Inject constructor(
return result
}
private fun averageTDD(tdds: LongSparseArray<TotalDailyDose>): TotalDailyDose {
fun calculateDaily():TotalDailyDose {
val startTime = MidnightTime.calc(dateUtil.now() )
val endTime = dateUtil.now()
val tdd = TotalDailyDose(timestamp = startTime)
//val result = TotalDailyDose()
repository.getBolusesDataFromTimeToTime(startTime, endTime, true).blockingGet()
.filter { it.type != Bolus.Type.PRIMING }
.forEach { t ->
//val midnight = MidnightTime.calc(t.timestamp)
//val tdd = result[midnight] ?: TotalDailyDose(timestamp = midnight)
tdd.bolusAmount += t.amount
//result.put(midnight, tdd)
}
repository.getCarbsDataFromTimeToTimeExpanded(startTime, endTime, true).blockingGet().forEach { t ->
//val midnight = MidnightTime.calc(t.timestamp)
//val tdd = result[midnight] ?: TotalDailyDose(timestamp = midnight)
tdd.carbs += t.amount
//result.put(midnight, tdd)
}
val calculationStep = T.mins(5).msecs()
for (t in startTime until endTime step calculationStep) {
//val midnight = MidnightTime.calc(t)
//val tdd = result[midnight] ?: TotalDailyDose(timestamp = midnight)
val tbr = iobCobCalculator.getTempBasalIncludingConvertedExtended(t)
val profile = profileFunction.getProfile(t) ?: continue
val absoluteRate = tbr?.convertedToAbsolute(t, profile) ?: profile.getBasal(t)
tdd.basalAmount += absoluteRate / T.mins(5).msecs().toDouble() * calculationStep.toDouble()
if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) {
// they are not included in TBRs
val eb = iobCobCalculator.getExtendedBolus(t)
val absoluteEbRate = eb?.rate ?: 0.0
tdd.bolusAmount += absoluteEbRate / T.mins(5).msecs().toDouble() * calculationStep.toDouble()
}
//result.put(midnight, tdd)
}
//for (i in 0 until tdd.size()) {
//val tdd = result.valueAt(i)
tdd.totalAmount = tdd.bolusAmount + tdd.basalAmount
//}
aapsLogger.debug(LTag.CORE, tdd.toString())
return tdd
}
fun averageTDD(tdds: LongSparseArray<TotalDailyDose>): TotalDailyDose {
val totalTdd = TotalDailyDose(timestamp = dateUtil.now())
for (i in 0 until tdds.size()) {
val tdd = tdds.valueAt(i)

View file

@ -112,7 +112,7 @@ public class TimeListEdit {
float factor = layout.getContext().getResources().getDisplayMetrics().density;
finalAdd = new ImageView(context);
finalAdd.setImageResource(R.drawable.ic_add);
finalAdd.setContentDescription(layout.getContext().getResources().getString(R.string.addnew));
finalAdd.setContentDescription(layout.getContext().getResources().getString(R.string.a11y_add_new_to_list));
LinearLayout.LayoutParams illp = new LinearLayout.LayoutParams((int) (35d * factor), (int) (35 * factor));
illp.setMargins(0, 25, 0, 25); // llp.setMargins(left, top, right, bottom);
illp.gravity = Gravity.CENTER;

View file

@ -18,6 +18,8 @@ import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InsertOrUpdateBolusCalculatorResultTransaction
import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.androidaps.extensions.highValueToUnitsToString
import info.nightscout.androidaps.extensions.lowValueToUnitsToString
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
@ -135,27 +137,28 @@ class BolusWizard @Inject constructor(
private var quickWizard: Boolean = true
var usePercentage: Boolean = false
fun doCalc(profile: Profile,
profileName: String,
tempTarget: TemporaryTarget?,
carbs: Int,
cob: Double,
bg: Double,
correction: Double,
percentageCorrection: Int = 100,
useBg: Boolean,
useCob: Boolean,
includeBolusIOB: Boolean,
includeBasalIOB: Boolean,
useSuperBolus: Boolean,
useTT: Boolean,
useTrend: Boolean,
useAlarm: Boolean,
notes: String = "",
carbTime: Int = 0,
usePercentage: Boolean = false,
totalPercentage: Double = 100.0,
quickWizard: Boolean = false
fun doCalc(
profile: Profile,
profileName: String,
tempTarget: TemporaryTarget?,
carbs: Int,
cob: Double,
bg: Double,
correction: Double,
percentageCorrection: Int = 100,
useBg: Boolean,
useCob: Boolean,
includeBolusIOB: Boolean,
includeBasalIOB: Boolean,
useSuperBolus: Boolean,
useTT: Boolean,
useTrend: Boolean,
useAlarm: Boolean,
notes: String = "",
carbTime: Int = 0,
usePercentage: Boolean = false,
totalPercentage: Double = 100.0,
quickWizard: Boolean = false
): BolusWizard {
this.profile = profile
@ -314,7 +317,9 @@ class BolusWizard @Inject constructor(
actions.add(rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbs).formatColor(rh, R.color.carbs) + timeShift)
}
if (insulinFromCOB > 0) {
actions.add(rh.gs(R.string.cobvsiob) + ": " + rh.gs(R.string.formatsignedinsulinunits, insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCOB + insulinFromBG).formatColor(rh, R.color.cobAlert))
actions.add(
rh.gs(R.string.cobvsiob) + ": " + rh.gs(R.string.formatsignedinsulinunits, insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCOB + insulinFromBG).formatColor(rh, R.color.cobAlert)
)
val absorptionRate = iobCobCalculator.ads.slowAbsorptionPercentage(60)
if (absorptionRate > .25)
actions.add(rh.gs(R.string.slowabsorptiondetected, rh.gc(R.color.cobAlert), (absorptionRate * 100).toInt()))
@ -344,8 +349,8 @@ class BolusWizard @Inject constructor(
carbTimer.removeEatReminder()
if (sp.getBoolean(R.string.key_usebolusadvisor, false) && Profile.toMgdl(bg, profile.units) > 180 && carbs > 0 && carbTime >= 0)
OKDialog.showYesNoCancel(ctx, rh.gs(R.string.bolusadvisor), rh.gs(R.string.bolusadvisormessage),
{ bolusAdvisorProcessing(ctx) },
{ commonProcessing(ctx) }
{ bolusAdvisorProcessing(ctx) },
{ commonProcessing(ctx) }
)
else
commonProcessing(ctx)
@ -367,10 +372,13 @@ class BolusWizard @Inject constructor(
carbTime = 0
bolusCalculatorResult = createBolusCalculatorResult()
notes = this@BolusWizard.notes
uel.log(Action.BOLUS_ADVISOR, if (quickWizard) Sources.QuickWizard else Sources.WizardDialog,
uel.log(
Action.BOLUS_ADVISOR,
if (quickWizard) Sources.QuickWizard else Sources.WizardDialog,
notes,
ValueWithUnit.TherapyEventType(eventType.toDBbEventType()),
ValueWithUnit.Insulin(insulinAfterConstraints))
ValueWithUnit.Insulin(insulinAfterConstraints)
)
if (insulin > 0) {
commandQueue.bolus(this, object : Callback() {
override fun run() {
@ -385,6 +393,26 @@ class BolusWizard @Inject constructor(
})
}
fun explainShort(): String {
var message = rh.gs(R.string.wizard_explain_calc, ic, sens)
message += "\n" + rh.gs(R.string.wizard_explain_carbs, insulinFromCarbs)
if (useTT && tempTarget != null) {
val tt = if (tempTarget?.lowTarget == tempTarget?.highTarget) tempTarget?.lowValueToUnitsToString(profile.units)
else rh.gs(R.string.wizard_explain_tt_to, tempTarget?.lowValueToUnitsToString(profile.units), tempTarget?.highValueToUnitsToString(profile.units))
message += "\n" + rh.gs(R.string.wizard_explain_tt, tt)
}
if (useCob) message += "\n" + rh.gs(R.string.wizard_explain_cob, cob, insulinFromCOB)
if (useBg) message += "\n" + rh.gs(R.string.wizard_explain_bg, insulinFromBG)
if (includeBolusIOB) message += "\n" + rh.gs(R.string.wizard_explain_bolus_iob, insulinFromBolusIOB)
if (includeBasalIOB) message += "\n" + rh.gs(R.string.wizard_explain_basal_iob, insulinFromBasalIOB)
if (useTrend) message += "\n" + rh.gs(R.string.wizard_explain_trend, insulinFromTrend)
if (useSuperBolus) message += "\n" + rh.gs(R.string.wizard_explain_superbolus, insulinFromSuperBolus)
if (percentageCorrection != 100) {
message += "\n" + rh.gs(R.string.wizard_explain_percent, totalBeforePercentageAdjustment, percentageCorrection, calculatedTotalInsulin)
}
return message
}
private fun commonProcessing(ctx: Context) {
val profile = profileFunction.getProfile() ?: return
val pump = activePlugin.activePump
@ -433,17 +461,17 @@ class BolusWizard @Inject constructor(
bolusCalculatorResult = createBolusCalculatorResult()
notes = this@BolusWizard.notes
if (insulin > 0 || carbs > 0) {
val action = when {
val action = when {
insulinAfterConstraints.equals(0.0) -> Action.CARBS
carbs.equals(0.0) -> Action.BOLUS
else -> Action.TREATMENT
}
uel.log(action, if (quickWizard) Sources.QuickWizard else Sources.WizardDialog,
notes,
ValueWithUnit.TherapyEventType(eventType.toDBbEventType()),
ValueWithUnit.Insulin(insulinAfterConstraints).takeIf { insulinAfterConstraints != 0.0 },
ValueWithUnit.Gram(this@BolusWizard.carbs).takeIf { this@BolusWizard.carbs != 0 },
ValueWithUnit.Minute(carbTime).takeIf { carbTime != 0 })
notes,
ValueWithUnit.TherapyEventType(eventType.toDBbEventType()),
ValueWithUnit.Insulin(insulinAfterConstraints).takeIf { insulinAfterConstraints != 0.0 },
ValueWithUnit.Gram(this@BolusWizard.carbs).takeIf { this@BolusWizard.carbs != 0 },
ValueWithUnit.Minute(carbTime).takeIf { carbTime != 0 })
commandQueue.bolus(this, object : Callback() {
override fun run() {
if (!result.success) {
@ -469,9 +497,9 @@ class BolusWizard @Inject constructor(
private fun calcPercentageWithConstraints() {
calculatedPercentage = 100.0
if (totalBeforePercentageAdjustment != insulinFromCorrection)
calculatedPercentage = calculatedTotalInsulin/(totalBeforePercentageAdjustment-insulinFromCorrection) * 100
calculatedPercentage = calculatedTotalInsulin / (totalBeforePercentageAdjustment - insulinFromCorrection) * 100
calculatedPercentage = max(calculatedPercentage, 10.0)
calculatedPercentage = min(calculatedPercentage,250.0)
calculatedPercentage = min(calculatedPercentage, 250.0)
}
private fun calcCorrectionWithConstraints() {
@ -481,4 +509,4 @@ class BolusWizard @Inject constructor(
calculatedCorrection = max(-constraintChecker.getMaxBolusAllowed().value(), calculatedCorrection)
}
}
}

View file

@ -1,10 +1,12 @@
package info.nightscout.androidaps.utils.wizard
import android.util.Log
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@ -18,6 +20,18 @@ class QuickWizard @Inject constructor(
init {
setData(JSONArray(sp.getString(R.string.key_quickwizard, "[]")))
setGuidsForOldEntries()
}
private fun setGuidsForOldEntries() {
// for migration purposes; guid is a new required property
for (i in 0 until storage.length()) {
val entry = QuickWizardEntry(injector).from(storage.get(i) as JSONObject, i)
if (entry.guid() == "") {
val guid = UUID.randomUUID().toString()
entry.storage.put("guid", guid)
}
}
}
fun getActive(): QuickWizardEntry? {
@ -41,6 +55,38 @@ class QuickWizard @Inject constructor(
operator fun get(position: Int): QuickWizardEntry =
QuickWizardEntry(injector).from(storage.get(position) as JSONObject, position)
fun get(guid: String): QuickWizardEntry? {
for (i in 0 until storage.length()) {
val entry = QuickWizardEntry(injector).from(storage.get(i) as JSONObject, i)
if (entry.guid() == guid) {
return entry
}
}
return null
}
fun move(from: Int, to: Int) {
Log.i("QuickWizard", "moveItem: $from $to")
val fromEntry = storage[from] as JSONObject
storage.remove(from)
addToPos(to, fromEntry, storage)
save()
}
fun removePos(pos: Int, jsonObj: JSONObject?, jsonArr: JSONArray) {
for (i in jsonArr.length() downTo pos + 1) {
jsonArr.put(i, jsonArr[i - 1])
}
jsonArr.put(pos, jsonObj)
}
private fun addToPos(pos: Int, jsonObj: JSONObject?, jsonArr: JSONArray) {
for (i in jsonArr.length() downTo pos + 1) {
jsonArr.put(i, jsonArr[i - 1])
}
jsonArr.put(pos, jsonObj)
}
fun newEmptyItem(): QuickWizardEntry {
return QuickWizardEntry(injector)
}
@ -57,4 +103,5 @@ class QuickWizard @Inject constructor(
storage.remove(position)
save()
}
}

View file

@ -19,6 +19,7 @@ import info.nightscout.androidaps.utils.JsonHelper.safeGetString
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
import org.json.JSONObject
import java.util.*
import javax.inject.Inject
class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjector) {
@ -41,11 +42,26 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
const val NO = 1
private const val POSITIVE_ONLY = 2
private const val NEGATIVE_ONLY = 3
const val DEVICE_ALL = 0
const val DEVICE_PHONE = 1
const val DEVICE_WATCH = 2
const val DEFAULT = 0
const val CUSTOM = 1
}
init {
injector.androidInjector().inject(this)
val emptyData = "{\"buttonText\":\"\",\"carbs\":0,\"validFrom\":0,\"validTo\":86340}"
val guid = UUID.randomUUID().toString()
val emptyData = """{
"guid": "$guid",
"buttonText": "",
"carbs": 0,
"validFrom": 0,
"validTo": 86340,
"device": "all",
"usePercentage": "default",
"percentage": 100
}""".trimMargin()
try {
storage = JSONObject(emptyData)
} catch (e: JSONException) {
@ -55,6 +71,8 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
/*
{
guid: string,
device: string, // (phone, watch, all)
buttonText: "Meal",
carbs: 36,
validFrom: 8 * 60 * 60, // seconds from midnight
@ -66,15 +84,18 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
useTrend: 0,
useSuperBolus: 0,
useTemptarget: 0
usePercentage: string, // default, custom
percentage: int,
}
*/
fun from(entry: JSONObject, position: Int): QuickWizardEntry {
// TODO set guid if missing for migration
storage = entry
this.position = position
return this
}
fun isActive(): Boolean = profileFunction.secondsFromMidnight() >= validFrom() && profileFunction.secondsFromMidnight() <= validTo()
fun isActive(): Boolean = profileFunction.secondsFromMidnight() >= validFrom() && profileFunction.secondsFromMidnight() <= validTo() && forDevice(DEVICE_PHONE)
fun doCalc(profile: Profile, profileName: String, lastBG: GlucoseValue, _synchronized: Boolean): BolusWizard {
val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
@ -119,10 +140,16 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
} else if (useTrend() == NEGATIVE_ONLY && glucoseStatus != null && glucoseStatus.shortAvgDelta < 0) {
trend = true
}
val percentage = sp.getInt(R.string.key_boluswizard_percentage, 100)
val percentage = if (usePercentage() == DEFAULT) sp.getInt(R.string.key_boluswizard_percentage, 100) else percentage()
return BolusWizard(injector).doCalc(profile, profileName, tempTarget, carbs(), cob, bg, 0.0, percentage, true, useCOB() == YES, bolusIOB, basalIOB, superBolus, useTempTarget() == YES, trend, false, buttonText(), quickWizard = true) //tbc, ok if only quickwizard, but if other sources elsewhere use Sources.QuickWizard
}
fun guid(): String = safeGetString(storage, "guid", "")
fun device(): Int = safeGetInt(storage, "device", DEVICE_ALL)
fun forDevice(device: Int) = device() == device || device() == DEVICE_ALL
fun buttonText(): String = safeGetString(storage, "buttonText", "")
fun carbs(): Int = safeGetInt(storage, "carbs")
@ -148,4 +175,8 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
fun useSuperBolus(): Int = safeGetInt(storage, "useSuperBolus", NO)
fun useTempTarget(): Int = safeGetInt(storage, "useTempTarget", NO)
}
fun usePercentage(): Int = safeGetInt(storage, "usePercentage", DEFAULT)
fun percentage(): Int = safeGetInt(storage, "percentage", 100)
}

View file

@ -83,14 +83,14 @@
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:labelFor="@+id/age"
android:text="@string/age"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/age"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/age" />
</TableRow>
@ -105,14 +105,14 @@
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:labelFor="@+id/weight"
android:text="@string/tdd_total"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/tdd"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/tdd_total" />
</TableRow>
@ -123,18 +123,18 @@
android:gravity="center_vertical">
<TextView
android:id="@+id/weigth_label"
android:id="@+id/weight_label"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:labelFor="@+id/weight"
android:text="@string/weight_label"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/weight"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/weight_label" />
</TableRow>
@ -149,14 +149,14 @@
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:labelFor="@+id/basalpctfromtdd"
android:text="@string/basalpctfromtdd_label"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/basalpctfromtdd"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/basalpctfromtdd_label" />
</TableRow>

View file

@ -58,6 +58,7 @@
android:paddingTop="10dp">
<TextView
android:id="@+id/bg_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -70,7 +71,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/bg"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/treatments_wizard_bg_label" />
<TextView
android:id="@+id/units"

View file

@ -93,6 +93,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/time_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -105,25 +106,24 @@
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="bold" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_access_alarm_24dp" />
<CheckBox
android:id="@+id/alarmCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:checked="false"
android:padding="2dp" />
android:padding="2dp"
android:layoutDirection="rtl"
android:contentDescription="@string/a11y_carb_reminder"
android:drawableEnd="@drawable/ic_access_alarm_24dp" />
</LinearLayout>
<info.nightscout.androidaps.utils.ui.MinutesNumberPicker
android:id="@+id/time"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/time_offset" />
<TextView
android:layout_width="wrap_content"
@ -143,6 +143,7 @@
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/duration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
@ -156,7 +157,8 @@
android:id="@+id/duration"
android:layout_width="130dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal" />
android:layout_gravity="center_horizontal"
app:customContentDescription="@string/careportal_newnstreatment_duration_label" />
<TextView
android:layout_width="wrap_content"
@ -176,6 +178,7 @@
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/carbs_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
@ -190,7 +193,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/carbs"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/treatments_wizard_carbs_label" />
<TextView
android:layout_width="wrap_content"

View file

@ -86,6 +86,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/bg_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -98,7 +99,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/bg"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/treatments_wizard_bg_label" />
<TextView
android:id="@+id/bg_units"
@ -121,6 +123,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/duration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -133,7 +136,8 @@
<info.nightscout.androidaps.utils.ui.MinutesNumberPicker
android:id="@+id/duration"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/careportal_newnstreatment_duration_label" />
<TextView
android:layout_width="wrap_content"

View file

@ -53,6 +53,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/insulin_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -65,7 +66,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/insulin"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/overview_insulin_label" />
<TextView
android:layout_width="wrap_content"
@ -86,6 +88,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/duration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -98,7 +101,8 @@
<info.nightscout.androidaps.utils.ui.MinutesNumberPicker
android:id="@+id/duration"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/careportal_newnstreatment_duration_label" />
<TextView
android:layout_width="wrap_content"

View file

@ -75,6 +75,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/fill_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@ -89,7 +90,8 @@
android:layout_height="40dp"
android:layout_gravity="end"
android:paddingLeft="5dp"
android:paddingRight="5dp" />
android:paddingRight="5dp"
app:customContentDescription="@string/overview_insulin_label" />
<TextView
android:layout_width="wrap_content"

View file

@ -76,6 +76,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/time_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -88,7 +89,8 @@
<info.nightscout.androidaps.utils.ui.MinutesNumberPicker
android:id="@+id/time"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/time_offset"/>
<TextView
android:layout_width="wrap_content"
@ -109,6 +111,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/insulin_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -121,7 +124,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/amount"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/overview_insulin_label"/>
<TextView
android:layout_width="wrap_content"
@ -147,6 +151,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:contentDescription="Increment insuline with 0.5"
android:text="+0.5" />
<Button

View file

@ -58,6 +58,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:width="120dp"
android:labelFor="@+id/profile"
android:padding="10dp"
android:text="@string/profile_label"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
@ -95,6 +96,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/duration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -108,7 +110,8 @@
<info.nightscout.androidaps.utils.ui.MinutesNumberPicker
android:id="@+id/duration"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/careportal_newnstreatment_duration_label"/>
<TextView
android:layout_width="wrap_content"
@ -129,6 +132,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/percentage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -142,7 +146,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/percentage"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/careportal_newnstreatment_percentage_label"/>
<TextView
android:layout_width="wrap_content"
@ -163,6 +168,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/timeshift_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -176,7 +182,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/timeshift"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/careportal_newnstreatment_timeshift_label"/>
<TextView
android:layout_width="wrap_content"

View file

@ -54,6 +54,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/basal_percent_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -66,7 +67,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/basal_percent_input"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/basal_rate" />
<TextView
android:layout_width="wrap_content"
@ -88,6 +90,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/basal_absolute_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -100,7 +103,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/basal_absolute_input"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/basal_rate" />
<TextView
android:layout_width="wrap_content"
@ -121,6 +125,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/duration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -133,7 +138,8 @@
<info.nightscout.androidaps.utils.ui.MinutesNumberPicker
android:id="@+id/duration"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/careportal_newnstreatment_duration_label" />
<TextView
android:layout_width="wrap_content"

View file

@ -54,6 +54,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/temptarget_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@ -67,7 +68,8 @@
android:id="@+id/temptarget"
android:layout_width="130dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical" />
android:layout_gravity="center_vertical"
app:customContentDescription="@string/target_label" />
<TextView
android:id="@+id/units"
@ -89,6 +91,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/duration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -101,7 +104,8 @@
<info.nightscout.androidaps.utils.ui.MinutesNumberPicker
android:id="@+id/duration"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/careportal_newnstreatment_duration_label" />
<TextView
android:layout_width="wrap_content"
@ -127,6 +131,7 @@
android:layout_gravity="center_vertical"
android:width="120dp"
android:padding="10dp"
android:labelFor="@+id/reason"
android:text="@string/reason"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="bold" />

View file

@ -63,6 +63,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/insulin_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -75,7 +76,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/insulin"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/overview_insulin_label" />
<TextView
android:layout_width="wrap_content"
@ -96,6 +98,7 @@
android:orientation="horizontal">
<TextView
android:id="@+id/carbs_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@ -108,7 +111,8 @@
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/carbs"
android:layout_width="130dp"
android:layout_height="40dp" />
android:layout_height="40dp"
app:customContentDescription="@string/treatments_wizard_carbs_label" />
<TextView
android:layout_width="wrap_content"

View file

@ -240,6 +240,69 @@
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|center_vertical"
android:layout_marginTop="0dp"
android:background="@android:color/transparent"
android:orientation="horizontal"
android:padding="0dp">
<CheckBox
android:id="@+id/bg_checkbox_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:scaleX="0.7"
android:scaleY="0.7"
android:button="@drawable/checkbox_bg_icon"
android:checked="true"
android:contentDescription="@string/treatments_wizard_bg_label" />
<CheckBox
android:id="@+id/trend_checkbox_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:scaleX="0.7"
android:scaleY="0.7"
android:button="@drawable/checkbox_trend_icon"
android:checked="true"
android:contentDescription="@string/bg_trend_label" />
<CheckBox
android:id="@+id/iob_checkbox_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:scaleX="0.9"
android:scaleY="0.9"
android:button="@drawable/checkbox_iob_icon"
android:checked="true"
android:contentDescription="@string/iob" />
<CheckBox
android:id="@+id/cob_checkbox_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:scaleX="0.7"
android:scaleY="0.7"
android:button="@drawable/checkbox_cob_icon"
android:checked="true"
android:contentDescription="@string/treatments_wizard_cob_label" />
</LinearLayout>
<LinearLayout
android:id="@+id/done_background"
@ -260,39 +323,7 @@
android:contentDescription="@string/show_calculation"
android:drawableEnd="@drawable/ic_visibility" />
<CheckBox
android:id="@+id/bg_checkbox_icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:button="@drawable/checkbox_bg_icon"
android:checked="true"
android:contentDescription="@string/treatments_wizard_bg_label" />
<CheckBox
android:id="@+id/trend_checkbox_icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:button="@drawable/checkbox_trend_icon"
android:checked="true"
android:contentDescription="@string/bg_trend_label" />
<CheckBox
android:id="@+id/iob_checkbox_icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:button="@drawable/checkbox_iob_icon"
android:checked="true"
android:contentDescription="@string/iob" />
<CheckBox
android:id="@+id/cob_checkbox_icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:button="@drawable/checkbox_cob_icon"
android:checked="true"
android:contentDescription="@string/treatments_wizard_cob_label" />
<include
<include
android:id="@+id/okcancel"
layout="@layout/okcancel" />

View file

@ -22,6 +22,7 @@
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:labelFor="@+id/spinner"
android:text="@string/selected_profile"
android:textAppearance="?android:attr/textAppearanceMedium" />
@ -97,7 +98,7 @@
android:layout_height="35dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="15dp"
android:contentDescription="@string/addnew"
android:contentDescription="@string/a11y_add_new_profile"
app:srcCompat="@drawable/ic_add" />
<ImageView
@ -106,7 +107,7 @@
android:layout_height="35dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="15dp"
android:contentDescription="@string/clone_label"
android:contentDescription="@string/a11y_clone_profile"
app:srcCompat="@drawable/ic_clone" />
<ImageView
@ -115,7 +116,7 @@
android:layout_height="35dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="15dp"
android:contentDescription="@string/remove_label"
android:contentDescription="@string/a11y_delete_current_profile"
android:scaleX="1"
android:scaleY="1"
app:srcCompat="@drawable/ic_remove" />
@ -190,7 +191,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/dia_label"
android:text="@string/dia_long_label"
android:textColor="@android:color/white"
android:textSize="20sp" />
@ -202,6 +203,7 @@
android:paddingTop="5dp">
<TextView
android:id="@+id/dia_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@ -214,7 +216,8 @@
android:layout_width="130dp"
android:layout_height="40dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp" />
android:layout_marginBottom="10dp"
app:customContentDescription="@string/dia" />
<TextView
android:layout_width="wrap_content"

View file

@ -108,6 +108,35 @@
</LinearLayout>
<TextView
android:id="@+id/device_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/overview_editquickwizard_show_on_device"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<RadioGroup
android:id="@+id/device"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="15dp">
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="All" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Phone" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Watch" />
</RadioGroup>
<TextView
android:layout_width="match_parent"
@ -276,7 +305,6 @@
</RadioGroup>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -301,6 +329,47 @@
</RadioGroup>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/overview_editquickwizard_usepercentage"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<RadioGroup
android:id="@+id/use_percentage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="15dp">
<RadioButton
android:id="@+id/use_percentage_default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_default" />
<RadioButton
android:id="@+id/use_percentage_custom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/custom" />
</RadioGroup>
<TextView
android:id="@+id/percentage_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/overview_editquickwizard_percentage"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<EditText
android:id="@+id/percentage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="number"
android:maxLength="3"
android:paddingLeft="10dp" />
<include
android:id="@+id/okcancel"

View file

@ -407,6 +407,19 @@
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/variable_sensitivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-9dp"
android:gravity="center_horizontal"
android:paddingBottom="3dp"
android:text="n/a"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold"
android:visibility="visible"
tools:ignore="HardcodedText" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -16,9 +16,9 @@
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
@ -29,37 +29,64 @@
card_view:srcCompat="@drawable/ic_quick_wizard" />
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:baselineAligned="true"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/overview_quickwizard_item_buttonText"
<TextView
android:id="@+id/overview_quickwizard_item_buttonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:text="Sample button text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@color/cardObjectiveText"
android:textStyle="normal|bold" />
<TextView
android:id="@+id/overview_quickwizard_item_carbs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:text="36g"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@color/cardObjectiveText"
android:textStyle="normal|bold" />
</LinearLayout>
<ImageView
android:id="@+id/overview_quickwizard_item_device"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:text="Sample button text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="normal|bold"
android:textColor="@color/cardObjectiveText" />
android:adjustViewBounds="false"
android:cropToPadding="false"
android:paddingRight="10dp"
android:scaleType="fitStart"
card_view:srcCompat="@drawable/ic_smartphone" />
<TextView
android:id="@+id/overview_quickwizard_item_carbs"
<ImageView
android:id="@+id/handleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:text="36g"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@color/cardObjectiveText"
android:textStyle="normal|bold" />
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:adjustViewBounds="false"
android:cropToPadding="false"
android:scaleType="fitStart"
card_view:srcCompat="@drawable/ic_reorder_gray_24dp" />
</LinearLayout>
@ -88,9 +115,9 @@
android:textStyle="normal|bold" />
<TextView
android:text="-"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:text="-" />
<TextView
android:id="@+id/overview_quickwizard_item_to"

View file

@ -80,6 +80,7 @@
<string name="description_ns_client">Synchronizes your data with Nightscout</string>
<string name="description_ama">State of the algorithm in 2017</string>
<string name="description_smb">Most recent algorithm for advanced users</string>
<string name="description_smb_dynamic_isf">Most recent algorithm for advanced users with dynamic/automatic ISF</string>
<string name="description_overview">Displays the current state of your loop and buttons for most common actions</string>
<string name="description_persistent_notification">Shows an ongoing notification with a short overview of what your loop is doing</string>
<string name="description_profile_local">Define a profile which is available offline.</string>
@ -313,7 +314,7 @@
<string name="wear">Wear</string>
<string name="resend_all_data">Resend All Data</string>
<string name="open_settings_on_wear">Open Settings on Wear</string>
<string name="basal_rate">Basal rate:</string>
<string name="basal_rate">Basal rate</string>
<string name="basalvaluebelowminimum">Basal value below minimum. Profile not set!</string>
<string name="sms_actualbg">BG:</string>
<string name="sms_lastbg">Last BG:</string>
@ -535,6 +536,7 @@
<string name="ns_localbroadcasts">Enable broadcasts to other apps (like xDrip+). Do not enable if you have more than one instance of AAPS or NSClient installed!</string>
<string name="ns_localbroadcasts_title">Enable local Broadcasts.</string>
<string name="openapssmb">OpenAPS SMB</string>
<string name="openaps_smb_dynamic_isf">OpenAPS SMB Dynamic ISF</string>
<string name="key_use_smb" translatable="false">use_smb</string>
<string name="key_use_uam" translatable="false">use_uam</string>
<string name="key_smb_enable_carbs_suggestions_threshold" translatable="false">smb_enable_carbs_suggestions_threshold</string>
@ -617,6 +619,7 @@
<string name="negativeonly">Negative only</string>
<string name="overview_editquickwizard_usecob">COB calculation</string>
<string name="overview_editquickwizard_usetemptarget">Temporary target calculation</string>
<string name="overview_editquickwizard_usepercentage">Percentage calculation</string>
<string name="loopenabled">Loop enabled</string>
<string name="apsselected">APS selected</string>
<string name="nsclienthaswritepermission">NSClient has write permission</string>
@ -953,8 +956,8 @@
<string name="low_mark_comment">Lower value of in range area (display only)</string>
<string name="high_mark_comment">Higher value of in range area (display only)</string>
<string name="age">Age:</string>
<string name="weight_label">Weight:</string>
<string name="age">Age</string>
<string name="weight_label">Weight</string>
<string name="id">ID:</string>
<string name="submit">Submit</string>
<string name="mostcommonprofile">Most common profile:</string>
@ -1164,5 +1167,44 @@
<string name="a11y_insulin_label">insulin</string>
<string name="a11y_blood_glucose">blood glucose</string>
<string name="a11y_bg_outdated">outdated</string>
<string name="a11y_carb_reminder">set reminder</string>
<string name="a11y_add_new_profile">add new profile</string>
<string name="a11y_clone_profile">clone current profile</string>
<string name="a11y_delete_current_profile">delete current profile</string>
<string name="a11y_add_new_to_list">add new to list</string>
<!-- WEAR OS-->
<string name="wear_action_tempt_preset_error">Temptarget unknown preset: %1$s</string>
<string name="wear_action_tempt_cancel_message">Cancelling running Temp-Targets?</string>
<string name="wear_action_tempt_unit_error">Different units used on watch and phone!</string>
<string name="wear_action_tempt_zero_message">Zero-Temp-Target - cancelling running Temp-Targets?</string>
<string name="wear_action_tempt_min_bg_error">Min-BG out of range!</string>
<string name="wear_action_tempt_max_bg_error">Max-BG out of range!</string>
<string name="wear_action_tempt_manual_range_message">Temptarget:\nMin: %1$s\nMax: %2$s\nDuration: %3$s</string>
<string name="wear_action_tempt_manual_message">Temptarget:\nTarget: %1$s\nDuration: %2$s</string>
<string name="wear_action_tempt_preset_message">Temptarget:\Reason: %1$s\nTarget: %2$s\nDuration: %3$s</string>
<string name="quick_wizard_message">QuickWizard: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg</string>
<string name="wizard_result">Calc. Wizard:\nInsulin: %1$.2fU\nCarbs: %2$dg</string>
<string name="overview_editquickwizard_show_on_device">Show entry on device:</string>
<string name="quick_wizard_not_available">Selected quickwizard no longer available, please refresh your tile</string>
<string name="wizard_no_actual_bg">No recent BG to base calculation on!</string>
<string name="wizard_no_active_profile">No active profile set!</string>
<string name="wizard_no_cob">Unknown COB! BG reading missing or recent app restart?</string>
<string name="wizard_carbs_constraint">Carb constraint violation!</string>
<string name="wizard_explain_calc">Calc (IC: %2$.1f, ISF: %2$.1f) from:"</string>
<string name="wizard_explain_carbs">Carbs: %1$.2fU</string>
<string name="wizard_explain_cob">COB: %1$.0fg %2$.2fU</string>
<string name="wizard_explain_bg">BG: %1$.2fU</string>
<string name="wizard_explain_basal_iob">Basal IOB: %1$.2fU</string>
<string name="wizard_explain_bolus_iob">Bolus IOB: %1$.2fU</string>
<string name="wizard_explain_superbolus">Superbolus: %1$.2fU</string>
<string name="wizard_explain_trend">15\' trend: %1$.2fU</string>
<string name="wizard_explain_percent">Percentage: %1$.2fU x %2$d%% ≈ %3$.2fU</string>
<string name="wizard_constraint_bolus_size">Insulin constraint violation!\nCannot deliver %1$.2fU</string>
<string name="wizard_explain_tt">TempT: %1$s</string>
<string name="wizard_explain_tt_to">%1$s to %2$s</string>
<string name="wizard_pump_not_available">No pump available!</string>
<string name="wear_unknown_action_string">Unknown action command:</string>
<string name="overview_editquickwizard_percentage">Percentage</string>
<string name="app_default">Application default</string>
</resources>

View file

@ -278,7 +278,7 @@ class AutomationPlugin @Inject constructor(
@Synchronized
fun removeIfExists(event: AutomationEvent) {
for (e in automationEvents) {
for (e in automationEvents.reversed()) {
if (event.title == e.title) {
automationEvents.remove(e)
rxBus.send(EventAutomationDataChanged())
@ -301,6 +301,7 @@ class AutomationPlugin @Inject constructor(
@Synchronized
fun at(index: Int) = automationEvents[index]
@Synchronized
fun size() = automationEvents.size
@Synchronized
@ -309,6 +310,7 @@ class AutomationPlugin @Inject constructor(
rxBus.send(EventAutomationDataChanged())
}
@Synchronized
fun userEvents(): List<AutomationEvent> {
val list = mutableListOf<AutomationEvent>()
val iterator: MutableIterator<AutomationEvent> = automationEvents.iterator()

View file

@ -9,7 +9,7 @@ buildscript {
rxkotlin_version = '2.4.0'
room_version = '2.3.0'
lifecycle_version = '2.3.1'
dagger_version = '2.40.5'
dagger_version = '2.41'
coroutines_version = '1.4.1'
activity_version = '1.3.1'
fragmentktx_version = '1.3.6'
@ -41,7 +41,7 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" } // jacoco 0.2
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.android.tools.build:gradle:7.1.0'
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.telephony.SmsManager
import dagger.Module
import dagger.Provides
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.resources.ResourceHelperImplementation
import javax.inject.Singleton
@ -17,7 +18,7 @@ open class CoreModule {
@Provides
@Singleton
fun provideResources(context: Context): ResourceHelper = ResourceHelperImplementation(context)
fun provideResources(context: Context, fabricPrivacy: FabricPrivacy): ResourceHelper = ResourceHelperImplementation(context, fabricPrivacy)
@Provides
fun smsManager() : SmsManager = SmsManager.getDefault()

View file

@ -1,10 +1,14 @@
package info.nightscout.androidaps.interfaces
import info.nightscout.androidaps.plugins.aps.loop.APSResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
interface APS {
val lastAPSResult: APSResult?
val lastAPSRun: Long
var lastDetermineBasalAdapter: DetermineBasalAdapterInterface?
var lastAutosensResult: AutosensResult
operator fun invoke(initiator: String, tempBasalFallback: Boolean)
}

View file

@ -0,0 +1,36 @@
package info.nightscout.androidaps.interfaces
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.plugins.aps.loop.APSResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
interface DetermineBasalAdapterInterface {
var currentTempParam: String?
var iobDataParam: String?
var glucoseStatusParam: String?
var profileParam: String?
var mealDataParam: String?
var scriptDebug: String
fun setData(profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean,
microBolusAllowed: Boolean = false,
uamAllowed: Boolean = false,
advancedFiltering: Boolean = false,
isSaveCgmSource: Boolean = false
) {}
operator fun invoke(): APSResult?
}

View file

@ -35,5 +35,5 @@ class PluginDescription {
fun enableByDefault(enableByDefault: Boolean): PluginDescription = this.also { it.enableByDefault = enableByDefault }
fun visibleByDefault(visibleByDefault: Boolean): PluginDescription = this.also { it.visibleByDefault = visibleByDefault }
fun description(description: Int): PluginDescription = this.also { it.description = description }
fun setDefault(): PluginDescription = this.also { it.defaultPlugin = true }
fun setDefault(value: Boolean = true): PluginDescription = this.also { it.defaultPlugin = value }
}

View file

@ -77,10 +77,16 @@ class FabricPrivacy @Inject constructor(
}
}
// Crashlytics log message
fun logMessage(message: String) {
aapsLogger.info(LTag.CORE,"Crashlytics log message: $message")
FirebaseCrashlytics.getInstance().log(message)
}
// Crashlytics logException
fun logException(throwable: Throwable) {
aapsLogger.error("Crashlytics log exception: ", throwable)
FirebaseCrashlytics.getInstance().recordException(throwable)
aapsLogger.error("Exception: ", throwable)
}
fun fabricEnabled(): Boolean {

View file

@ -11,17 +11,35 @@ import android.util.DisplayMetrics
import androidx.annotation.*
import androidx.core.content.ContextCompat
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.utils.FabricPrivacy
import java.util.*
import javax.inject.Inject
/**
* Created by adrian on 2019-12-23.
*/
class ResourceHelperImplementation @Inject constructor(private val context: Context) : ResourceHelper {
class ResourceHelperImplementation @Inject constructor(private val context: Context, private val fabricPrivacy: FabricPrivacy) : ResourceHelper {
override fun gs(@StringRes id: Int): String = context.getString(id)
override fun gs(@StringRes id: Int, vararg args: Any?): String = context.getString(id, *args)
override fun gs(@StringRes id: Int, vararg args: Any?) : String {
return try {
context.getString(id, *args)
} catch (exception: Exception) {
val resourceName = context.resources.getResourceEntryName(id)
val resourceValue = context.getString(id)
val currentLocale: Locale = context.resources.configuration.locale
fabricPrivacy.logMessage("Failed to get string for resource $resourceName ($id) '$resourceValue' for locale $currentLocale with args ${args.map{it.toString()}}")
fabricPrivacy.logException(exception)
try {
gsNotLocalised(id, *args)
} catch (exceptionNonLocalized: Exception) {
fabricPrivacy.logMessage("Fallback failed to get string for resource $resourceName ($id) '$resourceValue' with args ${args.map { it.toString() }}")
fabricPrivacy.logException(exceptionNonLocalized)
"FAILED to get string $resourceName"
}
}
}
override fun gq(@PluralsRes id: Int, quantity: Int, vararg args: Any?): String =
context.resources.getQuantityString(id, quantity, *args)
@ -29,7 +47,7 @@ class ResourceHelperImplementation @Inject constructor(private val context: Cont
override fun gsNotLocalised(@StringRes id: Int, vararg args: Any?): String =
with(Configuration(context.resources.configuration)) {
setLocale(Locale.ENGLISH)
context.createConfigurationContext(this).getString(id, args)
context.createConfigurationContext(this).getString(id, *args)
}
override fun gc(@ColorRes id: Int): Int = ContextCompat.getColor(context, id)

View file

@ -11,7 +11,6 @@
android:shape="rectangle">
<stroke android:width="1dp" android:color="@color/colorLightGray" />
<solid android:color="@android:color/white" />
<corners android:radius="4dp" />
</shape>
</item>
</layer-list>

View file

@ -1,8 +1,9 @@
<vector
android:height="48dp"
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:viewportHeight="24"
android:height="48dp"
android:viewportWidth="24"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#67DFE8" android:pathData="M18.501,12.002l-0.428,0.564L8.179,5.064c-0.092,-0.07 -0.212,-0.09 -0.323,-0.055L5.115,5.89L1.571,3.501L4.755,6.36L4.647,9.243c-0.004,0.116 0.047,0.226 0.14,0.296l9.895,7.502l-0.428,0.564c-0.26,0.343 -0.193,0.832 0.15,1.092s0.832,0.193 1.092,-0.15l1.653,-2.18l2.219,1.683l-0.719,0.948c-0.26,0.343 -0.193,0.832 0.15,1.092c0.343,0.26 0.832,0.193 1.092,-0.15l2.379,-3.138c0.26,-0.343 0.193,-0.832 -0.15,-1.092c-0.343,-0.26 -0.832,-0.193 -1.092,0.15l-0.719,0.948l-2.219,-1.683l1.653,-2.18c0.26,-0.343 0.193,-0.832 -0.15,-1.092S18.762,11.659 18.501,12.002zM5.458,6.524l2.44,-0.783l9.748,7.39L17.26,13.64l-5.754,-4.362c-0.091,-0.069 -0.221,-0.053 -0.293,0.035L9.087,11.91l-0.67,-0.508l1.526,-1.833c0.002,-0.002 0.004,-0.005 0.006,-0.008c0.068,-0.09 0.054,-0.219 -0.033,-0.291c-0.09,-0.075 -0.224,-0.063 -0.299,0.027l-1.538,1.848l-0.769,-0.583l1.526,-1.833c0.002,-0.002 0.004,-0.005 0.006,-0.008c0.068,-0.09 0.054,-0.219 -0.033,-0.291c-0.09,-0.075 -0.224,-0.063 -0.299,0.027L6.97,10.305L6.201,9.721l1.526,-1.833C7.728,7.886 7.731,7.882 7.732,7.88c0.068,-0.09 0.054,-0.219 -0.033,-0.291C7.609,7.513 7.475,7.526 7.4,7.616L5.861,9.464L5.362,9.085L5.458,6.524z"/>
android:viewportHeight="24">
<path
android:fillColor="#67DFE8"
android:pathData="M18.501,12.002l-0.428,0.564L8.179,5.064c-0.092,-0.07 -0.212,-0.09 -0.323,-0.055L5.115,5.89L1.571,3.501L4.755,6.36L4.647,9.243c-0.004,0.116 0.047,0.226 0.14,0.296l9.895,7.502l-0.428,0.564c-0.26,0.343 -0.193,0.832 0.15,1.092s0.832,0.193 1.092,-0.15l1.653,-2.18l2.219,1.683l-0.719,0.948c-0.26,0.343 -0.193,0.832 0.15,1.092c0.343,0.26 0.832,0.193 1.092,-0.15l2.379,-3.138c0.26,-0.343 0.193,-0.832 -0.15,-1.092c-0.343,-0.26 -0.832,-0.193 -1.092,0.15l-0.719,0.948l-2.219,-1.683l1.653,-2.18c0.26,-0.343 0.193,-0.832 -0.15,-1.092S18.762,11.659 18.501,12.002zM5.458,6.524l2.44,-0.783l9.748,7.39L17.26,13.64l-5.754,-4.362c-0.091,-0.069 -0.221,-0.053 -0.293,0.035L9.087,11.91l-0.67,-0.508l1.526,-1.833c0.002,-0.002 0.004,-0.005 0.006,-0.008c0.068,-0.09 0.054,-0.219 -0.033,-0.291c-0.09,-0.075 -0.224,-0.063 -0.299,0.027l-1.538,1.848l-0.769,-0.583l1.526,-1.833c0.002,-0.002 0.004,-0.005 0.006,-0.008c0.068,-0.09 0.054,-0.219 -0.033,-0.291c-0.09,-0.075 -0.224,-0.063 -0.299,0.027L6.97,10.305L6.201,9.721l1.526,-1.833C7.728,7.886 7.731,7.882 7.732,7.88c0.068,-0.09 0.054,-0.219 -0.033,-0.291C7.609,7.513 7.475,7.526 7.4,7.616L5.861,9.464L5.362,9.085L5.458,6.524z" />
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
</vector>

View file

@ -9,13 +9,13 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:width="120dp"
android:labelFor="@+id/eventtime"
android:paddingLeft="10dp"
android:paddingRight="4dp"
android:text="@string/event_time_label"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="bold" />
<TextView
android:id="@+id/eventdate"
android:layout_width="wrap_content"

View file

@ -27,6 +27,7 @@
<string name="key_virtualpump_type" translatable="false">virtualpump_type</string>
<string name="key_quickwizard" translatable="false">QuickWizard</string>
<string name="key_wear_control" translatable="false">wearcontrol</string>
<string name="key_units_mgdl" translatable="false">units_mgdl</string>
<string name="key_show_notes_entry_dialogs" translatable="false">show_notes_entry_dialogs</string>
<string name="key_openapsama_autosens_max" translatable="false">autosens_max</string>
<string name="key_openapsama_autosens_min" translatable="false">autosens_min</string>

View file

@ -25,7 +25,7 @@ dependencies {
api "io.reactivex.rxjava2:rxandroid:$rxandroid_version"
api("io.reactivex.rxjava2:rxkotlin:$rxkotlin_version")
api "com.google.code.gson:gson:2.8.9"
api "com.google.code.gson:gson:2.9.0"
api "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"

View file

@ -30,7 +30,7 @@ android {
targetCompatibility JavaVersion.VERSION_11
}
lintOptions {
lint {
checkReleaseBuilds false
disable 'MissingTranslation'
disable 'ExtraTranslation'

View file

@ -768,7 +768,7 @@ class MedtronicPumpPlugin @Inject constructor(
medtronicPumpStatus.tempBasalAmount = absoluteRate
medtronicPumpStatus.tempBasalLength = durationInMinutes
val tempData = PumpDbEntryTBR(absoluteRate, true, durationInMinutes, tbrType)
val tempData = PumpDbEntryTBR(absoluteRate, true, durationInMinutes * 60, tbrType)
medtronicPumpStatus.runningTBRWithTemp = tempData
pumpSyncStorage.addTemporaryBasalRateWithTempId(tempData, true, this)

View file

@ -3,7 +3,11 @@ package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble
import android.annotation.SuppressLint
import android.bluetooth.*
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.SystemClock
import androidx.core.content.ContextCompat
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes
@ -32,14 +36,15 @@ import javax.inject.Singleton
* Added: State handling, configuration of RF for different configuration ranges, connection handling
*/
@Singleton
class RileyLinkBLE @Inject constructor(private val context: Context) {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var rileyLinkServiceData: RileyLinkServiceData
@Inject lateinit var rileyLinkUtil: RileyLinkUtil
@Inject lateinit var sp: SP
@Inject lateinit var orangeLink: OrangeLinkImpl
class RileyLinkBLE @Inject constructor(
private val context: Context,
private val aapsLogger: AAPSLogger,
private val rileyLinkServiceData: RileyLinkServiceData,
private val rileyLinkUtil: RileyLinkUtil,
private val sp: SP,
private val orangeLink: OrangeLinkImpl,
private val config: Config
) {
private val gattDebugEnabled = true
private var manualDisconnect = false
@ -154,7 +159,12 @@ class RileyLinkBLE @Inject constructor(private val context: Context) {
aapsLogger.error(LTag.PUMPBTCOMM, "RileyLink device is null, can't do connectGatt.")
return
}
bluetoothConnectionGatt = rileyLinkDevice?.connectGatt(context, true, bluetoothGattCallback)
if (config.PUMPDRIVERS && Build.VERSION.SDK_INT >= /*Build.VERSION_CODES.S*/31 &&
ContextCompat.checkSelfPermission(context, "android.permission.BLUETOOTH_CONNECT") == PackageManager.PERMISSION_GRANTED
) {
aapsLogger.debug(LTag.PUMPBTCOMM, "no permission")
return
} else bluetoothConnectionGatt = rileyLinkDevice?.connectGatt(context, true, bluetoothGattCallback)
// , BluetoothDevice.TRANSPORT_LE
if (bluetoothConnectionGatt == null)
aapsLogger.error(LTag.PUMPBTCOMM, "Failed to connect to Bluetooth Low Energy device at " + bluetoothAdapter?.address)

View file

@ -53,7 +53,7 @@ android {
defaultConfig {
applicationId "info.nightscout.androidaps"
minSdkVersion 23
targetSdkVersion 28
targetSdkVersion 29
versionCode 2
versionName "1.0.3"
buildConfigField "String", "BUILDVERSION", generateGitBuild()
@ -107,7 +107,6 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.wear:wear:1.2.0'
compileOnly "com.google.android.wearable:wearable:${wearableVersion}"
implementation "com.google.android.support:wearable:${wearableVersion}"
implementation "com.google.android.gms:play-services-wearable:${playServicesWearable}"
@ -115,6 +114,10 @@ dependencies {
implementation(name: 'wearpreferenceactivity-0.5.0', ext: 'aar')
implementation('com.github.lecho:hellocharts-library:1.5.8@aar')
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0'
implementation "androidx.core:core-ktx:$core_version"
implementation "androidx.wear.tiles:tiles:1.0.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
testImplementation "junit:junit:$junit_version"

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="info.nightscout.androidaps">
<uses-feature android:name="android.hardware.type.watch" />
@ -8,6 +9,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-sdk tools:overrideLibrary="androidx.wear.tiles" />
<application
android:name=".Aaps"
@ -256,6 +258,10 @@
android:host="*"
android:pathPrefix="/openwearsettings"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/send_quick_wizard"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/sendstatustowear"
@ -508,6 +514,54 @@
android:value="0" />
</service>
<service
android:name=".tile.ActionsTileService"
android:exported="true"
android:label="@string/label_actions_tile"
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
<intent-filter>
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
</intent-filter>
<meta-data
android:name="com.google.android.clockwork.tiles.PROVIDER_CONFIG_ACTION"
android:value="tile_configuration_activity" />
<meta-data
android:name="androidx.wear.tiles.PREVIEW"
android:resource="@drawable/action_tile_preview" />
</service>
<service
android:name=".tile.TempTargetTileService"
android:exported="true"
android:label="@string/label_temp_target_tile"
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
<intent-filter>
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
</intent-filter>
<meta-data
android:name="com.google.android.clockwork.tiles.PROVIDER_CONFIG_ACTION"
android:value="tile_configuration_tempt" />
<meta-data
android:name="androidx.wear.tiles.PREVIEW"
android:resource="@drawable/temp_target_tile_preview" />
</service>
<service
android:name=".tile.QuickWizardTileService"
android:exported="true"
android:label="@string/label_quick_wizard_tile"
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
<intent-filter>
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
</intent-filter>
<meta-data
android:name="androidx.wear.tiles.PREVIEW"
android:resource="@drawable/quick_wizard_tile_preview" />
</service>
<receiver android:name=".complications.ComplicationTapBroadcastReceiver" />
<activity
@ -523,6 +577,7 @@
android:label="@string/menu_settings" />
<activity
android:name=".interaction.actions.WizardActivity"
android:exported="true"
android:label="@string/menu_wizard" />
<activity
android:name=".interaction.menus.FillMenuActivity"
@ -530,9 +585,14 @@
<activity
android:name=".interaction.menus.StatusMenuActivity"
android:label="@string/menu_status" />
<activity
android:name=".interaction.actions.TreatmentActivity"
android:exported="true"
android:label="@string/menu_treatment" />
<activity
android:name=".interaction.actions.BolusActivity"
android:label="@string/action_bolus" />
android:exported="true"
android:label="@string/action_insulin" />
<activity
android:name=".interaction.actions.CPPActivity"
android:label="@string/status_cpp" />
@ -542,14 +602,21 @@
android:launchMode="singleInstance" />
<activity
android:name=".interaction.actions.FillActivity"
android:exported="true"
android:label="@string/menu_prime_fill" />
<activity
android:name=".interaction.actions.ECarbActivity"
android:exported="true"
android:label="@string/action_carbs" />
<activity
android:name=".interaction.actions.TempTargetActivity"
android:exported="true"
android:label="@string/menu_tempt" />
<activity
android:name=".interaction.actions.BackgroundActionActivity"
android:exported="true" />
<activity android:name=".interaction.ConfigurationActivity">
<intent-filter>
<!-- action-name must be equal with name of xml-ressource where the configuration is describe -->
@ -567,5 +634,16 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".interaction.TileConfigurationActivity">
<intent-filter>
<action android:name="tile_configuration_activity" />
<action android:name="tile_configuration_tempt" />
<category android:name="com.google.android.clockwork.tiles.category.PROVIDER_CONFIG" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -15,7 +15,7 @@ import javax.inject.Inject;
import dagger.android.DaggerBroadcastReceiver;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.interaction.actions.BolusActivity;
import info.nightscout.androidaps.interaction.actions.TreatmentActivity;
import info.nightscout.androidaps.interaction.actions.ECarbActivity;
import info.nightscout.androidaps.interaction.actions.WizardActivity;
import info.nightscout.androidaps.interaction.menus.MainMenuActivity;
@ -77,7 +77,7 @@ public class ComplicationTapBroadcastReceiver extends DaggerBroadcastReceiver {
intentOpen = new Intent(context, WizardActivity.class);
break;
case BOLUS:
intentOpen = new Intent(context, BolusActivity.class);
intentOpen = new Intent(context, TreatmentActivity.class);
break;
case ECARB:
intentOpen = new Intent(context, ECarbActivity.class);

View file

@ -13,18 +13,16 @@ import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.wear.tiles.TileService;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.ChannelApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
@ -35,7 +33,8 @@ import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
@ -46,9 +45,11 @@ import info.nightscout.androidaps.interaction.AAPSPreferences;
import info.nightscout.androidaps.interaction.actions.AcceptActivity;
import info.nightscout.androidaps.interaction.actions.CPPActivity;
import info.nightscout.androidaps.interaction.utils.Persistence;
import info.nightscout.shared.SafeParse;
import info.nightscout.androidaps.interaction.utils.WearUtil;
import info.nightscout.androidaps.tile.ActionsTileService;
import info.nightscout.androidaps.tile.QuickWizardTileService;
import info.nightscout.androidaps.tile.TempTargetTileService;
import info.nightscout.shared.SafeParse;
/**
* Created by emmablack on 12/26/14.
@ -59,7 +60,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Inject WearUtil wearUtil;
@Inject Persistence persistence;
private static final String WEARABLE_DATA_PATH = "/nightscout_watch_data";
private static final String WEARABLE_RESEND_PATH = "/nightscout_watch_data_resend";
private static final String WEARABLE_CANCELBOLUS_PATH = "/nightscout_watch_cancel_bolus";
public static final String WEARABLE_CONFIRM_ACTIONSTRING_PATH = "/nightscout_watch_confirmactionstring";
@ -68,13 +68,13 @@ public class ListenerService extends WearableListenerService implements GoogleAp
private static final String OPEN_SETTINGS = "/openwearsettings";
private static final String NEW_STATUS_PATH = "/sendstatustowear";
private static final String NEW_PREFERENCES_PATH = "/sendpreferencestowear";
private static final String QUICK_WIZARD_PATH = "/send_quick_wizard";
public static final String BASAL_DATA_PATH = "/nightscout_watch_basal";
public static final String BOLUS_PROGRESS_PATH = "/nightscout_watch_bolusprogress";
public static final String ACTION_CONFIRMATION_REQUEST_PATH = "/nightscout_watch_actionconfirmationrequest";
public static final String NEW_CHANGECONFIRMATIONREQUEST_PATH = "/nightscout_watch_changeconfirmationrequest";
public static final String ACTION_CANCELNOTIFICATION_REQUEST_PATH = "/nightscout_watch_cancelnotificationrequest";
public static final int BOLUS_PROGRESS_NOTIF_ID = 1;
public static final int CONFIRM_NOTIF_ID = 2;
public static final int CHANGE_NOTIF_ID = 556677;
@ -85,29 +85,15 @@ public class ListenerService extends WearableListenerService implements GoogleAp
private static final String ACTION_CONFIRMCHANGE = "com.dexdrip.stephenblack.nightwatch.CONFIRMCHANGE";
private static final String ACTION_INITIATE_ACTION = "com.dexdrip.stephenblack.nightwatch.INITIATE_ACTION";
private static final String ACTION_RESEND_BULK = "com.dexdrip.stephenblack.nightwatch.RESEND_BULK_DATA";
private static final String AAPS_NOTIFY_CHANNEL_ID_OPENLOOP = "AndroidAPS-OpenLoop";
private static final String AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS = "bolus progress vibration";
private static final String AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT = "bolus progress silent";
GoogleApiClient googleApiClient;
private long lastRequest = 0;
private DismissThread bolusprogressThread;
private static final String TAG = "ListenerService";
private DataRequester mDataRequester = null;
private static final int GET_CAPABILITIES_TIMEOUT_MS = 5000;
// Phone
private static final String CAPABILITY_PHONE_APP = "phone_app_sync_bgs";
private static final String MESSAGE_PATH_PHONE = "/phone_message_path";
// Wear
private static final String CAPABILITY_WEAR_APP = "wear_app_sync_bgs";
private static final String MESSAGE_PATH_WEAR = "/wear_message_path";
private final String mPhoneNodeId = null;
private String localnode = null;
private final String logPrefix = ""; // "WR: "
// Not derived from DaggerService, do injection here
@ -117,143 +103,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
super.onCreate();
}
public class DataRequester extends AsyncTask<Void, Void, Void> {
Context mContext;
String path;
byte[] payload;
DataRequester(Context context, String thispath, byte[] thispayload) {
path = thispath;
payload = thispayload;
// Log.d(TAG, logPrefix + "DataRequester DataRequester: " + thispath + " lastRequest:" + lastRequest);
}
@Override
protected Void doInBackground(Void... params) {
// Log.d(TAG, logPrefix + "DataRequester: doInBack: " + params);
try {
forceGoogleApiConnect();
DataMap datamap;
if (isCancelled()) {
Log.d(TAG, "doInBackground CANCELLED programmatically");
return null;
}
if (googleApiClient != null) {
if (!googleApiClient.isConnected())
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
// this code might not be needed in this way, but we need to see that later
if ((googleApiClient != null) && (googleApiClient.isConnected())) {
if ((System.currentTimeMillis() - lastRequest > 20 * 1000)) {
// enforce 20-second debounce period
lastRequest = System.currentTimeMillis();
// NodeApi.GetConnectedNodesResult nodes =
// Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
if (localnode == null || (localnode != null && localnode.isEmpty()))
setLocalNodeName();
CapabilityInfo capabilityInfo = getCapabilities();
int count = 0;
Node phoneNode = null;
if (capabilityInfo != null) {
phoneNode = updatePhoneSyncBgsCapability(capabilityInfo);
count = capabilityInfo.getNodes().size();
}
Log.d(TAG, "doInBackground connected. CapabilityApi.GetCapabilityResult mPhoneNodeID="
+ (phoneNode != null ? phoneNode.getId() : "") + " count=" + count + " localnode="
+ localnode);// KS
if (count > 0) {
for (Node node : capabilityInfo.getNodes()) {
// Log.d(TAG, "doInBackground path: " + path);
switch (path) {
// simple send as is payloads
case WEARABLE_RESEND_PATH:
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(),
WEARABLE_RESEND_PATH, null);
break;
case WEARABLE_DATA_PATH:
case WEARABLE_CANCELBOLUS_PATH:
case WEARABLE_CONFIRM_ACTIONSTRING_PATH:
case WEARABLE_INITIATE_ACTIONSTRING_PATH:
case OPEN_SETTINGS:
case NEW_STATUS_PATH:
case NEW_PREFERENCES_PATH:
case BASAL_DATA_PATH:
case BOLUS_PROGRESS_PATH:
case ACTION_CONFIRMATION_REQUEST_PATH:
case NEW_CHANGECONFIRMATIONREQUEST_PATH:
case ACTION_CANCELNOTIFICATION_REQUEST_PATH: {
Log.w(TAG, logPrefix + "Unhandled path");
// sendMessagePayload(node, path, path, payload);
}
default:// SYNC_ALL_DATA
// this fall through is messy and non-deterministic for new paths
}
}
} else {
Log.d(TAG, logPrefix + "doInBackground connected but getConnectedNodes returns 0.");
}
} else {
// no resend
Log.d(TAG, logPrefix + "Inside the timeout, will not be executed");
}
} else {
Log.d(TAG, logPrefix + "Not connected for sending: api "
+ ((googleApiClient == null) ? "is NULL!" : "not null"));
if (googleApiClient != null) {
googleApiClient.connect();
} else {
googleApiConnect();
}
}
} catch (Exception ex) {
Log.e(TAG, logPrefix + "Error executing DataRequester in background. Exception: " + ex.getMessage());
}
return null;
}
}
public CapabilityInfo getCapabilities() {
CapabilityApi.GetCapabilityResult capabilityResult = Wearable.CapabilityApi.getCapability(googleApiClient,
CAPABILITY_PHONE_APP, CapabilityApi.FILTER_REACHABLE).await(GET_CAPABILITIES_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
if (!capabilityResult.getStatus().isSuccess()) {
Log.e(TAG, logPrefix + "doInBackground Failed to get capabilities, status: "
+ capabilityResult.getStatus().getStatusMessage());
return null;
}
return capabilityResult.getCapability();
}
public class BolusCancelTask extends AsyncTask<Void, Void, Void> {
Context mContext;
@ -263,8 +112,11 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override
protected Void doInBackground(Void... params) {
// Log.d(TAG, logPrefix + "BolusCancelTask: doInBack: " + params);
Log.d(TAG, logPrefix + "BolusCancelTask.doInBackground: " + params);
if (!googleApiClient.isConnected()) {
Log.i(TAG, "BolusCancelTask.doInBackground: not connected");
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
@ -272,16 +124,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_CANCELBOLUS_PATH, null);
}
} else {
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_CANCELBOLUS_PATH, null);
}
}
}
return null;
}
@ -300,9 +142,12 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override
protected Void doInBackground(Void... params) {
Log.i(TAG, "MessageActionTask.doInBackground: ");
forceGoogleApiConnect();
if (!googleApiClient.isConnected()) {
Log.i(TAG, "MessageActionTask.doInBackground: not connected");
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
@ -310,22 +155,42 @@ public class ListenerService extends WearableListenerService implements GoogleAp
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), mMessagePath, mActionstring.getBytes());
}
} else {
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), mMessagePath, mActionstring.getBytes());
}
}
}
return null;
}
}
public class ResendDataTask extends AsyncTask<Void, Void, Void> {
Context mContext;
ResendDataTask(Context context) {
mContext = context;
}
@Override
protected Void doInBackground(Void... params) {
Log.d(TAG, logPrefix + "ResendDataTask.doInBackground: " + params);
if (!googleApiClient.isConnected()) {
Log.i(TAG, "ResendDataTask.doInBackground: not connected");
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
if (googleApiClient.isConnected()) {
Log.i(TAG, "ResendDataTask.doInBackground: connected");
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_RESEND_PATH, null);
}
} else {
Log.i(TAG, "ResendDataTask.doInBackground: could not connect");
}
return null;
}
}
public void requestData() {
sendData(WEARABLE_RESEND_PATH, null);
new ResendDataTask(this).execute();
}
public void cancelBolus() {
@ -340,59 +205,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
new MessageActionTask(this, WEARABLE_INITIATE_ACTIONSTRING_PATH, actionstring).execute();
}
private Node updatePhoneSyncBgsCapability(CapabilityInfo capabilityInfo) {
// Log.d(TAG, "CapabilityInfo: " + capabilityInfo);
Set<Node> connectedNodes = capabilityInfo.getNodes();
return pickBestNode(connectedNodes);
// mPhoneNodeId = pickBestNodeId(connectedNodes);
}
private Node pickBestNode(Set<Node> nodes) {
Node bestNode = null;
// Find a nearby node or pick one arbitrarily
for (Node node : nodes) {
if (node.isNearby()) {
return node;
}
bestNode = node;
}
return bestNode;
}
private synchronized void sendData(String path, byte[] payload) {
// Log.d(TAG, "WR: sendData: path: " + path + ", payload=" + payload);
if (path == null)
return;
if (mDataRequester != null) {
// Log.d(TAG, logPrefix + "sendData DataRequester != null lastRequest:" +
// WearUtil.dateTimeText(lastRequest));
if (mDataRequester.getStatus() != AsyncTask.Status.FINISHED) {
// Log.d(TAG, logPrefix + "sendData Should be canceled? Let run 'til finished.");
// mDataRequester.cancel(true);
}
}
Log.d(TAG,
logPrefix + "sendData: execute lastRequest:" + wearUtil.dateTimeText(lastRequest));
mDataRequester = (DataRequester) new DataRequester(this, path, payload).execute();
// executeTask(mDataRequester);
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Log.d(TAG, "sendData SDK < M call execute lastRequest:" + WearUtil.dateTimeText(lastRequest));
// mDataRequester = (DataRequester) new DataRequester(this, path, payload).execute();
// } else {
// Log.d(TAG, "sendData SDK >= M call executeOnExecutor lastRequest:" + WearUtil.dateTimeText(lastRequest));
// // TODO xdrip executor
// mDataRequester = (DataRequester) new DataRequester(this, path, payload).executeOnExecutor(xdrip.executor);
// }
}
private void googleApiConnect() {
if (googleApiClient != null) {
// Remove old listener(s)
@ -416,20 +228,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
Wearable.MessageApi.addListener(googleApiClient, this);
}
private void forceGoogleApiConnect() {
if (googleApiClient == null || (!googleApiClient.isConnected() && !googleApiClient.isConnecting())) {
try {
Log.d(TAG, "forceGoogleApiConnect: forcing google api reconnection");
googleApiConnect();
Thread.sleep(2000);
} catch (InterruptedException e) {
//
}
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
@ -483,7 +281,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
return START_STICKY;
}
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
@ -541,12 +338,62 @@ public class ListenerService extends WearableListenerService implements GoogleAp
LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
} else if (path.equals(NEW_PREFERENCES_PATH)) {
dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
if (dataMap.containsKey("wearcontrol")) {
boolean wearcontrol = dataMap.getBoolean("wearcontrol", false);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("wearcontrol", wearcontrol);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sharedPreferences.edit();
String keyControl = getString(R.string.key_wear_control);
if (dataMap.containsKey(keyControl)) {
boolean previousWearControl = sharedPreferences.getBoolean(keyControl, false);
boolean wearControl = dataMap.getBoolean(keyControl, false);
editor.putBoolean(keyControl, wearControl);
editor.apply();
if (wearControl != previousWearControl) {
updateTiles();
}
}
String keyPercentage = getString(R.string.key_boluswizard_percentage);
if (dataMap.containsKey(keyPercentage)) {
int wpercentage = dataMap.getInt(keyPercentage, 100);
editor.putInt(keyPercentage, wpercentage);
editor.apply();
}
String keyUnits = getString(R.string.key_units_mgdl);
if (dataMap.containsKey(keyUnits)) {
boolean mgdl = dataMap.getBoolean(keyUnits, true);
editor.putBoolean(keyUnits, mgdl);
editor.apply();
}
String keyMaxCarbs = getString(R.string.key_treatmentssafety_maxcarbs);
if (dataMap.containsKey(keyMaxCarbs)) {
int maxCarbs = dataMap.getInt(keyMaxCarbs, 48);
editor.putInt(keyMaxCarbs, maxCarbs);
editor.apply();
}
String keyMaxBolus = getString(R.string.key_treatmentssafety_maxbolus);
if (dataMap.containsKey(keyMaxBolus)) {
float maxBolus = (float)dataMap.getDouble(keyMaxBolus, 3.0f);
editor.putFloat(keyMaxBolus, maxBolus);
editor.apply();
}
} else if (path.equals(QUICK_WIZARD_PATH)) {
dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
Log.i(TAG, "onDataChanged: QUICK_WIZARD_PATH" + dataMap);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
dataMap.remove("timestamp");
String key = getString(R.string.key_quick_wizard_data_map);
String dataString = Base64.encodeToString(dataMap.toByteArray(), Base64.DEFAULT);
if (!dataString.equals(sharedPreferences.getString(key, ""))) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(key, dataString);
editor.apply();
// Todo maybe add debounce function, due to 20 seconds update limit?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
TileService.getUpdater(this)
.requestUpdate(QuickWizardTileService.class);
}
Log.i(TAG, "onDataChanged: updated QUICK_WIZARD");
} else {
Log.i(TAG, "onDataChanged: ignore update");
}
} else if (path.equals(NEW_CHANGECONFIRMATIONREQUEST_PATH)) {
String title = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("title");
@ -568,6 +415,19 @@ public class ListenerService extends WearableListenerService implements GoogleAp
}
}
private void updateTiles() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
TileService.getUpdater(this)
.requestUpdate(ActionsTileService.class);
TileService.getUpdater(this)
.requestUpdate(TempTargetTileService.class);
TileService.getUpdater(this)
.requestUpdate(QuickWizardTileService.class);
}
}
private void notifyChangeRequest(String title, String message, String actionstring) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
@ -639,7 +499,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp
PendingIntent cancelPendingIntent = PendingIntent.getService(this, 0, cancelIntent, 0);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, vibrate ? AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS: AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT)
new NotificationCompat.Builder(this, vibrate ? AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS : AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT)
.setSmallIcon(R.drawable.ic_icon)
.setContentTitle(getString(R.string.bolus_progress))
.setContentText(progresspercent + "% - " + progresstatus)
@ -696,7 +556,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
bolusprogressThread.start();
}
private class DismissThread extends Thread {
private final int notificationID;
private final int seconds;
@ -730,7 +589,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp
context.startService(intent);
}
public static void initiateAction(Context context, String actionstring) {
public static void initiateAction(Context context, @NotNull String actionstring) {
Intent intent = new Intent(context, ListenerService.class);
intent.putExtra("actionstring", actionstring);
intent.setAction(ACTION_INITIATE_ACTION);
@ -753,20 +612,8 @@ public class ListenerService extends WearableListenerService implements GoogleAp
public void onConnected(Bundle bundle) {
// Log.d(TAG, logPrefix + "onConnected call requestData");
CapabilityApi.CapabilityListener capabilityListener = new CapabilityApi.CapabilityListener() {
@Override
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
updatePhoneSyncBgsCapability(capabilityInfo);
Log.d(TAG, logPrefix + "onConnected onCapabilityChanged mPhoneNodeID:" + mPhoneNodeId
+ ", Capability: " + capabilityInfo);
}
};
Wearable.CapabilityApi.addCapabilityListener(googleApiClient, capabilityListener, CAPABILITY_PHONE_APP);
Wearable.ChannelApi.addListener(googleApiClient, this);
requestData();
// requestData();
}
@Override
@ -779,28 +626,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
}
private void setLocalNodeName() {
forceGoogleApiConnect();
PendingResult<NodeApi.GetLocalNodeResult> result = Wearable.NodeApi.getLocalNode(googleApiClient);
result.setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>() {
@Override
public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
if (!getLocalNodeResult.getStatus().isSuccess()) {
Log.e(TAG, "ERROR: failed to getLocalNode Status="
+ getLocalNodeResult.getStatus().getStatusMessage());
} else {
Log.d(TAG, "getLocalNode Status=: " + getLocalNodeResult.getStatus().getStatusMessage());
Node getnode = getLocalNodeResult.getNode();
localnode = getnode != null ? getnode.getDisplayName() + "|" + getnode.getId() : "";
Log.d(TAG, "setLocalNodeName. localnode=" + localnode);
}
}
});
}
@Override
public void onDestroy() {
super.onDestroy();

View file

@ -5,6 +5,8 @@ import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import info.nightscout.androidaps.R;
import preference.WearPreferenceActivity;
@ -14,21 +16,15 @@ public class AAPSPreferences extends WearPreferenceActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
ViewGroup view = (ViewGroup)getWindow().getDecorView();
ViewGroup view = (ViewGroup) getWindow().getDecorView();
removeBackgroundRecursively(view);
view.setBackground(getResources().getDrawable(R.drawable.settings_background));
view.setBackground(ContextCompat.getDrawable(this, R.drawable.settings_background));
view.requestFocus();
}
@Override
protected void onPause(){
super.onPause();
finish();
}
void removeBackgroundRecursively(View parent) {
if (parent instanceof ViewGroup) {
ViewGroup group = (ViewGroup)parent;
ViewGroup group = (ViewGroup) parent;
for (int i = 0; i < group.getChildCount(); i++) {
removeBackgroundRecursively(group.getChildAt(i));
}
@ -36,5 +32,4 @@ public class AAPSPreferences extends WearPreferenceActivity {
parent.setBackground(null);
}
}
}

View file

@ -5,6 +5,9 @@ import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import info.nightscout.androidaps.R;
import preference.WearPreferenceActivity;
@ -14,14 +17,14 @@ public class ConfigurationActivity extends WearPreferenceActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("Watchface");
String configFileName=getIntent().getAction();
String configFileName = getIntent().getAction();
int resXmlId = getResources().getIdentifier(configFileName, "xml", getApplicationContext().getPackageName());
Log.d("ConfigurationActivity::onCreate --->> getIntent().getAction()",configFileName);
Log.d("ConfigurationActivity::onCreate --->> resXmlId",String.valueOf(resXmlId));
Log.d("ConfigurationActivity::onCreate --->> getIntent().getAction()", configFileName);
Log.d("ConfigurationActivity::onCreate --->> resXmlId", String.valueOf(resXmlId));
addPreferencesFromResource(resXmlId);
ViewGroup view = (ViewGroup) getWindow().getDecorView();
removeBackgroundRecursively(view);
view.setBackground(getResources().getDrawable(R.drawable.settings_background));
view.setBackground(ContextCompat.getDrawable(this, R.drawable.settings_background));
view.requestFocus();
}
@ -41,5 +44,4 @@ public class ConfigurationActivity extends WearPreferenceActivity {
parent.setBackground(null);
}
}

View file

@ -0,0 +1,44 @@
package info.nightscout.androidaps.interaction
import android.os.Bundle
import android.util.Log
import android.view.ViewGroup
import androidx.wear.tiles.TileService
import preference.WearPreferenceActivity
import info.nightscout.androidaps.tile.ActionsTileService
import info.nightscout.androidaps.tile.TempTargetTileService
var TAG = "ASTAG-config"
class TileConfigurationActivity : WearPreferenceActivity() {
private var configFileName: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
title = "Tile"
configFileName = intent.action
val resXmlId = resources.getIdentifier(configFileName, "xml", applicationContext.packageName)
Log.d("ConfigurationActivity::onCreate --->> getIntent().getAction()", configFileName!!)
Log.d("ConfigurationActivity::onCreate --->> resXmlId", resXmlId.toString())
addPreferencesFromResource(resXmlId)
val view = window.decorView as ViewGroup
view.requestFocus()
}
override fun onDestroy() {
super.onDestroy()
// Note that TileService updates are hard limited to once every 20 seconds.
if (configFileName === "tile_configuration_activity") {
Log.i(TAG, "onDestroy a: requestUpdate!!")
TileService.getUpdater(this)
.requestUpdate(ActionsTileService::class.java)
} else if (configFileName === "tile_configuration_tempt") {
Log.i(TAG, "onDestroy tt: requestUpdate!!")
TileService.getUpdater(this)
.requestUpdate(TempTargetTileService::class.java)
} else {
Log.i(TAG, "onDestroy : NO tile service available for $configFileName")
}
}
}

View file

@ -103,12 +103,9 @@ public class AcceptActivity extends ViewSelectorActivity {
} else {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ListenerService.confirmAction(AcceptActivity.this, actionstring);
finishAffinity();
}
confirmbutton.setOnClickListener((View v) -> {
ListenerService.confirmAction(AcceptActivity.this, actionstring);
finishAffinity();
});
container.addView(view);
return view;

View file

@ -0,0 +1,29 @@
package info.nightscout.androidaps.interaction.actions
import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import info.nightscout.androidaps.data.ListenerService
const val TAG = "QuickWizard"
class BackgroundActionActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val actionString = intent.extras?.getString("actionString")
Log.i(TAG, "QuickWizardActivity.onCreate: actionString=$actionString")
if (actionString != null) {
ListenerService.initiateAction(this, actionString)
val message = intent.extras?.getString("message")
if (message != null) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
} else {
Log.e(TAG, "BackgroundActionActivity.onCreate extras 'actionString' required")
}
finishAffinity()
}
}

View file

@ -1,6 +1,8 @@
package info.nightscout.androidaps.interaction.actions;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
@ -14,19 +16,17 @@ import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
/**
* Created by adrian on 09/02/17.
*/
public class BolusActivity extends ViewSelectorActivity {
PlusMinusEditText editCarbs;
PlusMinusEditText editInsulin;
float maxBolus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
maxBolus = sp.getFloat(getString(R.string.key_treatmentssafety_maxbolus), 3f);
}
@Override
@ -35,11 +35,10 @@ public class BolusActivity extends ViewSelectorActivity {
finish();
}
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
return 3;
return 2;
}
@Override
@ -56,35 +55,20 @@ public class BolusActivity extends ViewSelectorActivity {
if (editInsulin != null) {
def = SafeParse.stringToDouble(editInsulin.editText.getText().toString());
}
editInsulin = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, 30d, 0.1d, new DecimalFormat("#0.0"), false);
editInsulin = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double)maxBolus, 0.1d, new DecimalFormat("#0.0"),false);
setLabelToPlusMinusView(view, getString(R.string.action_insulin));
container.addView(view);
view.requestFocus();
return view;
} else if (col == 1) {
final View view = getInflatedPlusMinusView(container);
double def = 0;
if (editCarbs != null) {
def = SafeParse.stringToDouble(editCarbs.editText.getText().toString());
}
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, 150d, 1d, new DecimalFormat("0"), false);
setLabelToPlusMinusView(view, getString(R.string.action_carbs));
container.addView(view);
return view;
} else {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "bolus " + SafeParse.stringToDouble(editInsulin.editText.getText().toString())
+ " " + SafeParse.stringToInt(editCarbs.editText.getText().toString());
ListenerService.initiateAction(BolusActivity.this, actionstring);
finishAffinity();
}
confirmbutton.setOnClickListener((View v) -> {
String actionstring = "bolus " + SafeParse.stringToDouble(editInsulin.editText.getText().toString())
+ " 0"; // Zero carbs
ListenerService.initiateAction(BolusActivity.this, actionstring);
confirmAction(BolusActivity.this, R.string.action_bolus_confirmation);
finishAffinity();
});
container.addView(view);
return view;
@ -93,8 +77,6 @@ public class BolusActivity extends ViewSelectorActivity {
@Override
public void destroyItem(ViewGroup container, int row, int col, Object view) {
// Handle this to get the data before the view is destroyed?
// Object should still be kept by this, just setup for reinit?
container.removeView((View) view);
}

View file

@ -89,18 +89,15 @@ public class CPPActivity extends ViewSelectorActivity {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
confirmbutton.setOnClickListener((View v) -> {
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "cppset " + SafeParse.stringToInt(editTimeshift.editText.getText().toString())
+ " " + SafeParse.stringToInt(editPercentage.editText.getText().toString());
ListenerService.initiateAction(CPPActivity.this, actionstring);
finishAffinity();
}
String actionstring = "cppset " + SafeParse.stringToInt(editTimeshift.editText.getText().toString())
+ " " + SafeParse.stringToInt(editPercentage.editText.getText().toString());
ListenerService.initiateAction(CPPActivity.this, actionstring);
confirmAction(CPPActivity.this, R.string.action_cpp_confirmation);
finishAffinity();
});
container.addView(view);
return view;

View file

@ -1,6 +1,8 @@
package info.nightscout.androidaps.interaction.actions;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
@ -23,11 +25,14 @@ public class ECarbActivity extends ViewSelectorActivity {
PlusMinusEditText editCarbs;
PlusMinusEditText editStartTime;
PlusMinusEditText editDuration;
int maxCarbs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
maxCarbs = sp.getInt(getString(R.string.key_treatmentssafety_maxcarbs), 48);
}
@Override
@ -56,7 +61,7 @@ public class ECarbActivity extends ViewSelectorActivity {
if (editCarbs != null) {
def = SafeParse.stringToDouble(editCarbs.editText.getText().toString());
}
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, 150d, 1d, new DecimalFormat("0"), true);
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double)maxCarbs, 1d, new DecimalFormat("0"), true);
setLabelToPlusMinusView(view, getString(R.string.action_carbs));
container.addView(view);
view.requestFocus();
@ -85,19 +90,18 @@ public class ECarbActivity extends ViewSelectorActivity {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
confirmbutton.setOnClickListener((View v) -> {
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "ecarbs " + SafeParse.stringToInt(editCarbs.editText.getText().toString())
+ " " + SafeParse.stringToInt(editStartTime.editText.getText().toString())
+ " " + SafeParse.stringToInt(editDuration.editText.getText().toString());
ListenerService.initiateAction(ECarbActivity.this, actionstring);
confirmAction(ECarbActivity.this, R.string.action_ecarb_confirmation);
finishAffinity();
String actionstring = "ecarbs " + SafeParse.stringToInt(editCarbs.editText.getText().toString())
+ " " + SafeParse.stringToInt(editStartTime.editText.getText().toString())
+ " " + SafeParse.stringToInt(editDuration.editText.getText().toString());
ListenerService.initiateAction(ECarbActivity.this, actionstring);
finishAffinity();
}
});
container.addView(view);
return view;

View file

@ -63,17 +63,14 @@ public class FillActivity extends ViewSelectorActivity {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
confirmbutton.setOnClickListener((View v) -> {
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "fill " + SafeParse.stringToDouble(editInsulin.editText.getText().toString());
ListenerService.initiateAction(FillActivity.this, actionstring);
confirmAction(FillActivity.this, R.string.action_fill_confirmation);
finishAffinity();
}
});
container.addView(view);
return view;

View file

@ -8,7 +8,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.text.DecimalFormat;
@ -32,6 +31,7 @@ public class TempTargetActivity extends ViewSelectorActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
@ -61,8 +61,6 @@ public class TempTargetActivity extends ViewSelectorActivity {
if (col == 0) {
final View view = getInflatedPlusMinusView(container);
final TextView textView = view.findViewById(R.id.label);
textView.setText("duration");
if (time == null) {
time = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, 60d, 0d, 24 * 60d, 5d, new DecimalFormat("0"), false);
} else {
@ -118,22 +116,19 @@ public class TempTargetActivity extends ViewSelectorActivity {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
confirmbutton.setOnClickListener((View v) -> {
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "temptarget"
+ " " + isMGDL
+ " " + SafeParse.stringToInt(time.editText.getText().toString())
+ " " + SafeParse.stringToDouble(lowRange.editText.getText().toString())
+ " " + (isSingleTarget ? SafeParse.stringToDouble(lowRange.editText.getText().toString()) : SafeParse.stringToDouble(highRange.editText.getText().toString()));
String actionstring = "temptarget "
+ " " + isMGDL
+ " " + SafeParse.stringToInt(time.editText.getText().toString())
+ " " + SafeParse.stringToDouble(lowRange.editText.getText().toString())
+ " " + (isSingleTarget ? SafeParse.stringToDouble(lowRange.editText.getText().toString()) : SafeParse.stringToDouble(highRange.editText.getText().toString()));
ListenerService.initiateAction(TempTargetActivity.this, actionstring);
finishAffinity();
}
ListenerService.initiateAction(TempTargetActivity.this, actionstring);
confirmAction(TempTargetActivity.this, R.string.action_tempt_confirmation);
finishAffinity();
});
container.addView(view);
return view;

View file

@ -0,0 +1,112 @@
package info.nightscout.androidaps.interaction.actions;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
/**
* Created by adrian on 09/02/17.
*/
public class TreatmentActivity extends ViewSelectorActivity {
PlusMinusEditText editCarbs;
PlusMinusEditText editInsulin;
int maxCarbs;
float maxBolus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
maxCarbs = sp.getInt(getString(R.string.key_treatmentssafety_maxcarbs), 48);
maxBolus = sp.getFloat(getString(R.string.key_treatmentssafety_maxbolus), 3f);
}
@Override
protected void onPause() {
super.onPause();
finish();
}
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
return 3;
}
@Override
public int getRowCount() {
return 1;
}
@Override
public Object instantiateItem(ViewGroup container, int row, int col) {
if (col == 0) {
final View view = getInflatedPlusMinusView(container);
double def = 0;
if (editInsulin != null) {
def = SafeParse.stringToDouble(editInsulin.editText.getText().toString());
}
editInsulin = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double) maxBolus, 0.1d, new DecimalFormat("#0.0"),false);
setLabelToPlusMinusView(view, getString(R.string.action_insulin));
container.addView(view);
view.requestFocus();
return view;
} else if (col == 1) {
final View view = getInflatedPlusMinusView(container);
double def = 0;
if (editCarbs != null) {
def = SafeParse.stringToDouble(editCarbs.editText.getText().toString());
}
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double)maxCarbs, 1d, new DecimalFormat("0"),false);
setLabelToPlusMinusView(view, getString(R.string.action_carbs));
container.addView(view);
return view;
} else {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener((View v) -> {
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "bolus " + SafeParse.stringToDouble(editInsulin.editText.getText().toString())
+ " " + SafeParse.stringToInt(editCarbs.editText.getText().toString());
ListenerService.initiateAction(TreatmentActivity.this, actionstring);
confirmAction(TreatmentActivity.this, R.string.action_treatment_confirmation);
finishAffinity();
});
container.addView(view);
return view;
}
}
@Override
public void destroyItem(ViewGroup container, int row, int col, Object view) {
// Handle this to get the data before the view is destroyed?
// Object should still be kept by this, just setup for reinit?
container.removeView((View) view);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
}

View file

@ -1,6 +1,7 @@
package info.nightscout.androidaps.interaction.actions;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
@ -11,6 +12,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.wear.widget.CurvedTextView;
@ -62,9 +64,9 @@ public class ViewSelectorActivity extends Activity {
private void setTitleBasedOnScreenShape() {
// intents can inject dynamic titles, otherwise we'll use the default
String title = String.valueOf(getTitle());
if (getIntent().getExtras() != null)
if (getIntent().getExtras() != null) {
title = getIntent().getExtras().getString("title", title);
}
CurvedTextView titleViewCurved = findViewById(R.id.title_curved);
TextView titleView = findViewById(R.id.title);
if (this.getResources().getConfiguration().isScreenRound()) {
@ -96,8 +98,12 @@ public class ViewSelectorActivity extends Activity {
}
void setLabelToPlusMinusView(View view, String labelText) {
final TextView textView = view.findViewById(R.id.label);
textView.setText(labelText);
final TextView textView = view.findViewById(R.id.label);
textView.setText(labelText);
}
void confirmAction(Context context, int text) {
Toast.makeText(context, getString(text), Toast.LENGTH_LONG).show();
}
}

View file

@ -26,6 +26,8 @@ public class WizardActivity extends ViewSelectorActivity {
PlusMinusEditText editPercentage;
boolean hasPercentage;
int percentage;
int maxCarbs;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -33,6 +35,8 @@ public class WizardActivity extends ViewSelectorActivity {
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
hasPercentage = sp.getBoolean("wizardpercentage", false);
percentage = sp.getInt(getString(R.string.key_boluswizard_percentage), 100);
maxCarbs = sp.getInt(getString(R.string.key_treatmentssafety_maxcarbs), 48);
}
@Override
@ -58,10 +62,10 @@ public class WizardActivity extends ViewSelectorActivity {
if (col == 0) {
final View view = getInflatedPlusMinusView(container);
if (editCarbs == null) {
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, 0d, 0d, 150d, 1d, new DecimalFormat("0"), false);
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, 0d, 0d, (double)maxCarbs, 1d, new DecimalFormat("0"), false);
} else {
double def = SafeParse.stringToDouble(editCarbs.editText.getText().toString());
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, 150d, 1d, new DecimalFormat("0"), false);
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double)maxCarbs, 1d, new DecimalFormat("0"),false);
}
setLabelToPlusMinusView(view, getString(R.string.action_carbs));
container.addView(view);
@ -70,7 +74,7 @@ public class WizardActivity extends ViewSelectorActivity {
} else if (col == 1 && hasPercentage) {
final View view = getInflatedPlusMinusView(container);
if (editPercentage == null) {
editPercentage = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, 100d, 50d, 150d, 1d, new DecimalFormat("0"), false);
editPercentage = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, (double)percentage, 50d, 150d, 1d, new DecimalFormat("0"), false);
} else {
double def = SafeParse.stringToDouble(editPercentage.editText.getText().toString());
editPercentage = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 50d, 150d, 1d, new DecimalFormat("0"), false);
@ -82,23 +86,16 @@ public class WizardActivity extends ViewSelectorActivity {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// check if it can happen that the fragment is never created that hold data?
// (you have to swipe past them anyways - but still)
int percentage = 100;
if (editPercentage != null)
percentage = SafeParse.stringToInt(editPercentage.editText.getText().toString());
String actionstring = "wizard2 " + SafeParse.stringToInt(editCarbs.editText.getText().toString())
+ " " + percentage;
ListenerService.initiateAction(WizardActivity.this, actionstring);
finishAffinity();
confirmbutton.setOnClickListener((View v) -> {
if (editPercentage != null) {
percentage = SafeParse.stringToInt(editPercentage.editText.getText().toString());
}
String actionstring = "wizard2 " + SafeParse.stringToInt(editCarbs.editText.getText().toString())
+ " " + percentage;
ListenerService.initiateAction(WizardActivity.this, actionstring);
confirmAction(WizardActivity.this, R.string.action_wizard_confirmation);
finishAffinity();
});
container.addView(view);
return view;

View file

@ -11,7 +11,7 @@ import java.util.List;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.interaction.AAPSPreferences;
import info.nightscout.androidaps.interaction.actions.BolusActivity;
import info.nightscout.androidaps.interaction.actions.TreatmentActivity;
import info.nightscout.androidaps.interaction.actions.ECarbActivity;
import info.nightscout.androidaps.interaction.actions.TempTargetActivity;
import info.nightscout.androidaps.interaction.actions.WizardActivity;
@ -49,7 +49,7 @@ public class MainMenuActivity extends MenuListActivity {
if (showWizard) menuItems.add(new MenuItem(R.drawable.ic_calculator, getString(R.string.menu_wizard)));
menuItems.add(new MenuItem(R.drawable.ic_e_carbs, getString(R.string.menu_ecarb)));
menuItems.add(new MenuItem(R.drawable.ic_bolus, getString(R.string.menu_bolus)));
menuItems.add(new MenuItem(R.drawable.ic_treatment, getString(R.string.menu_treatment)));
menuItems.add(new MenuItem(R.drawable.ic_temptarget, getString(R.string.menu_tempt)));
menuItems.add(new MenuItem(R.drawable.ic_settings, getString(R.string.menu_settings)));
menuItems.add(new MenuItem(R.drawable.ic_status, getString(R.string.menu_status)));
@ -73,8 +73,8 @@ public class MainMenuActivity extends MenuListActivity {
intent = new Intent(this, TempTargetActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
} else if (getString(R.string.menu_bolus).equals(action)) {
intent = new Intent(this, BolusActivity.class);
} else if (getString(R.string.menu_treatment).equals(action)) {
intent = new Intent(this, TreatmentActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
} else if (getString(R.string.menu_wizard).equals(action)) {

View file

@ -32,7 +32,7 @@ public class PlusMinusEditText implements View.OnKeyListener,
Double minValue = 0d;
Double maxValue = 1d;
Double step = 1d;
NumberFormat formater;
NumberFormat formatter;
boolean allowZero = false;
boolean roundRobin;
@ -74,11 +74,11 @@ public class PlusMinusEditText implements View.OnKeyListener,
private static final int MSG_INC = 0;
private static final int MSG_DEC = 1;
public PlusMinusEditText(View view, int editTextID, int plusID, int minusID, Double initValue, Double minValue, Double maxValue, Double step, NumberFormat formater, boolean allowZero) {
this(view, editTextID, plusID, minusID, initValue, minValue, maxValue, step, formater, allowZero, false);
public PlusMinusEditText(View view, int editTextID, int plusID, int minusID, Double initValue, Double minValue, Double maxValue, Double step, NumberFormat formatter, boolean allowZero) {
this(view, editTextID, plusID, minusID, initValue, minValue, maxValue, step, formatter, allowZero, false);
}
public PlusMinusEditText(View view, int editTextID, int plusID, int minusID, Double initValue, Double minValue, Double maxValue, Double step, NumberFormat formater, boolean allowZero, boolean roundRobin) {
public PlusMinusEditText(View view, int editTextID, int plusID, int minusID, Double initValue, Double minValue, Double maxValue, Double step, NumberFormat formatter, boolean allowZero, boolean roundRobin) {
editText = view.findViewById(editTextID);
minusImage = view.findViewById(minusID);
plusImage = view.findViewById(plusID);
@ -87,7 +87,7 @@ public class PlusMinusEditText implements View.OnKeyListener,
this.minValue = minValue;
this.maxValue = maxValue;
this.step = step;
this.formater = formater;
this.formatter = formatter;
this.allowZero = allowZero;
this.roundRobin = roundRobin;
@ -159,7 +159,7 @@ public class PlusMinusEditText implements View.OnKeyListener,
if (value == 0d && !allowZero)
editText.setText("");
else
editText.setText(formater.format(value));
editText.setText(formatter.format(value));
}
private void startUpdating(boolean inc) {

View file

@ -0,0 +1,63 @@
package info.nightscout.androidaps.tile
import android.content.res.Resources
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interaction.actions.BolusActivity
import info.nightscout.androidaps.interaction.actions.TreatmentActivity
import info.nightscout.androidaps.interaction.actions.ECarbActivity
import info.nightscout.androidaps.interaction.actions.TempTargetActivity
import info.nightscout.androidaps.interaction.actions.WizardActivity
object ActionSource : StaticTileSource() {
override val preferencePrefix = "tile_action_"
override fun getActions(resources: Resources): List<StaticAction> {
return listOf(
StaticAction(
settingName = "wizard",
buttonText = resources.getString(R.string.menu_wizard_short),
iconRes = R.drawable.ic_calculator_green,
activityClass = WizardActivity::class.java.name,
),
StaticAction(
settingName = "treatment",
buttonText = resources.getString(R.string.menu_treatment_short),
iconRes = R.drawable.ic_bolus_carbs,
activityClass = TreatmentActivity::class.java.name,
),
StaticAction(
settingName = "bolus",
buttonText = resources.getString(R.string.action_insulin),
iconRes = R.drawable.ic_bolus,
activityClass = BolusActivity::class.java.name,
),
StaticAction(
settingName = "carbs",
buttonText = resources.getString(R.string.action_carbs),
iconRes = R.drawable.ic_carbs_orange,
activityClass = ECarbActivity::class.java.name,
),
StaticAction(
settingName = "temp_target",
buttonText = resources.getString(R.string.menu_tempt),
iconRes = R.drawable.ic_temptarget_flat,
activityClass = TempTargetActivity::class.java.name,
)
)
}
override fun getResourceReferences(resources: Resources): List<Int> {
return getActions(resources).map { it.iconRes }
}
override fun getDefaultConfig(): Map<String, String> {
return mapOf(
"tile_action_1" to "wizard",
"tile_action_2" to "treatment",
"tile_action_3" to "carbs",
"tile_action_4" to "temp_target"
)
}
}

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.tile
class ActionsTileService : TileBase() {
override val resourceVersion = "ActionsTileService"
override val source = ActionSource
}

View file

@ -0,0 +1,108 @@
package info.nightscout.androidaps.tile
import android.content.Context
import android.content.res.Resources
import android.util.Base64
import android.util.Log
import androidx.preference.PreferenceManager
import com.google.android.gms.wearable.DataMap
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interaction.actions.BackgroundActionActivity
import java.util.*
object QuickWizardSource : TileSource {
override fun getSelectedActions(context: Context): List<Action> {
val quickList = mutableListOf<Action>()
val quickMap = getDataMap(context)
val sfm = secondsFromMidnight()
for (quick in quickMap) {
val validFrom = quick.getInt("from", 0)
val validTo = quick.getInt("to", 0)
val isActive = sfm in validFrom..validTo
val guid = quick.getString("guid", "")
if (isActive && guid != "") {
quickList.add(
Action(
buttonText = quick.getString("button_text", "?"),
buttonTextSub = "${quick.getInt("carbs", 0)} g",
iconRes = R.drawable.ic_quick_wizard,
activityClass = BackgroundActionActivity::class.java.name,
actionString = "quick_wizard $guid",
message = context.resources.getString(R.string.action_quick_wizard_confirmation),
)
)
Log.i(TAG, "getSelectedActions: active " + quick.getString("button_text", "?") + " guid=" + guid)
} else {
Log.i(TAG, "getSelectedActions: not active " + quick.getString("button_text", "?") + " guid=" + guid)
}
}
return quickList
}
override fun getValidFor(context: Context): Long? {
val quickMap = getDataMap(context)
if (quickMap.size == 0) {
return null
}
val sfm = secondsFromMidnight()
var validTill = 24 * 60 * 60
for (quick in quickMap) {
val validFrom = quick.getInt("from", 0)
val validTo = quick.getInt("to", 0)
val isActive = sfm in validFrom..validTo
val guid = quick.getString("guid", "")
Log.i(TAG, "valid: " + validFrom + "-" + validTo)
if (guid != "") {
if (isActive && validTill > validTo) {
validTill = validTo
}
if (validFrom > sfm && validTill > validFrom) {
validTill = validFrom
}
}
}
val validWithin = 60
val delta = (validTill - sfm + validWithin) * 1000L
Log.i(TAG, "getValidTill: sfm" + sfm + " till" + validTill + " d=" + delta)
return delta
}
private fun getDataMap(context: Context): ArrayList<DataMap> {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
val key = context.resources.getString(R.string.key_quick_wizard_data_map)
if (sharedPrefs.contains(key)) {
val rawB64Data: String? = sharedPrefs.getString(key, null)
val rawData: ByteArray = Base64.decode(rawB64Data, Base64.DEFAULT)
try {
val map = DataMap.fromByteArray(rawData)
return map.getDataMapArrayList("quick_wizard")
} catch (ex: IllegalArgumentException) {
Log.e(TAG, "getSelectedActions: IllegalArgumentException ", ex)
}
}
return arrayListOf()
}
private fun secondsFromMidnight(): Int {
val c = Calendar.getInstance()
c.set(Calendar.HOUR_OF_DAY, 0)
c.set(Calendar.MINUTE, 0)
c.set(Calendar.SECOND, 0)
c.set(Calendar.MILLISECOND, 0)
val passed: Long = System.currentTimeMillis() - c.timeInMillis
return (passed / 1000).toInt()
}
override fun getResourceReferences(resources: Resources): List<Int> {
return listOf(R.drawable.ic_quick_wizard)
}
}

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.tile
const val TAG = "QuickWizard"
class QuickWizardTileService : TileBase() {
override val resourceVersion = "QuickWizardTileService"
override val source = QuickWizardSource
}

View file

@ -0,0 +1,62 @@
package info.nightscout.androidaps.tile
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources
import androidx.annotation.DrawableRes
import androidx.preference.PreferenceManager
class StaticAction(
val settingName: String,
buttonText: String,
buttonTextSub: String? = null,
activityClass: String,
@DrawableRes iconRes: Int,
actionString: String? = null,
message: String? = null,
) : Action(buttonText, buttonTextSub, activityClass, iconRes, actionString, message)
abstract class StaticTileSource : TileSource {
abstract fun getActions(resources: Resources): List<StaticAction>
abstract val preferencePrefix: String
abstract fun getDefaultConfig(): Map<String, String>
override fun getSelectedActions(context: Context): List<Action> {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
setDefaultSettings(sharedPrefs)
val actionList: MutableList<Action> = mutableListOf()
for (i in 1..4) {
val action = getActionFromPreference(context.resources, sharedPrefs, i)
if (action != null) {
actionList.add(action)
}
}
if (actionList.isEmpty()) {
return getActions(context.resources).take(4)
}
return actionList
}
override fun getValidFor(context: Context): Long? = null
private fun getActionFromPreference(resources: Resources, sharedPrefs: SharedPreferences, index: Int): Action? {
val actionPref = sharedPrefs.getString(preferencePrefix + index, "none")
return getActions(resources).find { action -> action.settingName == actionPref }
}
private fun setDefaultSettings(sharedPrefs: SharedPreferences) {
val defaults = getDefaultConfig()
val firstKey = defaults.firstNotNullOf { settings -> settings.key }
if (!sharedPrefs.contains(firstKey)) {
val editor = sharedPrefs.edit()
for ((key, value) in defaults) {
editor.putString(key, value)
}
editor.apply()
}
}
}

View file

@ -0,0 +1,71 @@
package info.nightscout.androidaps.tile
import android.content.res.Resources
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interaction.actions.BackgroundActionActivity
import info.nightscout.androidaps.interaction.actions.TempTargetActivity
object TempTargetSource : StaticTileSource() {
override val preferencePrefix = "tile_tempt_"
override fun getActions(resources: Resources): List<StaticAction> {
val message = resources.getString(R.string.action_tempt_confirmation)
return listOf(
StaticAction(
settingName = "activity",
buttonText = resources.getString(R.string.temp_target_activity),
iconRes = R.drawable.ic_target_activity,
activityClass = BackgroundActionActivity::class.java.name,
message = message,
// actionString = "temptarget false 90 8.0 8.0",
actionString = "temptarget preset activity",
),
StaticAction(
settingName = "eating_soon",
buttonText = resources.getString(R.string.temp_target_eating_soon),
iconRes = R.drawable.ic_target_eatingsoon,
activityClass = BackgroundActionActivity::class.java.name,
message = message,
// actionString = "temptarget false 45 4.5 4.5",
actionString = "temptarget preset eating",
),
StaticAction(
settingName = "hypo",
buttonText = resources.getString(R.string.temp_target_hypo),
iconRes = R.drawable.ic_target_hypo,
activityClass = BackgroundActionActivity::class.java.name,
message = message,
// actionString = "temptarget false 45 7.0 7.0",
actionString = "temptarget preset hypo",
),
StaticAction(
settingName = "manual",
buttonText = resources.getString(R.string.temp_target_manual),
iconRes = R.drawable.ic_target_manual,
activityClass = TempTargetActivity::class.java.name,
),
StaticAction(
settingName = "cancel",
buttonText = resources.getString(R.string.generic_cancel),
iconRes = R.drawable.ic_target_cancel,
activityClass = BackgroundActionActivity::class.java.name,
message = message,
actionString = "temptarget cancel",
)
)
}
override fun getResourceReferences(resources: Resources): List<Int> {
return getActions(resources).map { it.iconRes }
}
override fun getDefaultConfig(): Map<String, String> {
return mapOf(
"tile_tempt_1" to "activity",
"tile_tempt_2" to "eating_soon",
"tile_tempt_3" to "hypo",
"tile_tempt_4" to "manual"
)
}
}

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.tile
class TempTargetTileService : TileBase() {
override val resourceVersion = "TempTargetTileService"
override val source = TempTargetSource;
}

View file

@ -0,0 +1,297 @@
package info.nightscout.androidaps.tile
import android.content.Context
import android.os.Build
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.wear.tiles.ActionBuilders
import androidx.wear.tiles.ColorBuilders.argb
import androidx.wear.tiles.DeviceParametersBuilders.SCREEN_SHAPE_ROUND
import androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters
import androidx.wear.tiles.DimensionBuilders.SpProp
import androidx.wear.tiles.DimensionBuilders.dp
import androidx.wear.tiles.DimensionBuilders.sp
import androidx.wear.tiles.LayoutElementBuilders.*
import androidx.wear.tiles.ModifiersBuilders.Background
import androidx.wear.tiles.ModifiersBuilders.Clickable
import androidx.wear.tiles.ModifiersBuilders.Corner
import androidx.wear.tiles.ModifiersBuilders.Modifiers
import androidx.wear.tiles.ModifiersBuilders.Semantics
import androidx.wear.tiles.RequestBuilders
import androidx.wear.tiles.RequestBuilders.ResourcesRequest
import androidx.wear.tiles.ResourceBuilders.AndroidImageResourceByResId
import androidx.wear.tiles.ResourceBuilders.ImageResource
import androidx.wear.tiles.ResourceBuilders.Resources
import androidx.wear.tiles.TileBuilders.Tile
import androidx.wear.tiles.TileService
import androidx.wear.tiles.TimelineBuilders.Timeline
import androidx.wear.tiles.TimelineBuilders.TimelineEntry
import com.google.common.util.concurrent.ListenableFuture
import info.nightscout.androidaps.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.guava.future
import kotlin.math.sqrt
private const val SPACING_ACTIONS = 3f
private const val ICON_SIZE_FRACTION = 0.4f // Percentage of button diameter
private const val BUTTON_COLOR = R.color.gray_850
private const val LARGE_SCREEN_WIDTH_DP = 210
interface TileSource {
fun getResourceReferences(resources: android.content.res.Resources): List<Int>
fun getSelectedActions(context: Context): List<Action>
fun getValidFor(context: Context): Long?
}
open class Action(
val buttonText: String,
val buttonTextSub: String? = null,
val activityClass: String,
@DrawableRes val iconRes: Int,
val actionString: String? = null,
val message: String? = null,
)
enum class WearControl {
NO_DATA, ENABLED, DISABLED
}
abstract class TileBase : TileService() {
abstract val resourceVersion: String
abstract val source: TileSource
private val serviceJob = Job()
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
override fun onTileRequest(
requestParams: RequestBuilders.TileRequest
): ListenableFuture<Tile> = serviceScope.future {
val actionsSelected = getSelectedActions()
val wearControl = getWearControl()
val tile = Tile.Builder()
.setResourcesVersion(resourceVersion)
.setTimeline(
Timeline.Builder().addTimelineEntry(
TimelineEntry.Builder().setLayout(
Layout.Builder().setRoot(layout(wearControl, actionsSelected, requestParams.deviceParameters!!)).build()
).build()
).build()
)
val validFor = validFor()
if (validFor != null) {
tile.setFreshnessIntervalMillis(validFor)
}
tile.build()
}
private fun getSelectedActions(): List<Action> {
// TODO check why thi scan not be don in scope of the coroutine
return source.getSelectedActions(this)
}
private fun validFor(): Long? {
return source.getValidFor(this)
}
@RequiresApi(Build.VERSION_CODES.N)
override fun onResourcesRequest(
requestParams: ResourcesRequest
): ListenableFuture<Resources> = serviceScope.future {
Resources.Builder()
.setVersion(resourceVersion)
.apply {
source.getResourceReferences(resources).forEach { resourceId ->
addIdToImageMapping(
resourceId.toString(),
ImageResource.Builder()
.setAndroidResourceByResId(
AndroidImageResourceByResId.Builder()
.setResourceId(resourceId)
.build()
)
.build()
)
}
}
.build()
}
private fun layout(wearControl: WearControl, actions: List<Action>, deviceParameters: DeviceParameters): LayoutElement {
if (wearControl == WearControl.DISABLED) {
return Text.Builder()
.setText(resources.getString(R.string.wear_control_not_enabled))
.build()
} else if (wearControl == WearControl.NO_DATA) {
return Text.Builder()
.setText(resources.getString(R.string.wear_control_no_data))
.build()
}
if (actions.isNotEmpty()) {
with(Column.Builder()) {
if (actions.size == 1 || actions.size == 3) {
addContent(addRowSingle(actions[0], deviceParameters))
}
if (actions.size == 4 || actions.size == 2) {
addContent(addRowDouble(actions[0], actions[1], deviceParameters))
}
if (actions.size == 3) {
addContent(addRowDouble(actions[1], actions[2], deviceParameters))
}
if (actions.size == 4) {
addContent(Spacer.Builder().setHeight(dp(SPACING_ACTIONS)).build())
addContent(addRowDouble(actions[2], actions[3], deviceParameters))
}
return build()
}
}
return Text.Builder()
.setText(resources.getString(R.string.tile_no_config))
.build()
}
private fun addRowSingle(action: Action, deviceParameters: DeviceParameters): LayoutElement =
Row.Builder()
.addContent(action(action, deviceParameters))
.build()
private fun addRowDouble(action1: Action, action2: Action, deviceParameters: DeviceParameters): LayoutElement =
Row.Builder()
.addContent(action(action1, deviceParameters))
.addContent(Spacer.Builder().setWidth(dp(SPACING_ACTIONS)).build())
.addContent(action(action2, deviceParameters))
.build()
private fun doAction(action: Action): ActionBuilders.Action {
val builder = ActionBuilders.AndroidActivity.Builder()
.setClassName(action.activityClass)
.setPackageName(this.packageName)
if (action.actionString != null) {
val actionString = ActionBuilders.AndroidStringExtra.Builder().setValue(action.actionString).build()
builder.addKeyToExtraMapping("actionString", actionString)
}
if (action.message != null) {
val message = ActionBuilders.AndroidStringExtra.Builder().setValue(action.message).build()
builder.addKeyToExtraMapping("message", message)
}
return ActionBuilders.LaunchAction.Builder()
.setAndroidActivity(builder.build())
.build()
}
private fun action(action: Action, deviceParameters: DeviceParameters): LayoutElement {
val circleDiameter = circleDiameter(deviceParameters)
val text = action.buttonText
val textSub = action.buttonTextSub
return Box.Builder()
.setWidth(dp(circleDiameter))
.setHeight(dp(circleDiameter))
.setModifiers(
Modifiers.Builder()
.setBackground(
Background.Builder()
.setColor(
argb(ContextCompat.getColor(baseContext, BUTTON_COLOR))
)
.setCorner(
Corner.Builder().setRadius(dp(circleDiameter / 2)).build()
)
.build()
)
.setSemantics(
Semantics.Builder()
.setContentDescription("$text $textSub")
.build()
)
.setClickable(
Clickable.Builder()
.setOnClick(doAction(action))
.build()
)
.build()
)
.addContent(addTextContent(action, deviceParameters))
.build()
}
private fun addTextContent(action: Action, deviceParameters: DeviceParameters): LayoutElement {
val circleDiameter = circleDiameter(deviceParameters)
val iconSize = dp(circleDiameter * ICON_SIZE_FRACTION)
val text = action.buttonText
val textSub = action.buttonTextSub
val col = Column.Builder()
.addContent(
Image.Builder()
.setWidth(iconSize)
.setHeight(iconSize)
.setResourceId(action.iconRes.toString())
.build()
).addContent(
Text.Builder()
.setText(text)
.setFontStyle(
FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
.setColor(
argb(ContextCompat.getColor(baseContext, R.color.white))
)
.setSize(buttonTextSize(deviceParameters, text))
.build()
)
.build()
)
if (textSub != null) {
col.addContent(
Text.Builder()
.setText(textSub)
.setFontStyle(
FontStyle.Builder()
.setColor(
argb(ContextCompat.getColor(baseContext, R.color.white))
)
.setSize(buttonTextSize(deviceParameters, textSub))
.build()
)
.build()
)
}
return col.build()
}
private fun circleDiameter(deviceParameters: DeviceParameters) = when (deviceParameters.screenShape) {
SCREEN_SHAPE_ROUND -> ((sqrt(2f) - 1) * deviceParameters.screenHeightDp) - (2 * SPACING_ACTIONS)
else -> 0.5f * deviceParameters.screenHeightDp - SPACING_ACTIONS
}
private fun buttonTextSize(deviceParameters: DeviceParameters, text: String): SpProp {
if (text.length > 6) {
return sp(if (isLargeScreen(deviceParameters)) 14f else 12f)
}
return sp(if (isLargeScreen(deviceParameters)) 16f else 14f)
}
private fun isLargeScreen(deviceParameters: DeviceParameters): Boolean {
return deviceParameters.screenWidthDp >= LARGE_SCREEN_WIDTH_DP
}
private fun getWearControl(): WearControl {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
if (!sharedPrefs.contains("wearcontrol")) {
return WearControl.NO_DATA
}
val wearControlPref = sharedPrefs.getBoolean("wearcontrol", false)
if (wearControlPref) {
return WearControl.ENABLED
}
return WearControl.DISABLED
}
}

View file

@ -71,6 +71,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
}
static IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
public final Point displaySize = new Point();
public TextView mTime, mHour, mMinute, mTimePeriod, mSgv, mDirection, mTimestamp, mUploaderBattery, mRigBattery, mDelta, mAvgDelta, mStatus, mBasalRate, mIOB1, mIOB2, mCOB1, mCOB2, mBgi, mLoop, mDay, mDayName, mMonth, isAAPSv2, mHighLight, mLowLight;
public ImageView mGlucoseDial, mDeltaGauge, mHourHand, mMinuteHand;
@ -111,7 +113,6 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
private Date mDateTime;
private String mLastSvg = "", mLastDirection = "";
private float mYOffset = 0;
private Intent mBatteryStatus;
@Override
public void onCreate() {
@ -145,10 +146,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
private void setupBatteryReceiver() {
IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
mBatteryStatus = this.registerReceiver(null, iFilter);
String setting = sharedPrefs.getString("simplify_ui", "off");
if (setting.equals("charging") || setting.equals("ambient_charging") && batteryReceiver == null) {
if ((setting.equals("charging") || setting.equals("ambient_charging")) && batteryReceiver == null) {
IntentFilter intentBatteryFilter = new IntentFilter();
intentBatteryFilter.addAction(BatteryManager.ACTION_CHARGING);
intentBatteryFilter.addAction(BatteryManager.ACTION_DISCHARGING);
@ -374,6 +373,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
private boolean isCharging() {
Intent mBatteryStatus = this.registerReceiver(null, iFilter);
int status = mBatteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
return status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
@ -658,7 +658,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
private boolean isLowRes(WatchMode watchMode) {
return (watchMode == WatchMode.LOW_BIT) || (watchMode == WatchMode.LOW_BIT_BURN_IN); // || (watchMode == WatchMode.LOW_BIT_BURN_IN);
return watchMode == WatchMode.LOW_BIT || watchMode == WatchMode.LOW_BIT_BURN_IN;
}
private boolean isSimpleUi() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Some files were not shown because too many files have changed in this diff Show more