diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt index eb56d8067e..065a2f1fc3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt @@ -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() { + @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() } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt index bfe47b2bdf..e7abb4690d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt @@ -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() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt index f463a10339..124f00e4ac 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt @@ -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 } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java index 1ce5c509bb..95cf6128a2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java @@ -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 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) { diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt index 5a66cd2510..de30b2f565 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt @@ -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) } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt index a254831690..b48d44c9cc 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt @@ -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() } + } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt index 79ff43bf66..c6dee8a660 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt @@ -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) -} \ No newline at end of file + + fun usePercentage(): Int = safeGetInt(storage, "usePercentage", DEFAULT) + + fun percentage(): Int = safeGetInt(storage, "percentage", 100) +} diff --git a/app/src/main/res/layout/overview_editquickwizard_dialog.xml b/app/src/main/res/layout/overview_editquickwizard_dialog.xml index 422b787269..b032ddebb9 100644 --- a/app/src/main/res/layout/overview_editquickwizard_dialog.xml +++ b/app/src/main/res/layout/overview_editquickwizard_dialog.xml @@ -108,6 +108,35 @@ + + + + + + + + + + + - + + + + + + + + + + + + + + android:layout_height="match_parent" + android:orientation="horizontal"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + - + + + + + + + android:adjustViewBounds="false" + android:cropToPadding="false" + android:paddingRight="10dp" + android:scaleType="fitStart" + card_view:srcCompat="@drawable/ic_smartphone" /> - - + 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" /> @@ -88,9 +115,9 @@ android:textStyle="normal|bold" /> + android:layout_height="wrap_content" + android:text="-" /> Negative only COB calculation Temporary target calculation + Percentage calculation Loop enabled APS selected NSClient has write permission @@ -1165,4 +1166,38 @@ blood glucose outdated + + Temptarget unknown preset: %1$s + Cancelling running Temp-Targets? + Different units used on watch and phone! + Zero-Temp-Target - cancelling running Temp-Targets? + Min-BG out of range! + Max-BG out of range! + Temptarget:\nMin: %1$s\nMax: %2$s\nDuration: %3$s + Temptarget:\nTarget: %1$s\nDuration: %2$s + Temptarget:\Reason: %1$s\nTarget: %2$s\nDuration: %3$s + QuickWizard: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg + Calc. Wizard:\nInsulin: %1$.2fU\nCarbs: %2$dg + Show entry on device: + Selected quickwizard no longer available, please refresh your tile + No recent BG to base calculation on! + No active profile set! + Unknown COB! BG reading missing or recent app restart? + Carb constraint violation! + Calc (IC: %2$.1f, ISF: %2$.1f) from:" + Carbs: %1$.2fU + COB: %1$.0fg %2$.2fU + BG: %1$.2fU + Basal IOB: %1$.2fU + Bolus IOB: %1$.2fU + Superbolus: %1$.2fU + 15\' trend: %1$.2fU + Percentage: %1$.2fU x %2$d%% ≈ %3$.2fU + Insulin constraint violation!\nCannot deliver %1$.2fU + TempT: %1$s + %1$s to %2$s + No pump available! + Unknown action command: + Percentage + Application default diff --git a/core/src/main/res/drawable/ic_bolus.xml b/core/src/main/res/drawable/ic_bolus.xml index 752e99ce35..6bbe19f3fc 100644 --- a/core/src/main/res/drawable/ic_bolus.xml +++ b/core/src/main/res/drawable/ic_bolus.xml @@ -1,8 +1,9 @@ - - + android:viewportHeight="24"> + diff --git a/core/src/main/res/drawable/ic_smartphone.xml b/core/src/main/res/drawable/ic_smartphone.xml new file mode 100644 index 0000000000..3d7f349a29 --- /dev/null +++ b/core/src/main/res/drawable/ic_smartphone.xml @@ -0,0 +1,5 @@ + + + diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index e0c2c022a0..76428aa88f 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -27,6 +27,7 @@ virtualpump_type QuickWizard wearcontrol + units_mgdl show_notes_entry_dialogs autosens_max autosens_min diff --git a/wear/build.gradle b/wear/build.gradle index bb1104bd2e..02458d4726 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -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.0" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testImplementation "junit:junit:$junit_version" diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index eb12cc4f02..224b6e59f6 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -8,6 +9,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:exported="true" + android:label="@string/action_insulin" /> @@ -542,14 +602,21 @@ android:launchMode="singleInstance" /> + + @@ -567,5 +634,16 @@ + + + + + + + + + + + diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java index 444b7702f0..158ad0f3e1 100644 --- a/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java +++ b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java @@ -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); diff --git a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java index 6661a16c2e..eebd92eaae 100644 --- a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java +++ b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java @@ -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 { - 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 { 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 { + 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 connectedNodes = capabilityInfo.getNodes(); - return pickBestNode(connectedNodes); - // mPhoneNodeId = pickBestNodeId(connectedNodes); - } - - - private Node pickBestNode(Set 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 result = Wearable.NodeApi.getLocalNode(googleApiClient); - result.setResultCallback(new ResultCallback() { - - @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(); diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/AAPSPreferences.java b/wear/src/main/java/info/nightscout/androidaps/interaction/AAPSPreferences.java index ccdaeff7dc..72f9f0a968 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/AAPSPreferences.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/AAPSPreferences.java @@ -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); } - -} \ No newline at end of file +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/ConfigurationActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/ConfigurationActivity.java index 8348dfcb22..e01a31216c 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/ConfigurationActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/ConfigurationActivity.java @@ -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); } - } diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/TileConfigurationActivity.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/TileConfigurationActivity.kt new file mode 100644 index 0000000000..0b73d7fa96 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/TileConfigurationActivity.kt @@ -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") + } + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/AcceptActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/AcceptActivity.java index 46ecf6be7a..1b3c1ba0f2 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/AcceptActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/AcceptActivity.java @@ -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; diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BackgroundActionActivity.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BackgroundActionActivity.kt new file mode 100644 index 0000000000..a7db92eea9 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BackgroundActionActivity.kt @@ -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() + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BolusActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BolusActivity.java index 97fe81fa6a..0814ac6a52 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BolusActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BolusActivity.java @@ -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); } diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/CPPActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/CPPActivity.java index 479e3945ed..d5c488f185 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/CPPActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/CPPActivity.java @@ -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; diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/ECarbActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/ECarbActivity.java index 379c9b33fe..426df4b839 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/ECarbActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/ECarbActivity.java @@ -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; diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/FillActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/FillActivity.java index a53fdb2ba5..b76829182e 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/FillActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/FillActivity.java @@ -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; diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TempTargetActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TempTargetActivity.java index 6d42a63964..df2db8fafa 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TempTargetActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TempTargetActivity.java @@ -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; diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TreatmentActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TreatmentActivity.java new file mode 100644 index 0000000000..7929dc2299 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TreatmentActivity.java @@ -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; + } + + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/ViewSelectorActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/ViewSelectorActivity.java index afb3dda6cc..068cca59f7 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/ViewSelectorActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/ViewSelectorActivity.java @@ -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(); } } diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/WizardActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/WizardActivity.java index e8ea60b0a4..0d7be17da4 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/WizardActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/WizardActivity.java @@ -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; diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/menus/MainMenuActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/menus/MainMenuActivity.java index 126f7a2ceb..fcc21cee0d 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/menus/MainMenuActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/menus/MainMenuActivity.java @@ -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)) { diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/PlusMinusEditText.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/PlusMinusEditText.java index b85f09a8c6..429537bf07 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/PlusMinusEditText.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/PlusMinusEditText.java @@ -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) { diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/ActionSource.kt b/wear/src/main/java/info/nightscout/androidaps/tile/ActionSource.kt new file mode 100644 index 0000000000..d9f87ad77f --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/ActionSource.kt @@ -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 { + 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 { + return getActions(resources).map { it.iconRes } + } + + override fun getDefaultConfig(): Map { + return mapOf( + "tile_action_1" to "wizard", + "tile_action_2" to "treatment", + "tile_action_3" to "carbs", + "tile_action_4" to "temp_target" + ) + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/ActionsTileService.kt b/wear/src/main/java/info/nightscout/androidaps/tile/ActionsTileService.kt new file mode 100644 index 0000000000..b30ec2a87a --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/ActionsTileService.kt @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.tile + +class ActionsTileService : TileBase() { + override val resourceVersion = "ActionsTileService" + override val source = ActionSource +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardSource.kt b/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardSource.kt new file mode 100644 index 0000000000..36e2f224fb --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardSource.kt @@ -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 { + val quickList = mutableListOf() + 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 { + 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 { + return listOf(R.drawable.ic_quick_wizard) + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardTileService.kt b/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardTileService.kt new file mode 100644 index 0000000000..ef85e789a7 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardTileService.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.tile + +const val TAG = "QuickWizard" + +class QuickWizardTileService : TileBase() { + override val resourceVersion = "QuickWizardTileService" + override val source = QuickWizardSource +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/StaticTileSource.kt b/wear/src/main/java/info/nightscout/androidaps/tile/StaticTileSource.kt new file mode 100644 index 0000000000..c1f369275c --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/StaticTileSource.kt @@ -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 + + abstract val preferencePrefix: String + abstract fun getDefaultConfig(): Map + + override fun getSelectedActions(context: Context): List { + val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context) + setDefaultSettings(sharedPrefs) + + val actionList: MutableList = 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() + } + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetSource.kt b/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetSource.kt new file mode 100644 index 0000000000..75cdbca36c --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetSource.kt @@ -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 { + 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 { + return getActions(resources).map { it.iconRes } + } + + override fun getDefaultConfig(): Map { + return mapOf( + "tile_tempt_1" to "activity", + "tile_tempt_2" to "eating_soon", + "tile_tempt_3" to "hypo", + "tile_tempt_4" to "manual" + ) + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetTileService.kt b/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetTileService.kt new file mode 100644 index 0000000000..6e0ff0e30f --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetTileService.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.tile + +class TempTargetTileService : TileBase() { + + override val resourceVersion = "TempTargetTileService" + override val source = TempTargetSource; + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/TileBase.kt b/wear/src/main/java/info/nightscout/androidaps/tile/TileBase.kt new file mode 100644 index 0000000000..031dbf223e --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/TileBase.kt @@ -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 + fun getSelectedActions(context: Context): List + 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 = 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 { + // 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 = 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, 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 + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java index b35aee5ade..1e35d8f827 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java @@ -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() { diff --git a/wear/src/main/res/drawable-notround-nodpi/action_tile_preview.png b/wear/src/main/res/drawable-notround-nodpi/action_tile_preview.png new file mode 100644 index 0000000000..f89aa2f952 Binary files /dev/null and b/wear/src/main/res/drawable-notround-nodpi/action_tile_preview.png differ diff --git a/wear/src/main/res/drawable-notround-nodpi/quick_wizard_tile_preview.png b/wear/src/main/res/drawable-notround-nodpi/quick_wizard_tile_preview.png new file mode 100644 index 0000000000..850ff52de7 Binary files /dev/null and b/wear/src/main/res/drawable-notround-nodpi/quick_wizard_tile_preview.png differ diff --git a/wear/src/main/res/drawable-notround-nodpi/temp_target_tile.png b/wear/src/main/res/drawable-notround-nodpi/temp_target_tile.png new file mode 100644 index 0000000000..449469e5c0 Binary files /dev/null and b/wear/src/main/res/drawable-notround-nodpi/temp_target_tile.png differ diff --git a/wear/src/main/res/drawable-round-nodpi/action_tile_preview.png b/wear/src/main/res/drawable-round-nodpi/action_tile_preview.png new file mode 100644 index 0000000000..7f1e51b813 Binary files /dev/null and b/wear/src/main/res/drawable-round-nodpi/action_tile_preview.png differ diff --git a/wear/src/main/res/drawable-round-nodpi/quick_wizard_tile_preview.png b/wear/src/main/res/drawable-round-nodpi/quick_wizard_tile_preview.png new file mode 100644 index 0000000000..850ff52de7 Binary files /dev/null and b/wear/src/main/res/drawable-round-nodpi/quick_wizard_tile_preview.png differ diff --git a/wear/src/main/res/drawable-round-nodpi/temp_target_tile_preview.png b/wear/src/main/res/drawable-round-nodpi/temp_target_tile_preview.png new file mode 100644 index 0000000000..c16889379e Binary files /dev/null and b/wear/src/main/res/drawable-round-nodpi/temp_target_tile_preview.png differ diff --git a/wear/src/main/res/drawable/ic_bolus.xml b/wear/src/main/res/drawable/ic_bolus.xml index f3fbf5cfad..752e99ce35 100644 --- a/wear/src/main/res/drawable/ic_bolus.xml +++ b/wear/src/main/res/drawable/ic_bolus.xml @@ -1,13 +1,8 @@ - - - - + + + diff --git a/wear/src/main/res/drawable/ic_bolus_carbs.xml b/wear/src/main/res/drawable/ic_bolus_carbs.xml new file mode 100644 index 0000000000..41fdf19a12 --- /dev/null +++ b/wear/src/main/res/drawable/ic_bolus_carbs.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/wear/src/main/res/drawable/ic_calculator_green.xml b/wear/src/main/res/drawable/ic_calculator_green.xml new file mode 100644 index 0000000000..81d189a8ab --- /dev/null +++ b/wear/src/main/res/drawable/ic_calculator_green.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/wear/src/main/res/drawable/ic_carbs_orange.xml b/wear/src/main/res/drawable/ic_carbs_orange.xml new file mode 100644 index 0000000000..6a41ff68ac --- /dev/null +++ b/wear/src/main/res/drawable/ic_carbs_orange.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_quick_wizard.xml b/wear/src/main/res/drawable/ic_quick_wizard.xml new file mode 100644 index 0000000000..b2d1f2569b --- /dev/null +++ b/wear/src/main/res/drawable/ic_quick_wizard.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/wear/src/main/res/drawable/ic_target_activity.xml b/wear/src/main/res/drawable/ic_target_activity.xml new file mode 100644 index 0000000000..4439597b75 --- /dev/null +++ b/wear/src/main/res/drawable/ic_target_activity.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/wear/src/main/res/drawable/ic_target_cancel.xml b/wear/src/main/res/drawable/ic_target_cancel.xml new file mode 100644 index 0000000000..50d7304662 --- /dev/null +++ b/wear/src/main/res/drawable/ic_target_cancel.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/wear/src/main/res/drawable/ic_target_eatingsoon.xml b/wear/src/main/res/drawable/ic_target_eatingsoon.xml new file mode 100644 index 0000000000..54b6162546 --- /dev/null +++ b/wear/src/main/res/drawable/ic_target_eatingsoon.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/wear/src/main/res/drawable/ic_target_hypo.xml b/wear/src/main/res/drawable/ic_target_hypo.xml new file mode 100644 index 0000000000..30721cbf14 --- /dev/null +++ b/wear/src/main/res/drawable/ic_target_hypo.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/wear/src/main/res/drawable/ic_target_manual.xml b/wear/src/main/res/drawable/ic_target_manual.xml new file mode 100644 index 0000000000..fc41c52757 --- /dev/null +++ b/wear/src/main/res/drawable/ic_target_manual.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/wear/src/main/res/drawable/ic_temptarget_flat.xml b/wear/src/main/res/drawable/ic_temptarget_flat.xml new file mode 100644 index 0000000000..c38ef9770f --- /dev/null +++ b/wear/src/main/res/drawable/ic_temptarget_flat.xml @@ -0,0 +1,12 @@ + + + + diff --git a/wear/src/main/res/drawable/ic_treatment.xml b/wear/src/main/res/drawable/ic_treatment.xml new file mode 100644 index 0000000000..f3fbf5cfad --- /dev/null +++ b/wear/src/main/res/drawable/ic_treatment.xml @@ -0,0 +1,13 @@ + + + + diff --git a/wear/src/main/res/layout/action_confirm_text.xml b/wear/src/main/res/layout/action_confirm_text.xml index 3674cf7e24..031d98d1e2 100644 --- a/wear/src/main/res/layout/action_confirm_text.xml +++ b/wear/src/main/res/layout/action_confirm_text.xml @@ -1,8 +1,7 @@ + android:layout_height="match_parent"> + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:layout_gravity="center_horizontal|bottom" + app:dotFadeOutDelay="100000"/> diff --git a/wear/src/main/res/values/arrays.xml b/wear/src/main/res/values/arrays.xml index f8ea2c4bfb..a70ec6e5ec 100644 --- a/wear/src/main/res/values/arrays.xml +++ b/wear/src/main/res/values/arrays.xml @@ -49,7 +49,7 @@ @string/menu_default @string/menu_menu @string/menu_wizard - @string/menu_bolus + @string/menu_treatment @string/menu_ecarb @string/menu_status @string/menu_none @@ -79,4 +79,40 @@ ambient_charging + + @string/menu_wizard + @string/menu_treatment + @string/menu_bolus + @string/menu_ecarb + @string/menu_tempt + @string/tile_none + + + + wizard + treatment + bolus + carbs + temp_target + none + + + + @string/temp_target_activity + @string/temp_target_eating_soon + @string/temp_target_hypo + @string/temp_target_manual + @string/temp_target_cancel + @string/tile_none + + + + activity + eating_soon + hypo + manual + cancel + none + + diff --git a/wear/src/main/res/values/colors.xml b/wear/src/main/res/values/colors.xml index 0bd7113370..708cec0302 100644 --- a/wear/src/main/res/values/colors.xml +++ b/wear/src/main/res/values/colors.xml @@ -46,6 +46,13 @@ #CF8BFE #FBC02D + + #00FF00 + #67DFE8 + #FFFB8C00 + #FF0000 + #77dd77 + @@ -157,13 +164,16 @@ #3E2723 - + #FAFAFA #F5F5F5 #E0E0E0 #9E9E9E - #9E9E9E + #9E9E9E + #616161 + #424242 + #313131 #212121 #333333 diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml index f30fed3c74..efd047c1e5 100644 --- a/wear/src/main/res/values/strings.xml +++ b/wear/src/main/res/values/strings.xml @@ -14,6 +14,9 @@ AAPS(Steampunk) AAPS(DigitalStyle) + AAPS(Actions) + AAPS(Temp Target) + AAPS(Quick Wizard) No data! Old data! @@ -60,21 +63,23 @@ Ring History Light Ring History Animations - Wizard in Menu + Calculator in Menu Prime in Menu Single Target - Wizard Percentage + Calculator Percentage Complication Tap Action Unicode in Complications Version: more Watchface settings Look into Watchface configuration, please. - TempT - Wizard + Calculator + Calc + Treatment + Treat Bolus - eCarb + Carbs Settings Status Re-Sync @@ -82,23 +87,32 @@ None Default Menu + XL - duration - target - low - high - carbs - percentage - start [min] - duration [h] - insulin + Duration + Temp Target Requested + Quick Wizard Requested + Treatment Requested + Bolus Requested + Calculation Requested + Fill Requested + Carbs Requested + CPP Requested + Target + Low + High + Carbs + Percentage + Start [min] + Duration [h] + Insulin Preset 1 Preset 2 Preset 3 Free amount CONFIRM timeshift - bolus + Bolus Bolus Progress press to cancel @@ -113,7 +127,6 @@ IOB no status - red pink purple @@ -139,7 +152,6 @@ Simplify UI Only show time and BG - Vibrate hourly Show Week number @@ -156,10 +168,25 @@ AAPS Bolus Progress Silent Bolus progress and cancel Bolus progress and cancel with less vibrations - + QuickWizard + wearcontrol + units_mgdl + boluswizard_percentage + treatmentssafety_maxcarbs + treatmentssafety_maxbolus Off During Charging Always On Mode Always On and Charging + Eating + Hypo + Activity + Manual + Cancel + None + No config available + Wear controls disabled + No data available + quick_wizard_data_map diff --git a/wear/src/main/res/xml/preferences.xml b/wear/src/main/res/xml/preferences.xml index 919787dec7..efadbc39d9 100644 --- a/wear/src/main/res/xml/preferences.xml +++ b/wear/src/main/res/xml/preferences.xml @@ -101,15 +101,6 @@ android:summary="Chart Timeframe" android:title="@string/pref_chart_timeframe" /> - - diff --git a/wear/src/main/res/xml/tile_configuration_activity.xml b/wear/src/main/res/xml/tile_configuration_activity.xml new file mode 100644 index 0000000000..19abf54a05 --- /dev/null +++ b/wear/src/main/res/xml/tile_configuration_activity.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/wear/src/main/res/xml/tile_configuration_tempt.xml b/wear/src/main/res/xml/tile_configuration_tempt.xml new file mode 100644 index 0000000000..44ba856ef0 --- /dev/null +++ b/wear/src/main/res/xml/tile_configuration_tempt.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + +