Wear: ad quick wizard tile

This commit is contained in:
Andries Smit 2022-01-18 20:54:39 +01:00
parent 2c7cf27047
commit 0233db8226
27 changed files with 790 additions and 499 deletions

View file

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

View file

@ -9,6 +9,7 @@ import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.view.WindowManager import android.view.WindowManager
import dagger.android.support.DaggerDialogFragment import dagger.android.support.DaggerDialogFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.OverviewEditquickwizardDialogBinding import info.nightscout.androidaps.databinding.OverviewEditquickwizardDialogBinding
import info.nightscout.shared.logging.AAPSLogger import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus 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.extensions.setSelection
import info.nightscout.androidaps.utils.wizard.QuickWizard import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.androidaps.utils.wizard.QuickWizardEntry import info.nightscout.androidaps.utils.wizard.QuickWizardEntry
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException import org.json.JSONException
import javax.inject.Inject import javax.inject.Inject
@ -30,6 +32,7 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
@Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var quickWizard: QuickWizard @Inject lateinit var quickWizard: QuickWizard
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var sp: SP
var position = -1 var position = -1
@ -57,6 +60,14 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
position = bundle.getInt("position", -1) position = bundle.getInt("position", -1)
} }
val entry = if (position == -1) quickWizard.newEmptyItem() else quickWizard[position] 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 { binding.okcancel.ok.setOnClickListener {
try { try {
entry.storage.put("buttonText", binding.buttonEdit.text.toString()) entry.storage.put("buttonText", binding.buttonEdit.text.toString())
@ -66,6 +77,7 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
entry.storage.put("useBG", binding.useBg.selectedItemPosition) entry.storage.put("useBG", binding.useBg.selectedItemPosition)
entry.storage.put("useCOB", binding.useCob.selectedItemPosition) entry.storage.put("useCOB", binding.useCob.selectedItemPosition)
entry.storage.put("useBolusIOB", binding.useBolusIob.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("useBasalIOB", binding.useBasalIob.selectedItemPosition)
entry.storage.put("useTrend", binding.useTrend.selectedItemPosition) entry.storage.put("useTrend", binding.useTrend.selectedItemPosition)
entry.storage.put("useSuperBolus", binding.useSuperBolus.selectedItemPosition) entry.storage.put("useSuperBolus", binding.useSuperBolus.selectedItemPosition)
@ -122,6 +134,7 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
binding.useCob.setSelection(entry.useCOB()) binding.useCob.setSelection(entry.useCOB())
binding.useBolusIob.setSelection(entry.useBolusIOB()) binding.useBolusIob.setSelection(entry.useBolusIOB())
binding.useBasalIob.setSelection(entry.useBasalIOB()) binding.useBasalIob.setSelection(entry.useBasalIOB())
binding.device.setSelection(entry.device())
binding.useTrend.setSelection(entry.useTrend()) binding.useTrend.setSelection(entry.useTrend())
binding.useSuperBolus.setSelection(entry.useSuperBolus()) binding.useSuperBolus.setSelection(entry.useSuperBolus())
binding.useTempTarget.setSelection(entry.useTempTarget()) binding.useTempTarget.setSelection(entry.useTempTarget())

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.general.wear
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.util.Log
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
@ -39,6 +40,7 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.androidaps.utils.wizard.BolusWizard import info.nightscout.androidaps.utils.wizard.BolusWizard
import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.shared.SafeParse import info.nightscout.shared.SafeParse
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.plusAssign
@ -71,6 +73,7 @@ class ActionStringHandler @Inject constructor(
private val activePlugin: ActivePlugin, private val activePlugin: ActivePlugin,
private val iobCobCalculator: IobCobCalculator, private val iobCobCalculator: IobCobCalculator,
private val localInsightPlugin: LocalInsightPlugin, private val localInsightPlugin: LocalInsightPlugin,
private val quickWizard: QuickWizard,
private val danaRPlugin: DanaRPlugin, private val danaRPlugin: DanaRPlugin,
private val danaRKoreanPlugin: DanaRKoreanPlugin, private val danaRKoreanPlugin: DanaRKoreanPlugin,
private val danaRv2Plugin: DanaRv2Plugin, private val danaRv2Plugin: DanaRv2Plugin,
@ -144,34 +147,39 @@ class ActionStringHandler @Inject constructor(
} }
rAction += "bolus $insulinAfterConstraints $carbsAfterConstraints" rAction += "bolus $insulinAfterConstraints $carbsAfterConstraints"
} else if ("temptarget" == act[0]) { ///////////////////////////////////////////////////////// TEMPTARGET } else if ("temptarget" == act[0]) { ///////////////////////////////////////////////////////// TEMPTARGET
aapsLogger.info(LTag.WEAR, "temptarget received:" + act) aapsLogger.info(LTag.WEAR, "temptarget received: $act")
if ("cancel" == act[1]) { if ("cancel" == act[1]) {
rMessage += rh.gs(R.string.wear_action_tempt_cancel_message) rMessage += rh.gs(R.string.wear_action_tempt_cancel_message)
rAction = "temptarget true 0 0 0" rAction = "temptarget true 0 0 0"
} else if ("preset" == act[1]) { } else if ("preset" == act[1]) {
val presetIsMGDL = profileFunction.getUnits() == GlucoseUnit.MGDL val presetIsMGDL = profileFunction.getUnits() == GlucoseUnit.MGDL
val preset = act[2] val preset = act[2]
if ("activity" == preset) { when (preset) {
val activityTTDuration = defaultValueHelper.determineActivityTTDuration() "activity" -> {
val activityTT = defaultValueHelper.determineActivityTT() val activityTTDuration = defaultValueHelper.determineActivityTTDuration()
val reason = rh.gs(R.string.activity) val activityTT = defaultValueHelper.determineActivityTT()
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, activityTT, activityTTDuration) val reason = rh.gs(R.string.activity)
rAction = "temptarget $presetIsMGDL $activityTTDuration $activityTT $activityTT" rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, activityTT, activityTTDuration)
} else if ("hypo" == preset) { rAction = "temptarget $presetIsMGDL $activityTTDuration $activityTT $activityTT"
val hypoTTDuration = defaultValueHelper.determineHypoTTDuration() }
val hypoTT = defaultValueHelper.determineHypoTT() "hypo" -> {
val reason = rh.gs(R.string.hypo) val hypoTTDuration = defaultValueHelper.determineHypoTTDuration()
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, hypoTT, hypoTTDuration) val hypoTT = defaultValueHelper.determineHypoTT()
rAction = "temptarget $presetIsMGDL $hypoTTDuration $hypoTT $hypoTT" val reason = rh.gs(R.string.hypo)
} else if ("eating" == preset) { rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, hypoTT, hypoTTDuration)
val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration() rAction = "temptarget $presetIsMGDL $hypoTTDuration $hypoTT $hypoTT"
val eatingSoonTT = defaultValueHelper.determineEatingSoonTT() }
val reason = rh.gs(R.string.eatingsoon) "eating" -> {
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, eatingSoonTT, eatingSoonTTDuration) val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration()
rAction = "temptarget $presetIsMGDL $eatingSoonTTDuration $eatingSoonTT $eatingSoonTT" val eatingSoonTT = defaultValueHelper.determineEatingSoonTT()
} else { val reason = rh.gs(R.string.eatingsoon)
sendError(rh.gs(R.string.wear_action_tempt_preset_error, preset)) rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, eatingSoonTT, eatingSoonTTDuration)
return rAction = "temptarget $presetIsMGDL $eatingSoonTTDuration $eatingSoonTT $eatingSoonTT"
}
else -> {
sendError(rh.gs(R.string.wear_action_tempt_preset_error, preset))
return
}
} }
} else { } else {
val isMGDL = java.lang.Boolean.parseBoolean(act[1]) val isMGDL = java.lang.Boolean.parseBoolean(act[1])
@ -281,6 +289,34 @@ class ActionStringHandler @Inject constructor(
rMessage += "\nPercentage: " + format.format(bolusWizard.totalBeforePercentageAdjustment) + "U * " + percentage + "% -> ~" + format.format(bolusWizard.calculatedTotalInsulin) + "U" rMessage += "\nPercentage: " + format.format(bolusWizard.totalBeforePercentageAdjustment) + "U * " + percentage + "% -> ~" + format.format(bolusWizard.calculatedTotalInsulin) + "U"
} }
lastBolusWizard = bolusWizard 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 pump = activePlugin.activePump
val quickWizardEntry = quickWizard.get(guid)
Log.i("QuickWizard", "handleInitiate: quick_wizard " + quickWizardEntry?.buttonText() + " c "+ quickWizardEntry?.carbs())
if (quickWizardEntry != null && actualBg != null && profile != null) {
// Logic related from Overview.kt
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
if (wizard.calculatedTotalInsulin > 0.0 && quickWizardEntry.carbs() > 0.0) {
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
val insulinAfterConstraints = wizard.insulinAfterConstraints
if (abs(insulinAfterConstraints - wizard.calculatedTotalInsulin) >= pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints) || carbsAfterConstraints != quickWizardEntry.carbs()) {
// TODO check error is correct
sendError(rh.gs(R.string.constraints_violation) + "\n" + rh.gs(R.string.changeyourinput))
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")
} else {
sendError(rh.gs(R.string.quick_wizard_no_action))
}
} else {
sendError(rh.gs(R.string.quick_wizard_can_not_calculate))
}
} else if ("opencpp" == act[0]) { } else if ("opencpp" == act[0]) {
val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet() val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
if (activeProfileSwitch is ValueWrapper.Existing) { // read CPP values if (activeProfileSwitch is ValueWrapper.Existing) { // read CPP values
@ -364,7 +400,10 @@ class ActionStringHandler @Inject constructor(
rAction = "cancelChangeRequest" rAction = "cancelChangeRequest"
wearPlugin.requestNotificationCancel(rAction) wearPlugin.requestNotificationCancel(rAction)
return return
} else return } else {
sendError("Unknown action command: " + act[0] )
return
}
// send result // send result
wearPlugin.requestActionConfirmation(rTitle, rMessage, rAction) wearPlugin.requestActionConfirmation(rTitle, rMessage, rAction)
lastSentTimestamp = System.currentTimeMillis() lastSentTimestamp = System.currentTimeMillis()

View file

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

View file

@ -1,10 +1,12 @@
package info.nightscout.androidaps.utils.wizard package info.nightscout.androidaps.utils.wizard
import android.util.Log
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -18,6 +20,18 @@ class QuickWizard @Inject constructor(
init { init {
setData(JSONArray(sp.getString(R.string.key_quickwizard, "[]"))) 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? { fun getActive(): QuickWizardEntry? {
@ -41,6 +55,38 @@ class QuickWizard @Inject constructor(
operator fun get(position: Int): QuickWizardEntry = operator fun get(position: Int): QuickWizardEntry =
QuickWizardEntry(injector).from(storage.get(position) as JSONObject, position) 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 { fun newEmptyItem(): QuickWizardEntry {
return QuickWizardEntry(injector) return QuickWizardEntry(injector)
} }
@ -57,4 +103,5 @@ class QuickWizard @Inject constructor(
storage.remove(position) storage.remove(position)
save() save()
} }
} }

View file

@ -19,6 +19,7 @@ import info.nightscout.androidaps.utils.JsonHelper.safeGetString
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import java.util.*
import javax.inject.Inject import javax.inject.Inject
class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjector) { class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjector) {
@ -41,11 +42,15 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
const val NO = 1 const val NO = 1
private const val POSITIVE_ONLY = 2 private const val POSITIVE_ONLY = 2
private const val NEGATIVE_ONLY = 3 private const val NEGATIVE_ONLY = 3
const val DEVICE_ALL = 0
const val DEVICE_PHONE = 1
const val DEVICE_WATCH = 2
} }
init { init {
injector.androidInjector().inject(this) 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\"}"
try { try {
storage = JSONObject(emptyData) storage = JSONObject(emptyData)
} catch (e: JSONException) { } catch (e: JSONException) {
@ -55,6 +60,8 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
/* /*
{ {
guid: string,
device: string, // (phone, watch, all)
buttonText: "Meal", buttonText: "Meal",
carbs: 36, carbs: 36,
validFrom: 8 * 60 * 60, // seconds from midnight validFrom: 8 * 60 * 60, // seconds from midnight
@ -69,12 +76,13 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
} }
*/ */
fun from(entry: JSONObject, position: Int): QuickWizardEntry { fun from(entry: JSONObject, position: Int): QuickWizardEntry {
// TODO set guid if missing for migration
storage = entry storage = entry
this.position = position this.position = position
return this 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 { fun doCalc(profile: Profile, profileName: String, lastBG: GlucoseValue, _synchronized: Boolean): BolusWizard {
val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
@ -123,6 +131,12 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
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 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 buttonText(): String = safeGetString(storage, "buttonText", "")
fun carbs(): Int = safeGetInt(storage, "carbs") fun carbs(): Int = safeGetInt(storage, "carbs")

View file

@ -108,6 +108,35 @@
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/device_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/overview_editquickwizard_show_on_device"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<RadioGroup
android:id="@+id/device"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="15dp">
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="All" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Phone" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Watch" />
</RadioGroup>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"

View file

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

View file

@ -1175,5 +1175,9 @@
<string name="wear_action_tempt_manual_range_message">Temptarget:\nMin: %1$s\nMax: %2$s\nDuration: %3$s</string> <string name="wear_action_tempt_manual_range_message">Temptarget:\nMin: %1$s\nMax: %2$s\nDuration: %3$s</string>
<string name="wear_action_tempt_manual_message">Temptarget:\nTarget: %1$s\nDuration: %2$s</string> <string name="wear_action_tempt_manual_message">Temptarget:\nTarget: %1$s\nDuration: %2$s</string>
<string name="wear_action_tempt_preset_message">Temptarget:\Reason: %1$s\nTarget: %2$s\nDuration: %3$s</string> <string name="wear_action_tempt_preset_message">Temptarget:\Reason: %1$s\nTarget: %2$s\nDuration: %3$s</string>
<string name="quick_wizard_no_action">No insulin needed nor are carbs added.</string>
<string name="quick_wizard_can_not_calculate">Can not calculate wizard, requires actual blood glucose and active profile.</string>
<string name="quick_wizard_message">Quick Wizard: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg</string>
<string name="overview_editquickwizard_show_on_device">Show entry on device:</string>
</resources> </resources>

View file

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

View file

@ -258,6 +258,10 @@
android:host="*" android:host="*"
android:pathPrefix="/openwearsettings" android:pathPrefix="/openwearsettings"
android:scheme="wear" /> android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/send_quick_wizard"
android:scheme="wear" />
<data <data
android:host="*" android:host="*"
android:pathPrefix="/sendstatustowear" android:pathPrefix="/sendstatustowear"
@ -544,6 +548,20 @@
android:resource="@drawable/temp_target_tile_preview" /> android:resource="@drawable/temp_target_tile_preview" />
</service> </service>
<service
android:name=".tile.QuickWizardTileService"
android:exported="true"
android:label="@string/label_quick_wizard_tile"
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
<intent-filter>
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
</intent-filter>
<meta-data
android:name="androidx.wear.tiles.PREVIEW"
android:resource="@drawable/quick_wizard_tile_preview" />
</service>
<receiver android:name=".complications.ComplicationTapBroadcastReceiver" /> <receiver android:name=".complications.ComplicationTapBroadcastReceiver" />
<activity <activity
@ -595,6 +613,10 @@
android:exported="true" android:exported="true"
android:label="@string/menu_tempt" /> android:label="@string/menu_tempt" />
<activity
android:name=".interaction.actions.BackgroundActionActivity"
android:exported="true" />
<activity android:name=".interaction.ConfigurationActivity"> <activity android:name=".interaction.ConfigurationActivity">
<intent-filter> <intent-filter>
<!-- action-name must be equal with name of xml-ressource where the configuration is describe --> <!-- action-name must be equal with name of xml-ressource where the configuration is describe -->

View file

@ -13,6 +13,7 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.SystemClock; import android.os.SystemClock;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
@ -22,10 +23,6 @@ import androidx.wear.tiles.TileService;
import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient; 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.ChannelApi;
import com.google.android.gms.wearable.DataEvent; import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.DataEventBuffer;
@ -36,7 +33,8 @@ import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable; import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService; import com.google.android.gms.wearable.WearableListenerService;
import java.util.Set; import org.jetbrains.annotations.NotNull;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
@ -47,11 +45,11 @@ import info.nightscout.androidaps.interaction.AAPSPreferences;
import info.nightscout.androidaps.interaction.actions.AcceptActivity; import info.nightscout.androidaps.interaction.actions.AcceptActivity;
import info.nightscout.androidaps.interaction.actions.CPPActivity; import info.nightscout.androidaps.interaction.actions.CPPActivity;
import info.nightscout.androidaps.interaction.utils.Persistence; import info.nightscout.androidaps.interaction.utils.Persistence;
import info.nightscout.androidaps.interaction.utils.WearUtil;
import info.nightscout.androidaps.tile.ActionsTileService; import info.nightscout.androidaps.tile.ActionsTileService;
import info.nightscout.androidaps.tile.QuickWizardTileService;
import info.nightscout.androidaps.tile.TempTargetTileService; import info.nightscout.androidaps.tile.TempTargetTileService;
import info.nightscout.shared.SafeParse; import info.nightscout.shared.SafeParse;
import info.nightscout.androidaps.interaction.utils.WearUtil;
/** /**
* Created by emmablack on 12/26/14. * Created by emmablack on 12/26/14.
@ -62,7 +60,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Inject WearUtil wearUtil; @Inject WearUtil wearUtil;
@Inject Persistence persistence; @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_RESEND_PATH = "/nightscout_watch_data_resend";
private static final String WEARABLE_CANCELBOLUS_PATH = "/nightscout_watch_cancel_bolus"; private static final String WEARABLE_CANCELBOLUS_PATH = "/nightscout_watch_cancel_bolus";
public static final String WEARABLE_CONFIRM_ACTIONSTRING_PATH = "/nightscout_watch_confirmactionstring"; public static final String WEARABLE_CONFIRM_ACTIONSTRING_PATH = "/nightscout_watch_confirmactionstring";
@ -71,13 +68,13 @@ public class ListenerService extends WearableListenerService implements GoogleAp
private static final String OPEN_SETTINGS = "/openwearsettings"; private static final String OPEN_SETTINGS = "/openwearsettings";
private static final String NEW_STATUS_PATH = "/sendstatustowear"; private static final String NEW_STATUS_PATH = "/sendstatustowear";
private static final String NEW_PREFERENCES_PATH = "/sendpreferencestowear"; 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 BASAL_DATA_PATH = "/nightscout_watch_basal";
public static final String BOLUS_PROGRESS_PATH = "/nightscout_watch_bolusprogress"; 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_CONFIRMATION_REQUEST_PATH = "/nightscout_watch_actionconfirmationrequest";
public static final String NEW_CHANGECONFIRMATIONREQUEST_PATH = "/nightscout_watch_changeconfirmationrequest"; public static final String NEW_CHANGECONFIRMATIONREQUEST_PATH = "/nightscout_watch_changeconfirmationrequest";
public static final String ACTION_CANCELNOTIFICATION_REQUEST_PATH = "/nightscout_watch_cancelnotificationrequest"; public static final String ACTION_CANCELNOTIFICATION_REQUEST_PATH = "/nightscout_watch_cancelnotificationrequest";
public static final int BOLUS_PROGRESS_NOTIF_ID = 1; public static final int BOLUS_PROGRESS_NOTIF_ID = 1;
public static final int CONFIRM_NOTIF_ID = 2; public static final int CONFIRM_NOTIF_ID = 2;
public static final int CHANGE_NOTIF_ID = 556677; public static final int CHANGE_NOTIF_ID = 556677;
@ -88,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_CONFIRMCHANGE = "com.dexdrip.stephenblack.nightwatch.CONFIRMCHANGE";
private static final String ACTION_INITIATE_ACTION = "com.dexdrip.stephenblack.nightwatch.INITIATE_ACTION"; 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_OPENLOOP = "AndroidAPS-OpenLoop";
private static final String AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS = "bolus progress vibration"; 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"; private static final String AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT = "bolus progress silent";
GoogleApiClient googleApiClient; GoogleApiClient googleApiClient;
private long lastRequest = 0;
private DismissThread bolusprogressThread; private DismissThread bolusprogressThread;
private static final String TAG = "ListenerService"; 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: " private final String logPrefix = ""; // "WR: "
// Not derived from DaggerService, do injection here // Not derived from DaggerService, do injection here
@ -120,143 +103,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
super.onCreate(); super.onCreate();
} }
public class DataRequester extends AsyncTask<Void, Void, Void> {
Context mContext;
String path;
byte[] payload;
DataRequester(Context context, String thispath, byte[] thispayload) {
path = thispath;
payload = thispayload;
// Log.d(TAG, logPrefix + "DataRequester DataRequester: " + thispath + " lastRequest:" + lastRequest);
}
@Override
protected Void doInBackground(Void... params) {
// Log.d(TAG, logPrefix + "DataRequester: doInBack: " + params);
try {
forceGoogleApiConnect();
DataMap datamap;
if (isCancelled()) {
Log.d(TAG, "doInBackground CANCELLED programmatically");
return null;
}
if (googleApiClient != null) {
if (!googleApiClient.isConnected())
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
// this code might not be needed in this way, but we need to see that later
if ((googleApiClient != null) && (googleApiClient.isConnected())) {
if ((System.currentTimeMillis() - lastRequest > 20 * 1000)) {
// enforce 20-second debounce period
lastRequest = System.currentTimeMillis();
// NodeApi.GetConnectedNodesResult nodes =
// Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
if (localnode == null || (localnode != null && localnode.isEmpty()))
setLocalNodeName();
CapabilityInfo capabilityInfo = getCapabilities();
int count = 0;
Node phoneNode = null;
if (capabilityInfo != null) {
phoneNode = updatePhoneSyncBgsCapability(capabilityInfo);
count = capabilityInfo.getNodes().size();
}
Log.d(TAG, "doInBackground connected. CapabilityApi.GetCapabilityResult mPhoneNodeID="
+ (phoneNode != null ? phoneNode.getId() : "") + " count=" + count + " localnode="
+ localnode);// KS
if (count > 0) {
for (Node node : capabilityInfo.getNodes()) {
// Log.d(TAG, "doInBackground path: " + path);
switch (path) {
// simple send as is payloads
case WEARABLE_RESEND_PATH:
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(),
WEARABLE_RESEND_PATH, null);
break;
case WEARABLE_DATA_PATH:
case WEARABLE_CANCELBOLUS_PATH:
case WEARABLE_CONFIRM_ACTIONSTRING_PATH:
case WEARABLE_INITIATE_ACTIONSTRING_PATH:
case OPEN_SETTINGS:
case NEW_STATUS_PATH:
case NEW_PREFERENCES_PATH:
case BASAL_DATA_PATH:
case BOLUS_PROGRESS_PATH:
case ACTION_CONFIRMATION_REQUEST_PATH:
case NEW_CHANGECONFIRMATIONREQUEST_PATH:
case ACTION_CANCELNOTIFICATION_REQUEST_PATH: {
Log.w(TAG, logPrefix + "Unhandled path");
// sendMessagePayload(node, path, path, payload);
}
default:// SYNC_ALL_DATA
// this fall through is messy and non-deterministic for new paths
}
}
} else {
Log.d(TAG, logPrefix + "doInBackground connected but getConnectedNodes returns 0.");
}
} else {
// no resend
Log.d(TAG, logPrefix + "Inside the timeout, will not be executed");
}
} else {
Log.d(TAG, logPrefix + "Not connected for sending: api "
+ ((googleApiClient == null) ? "is NULL!" : "not null"));
if (googleApiClient != null) {
googleApiClient.connect();
} else {
googleApiConnect();
}
}
} catch (Exception ex) {
Log.e(TAG, logPrefix + "Error executing DataRequester in background. Exception: " + ex.getMessage());
}
return null;
}
}
public CapabilityInfo getCapabilities() {
CapabilityApi.GetCapabilityResult capabilityResult = Wearable.CapabilityApi.getCapability(googleApiClient,
CAPABILITY_PHONE_APP, CapabilityApi.FILTER_REACHABLE).await(GET_CAPABILITIES_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
if (!capabilityResult.getStatus().isSuccess()) {
Log.e(TAG, logPrefix + "doInBackground Failed to get capabilities, status: "
+ capabilityResult.getStatus().getStatusMessage());
return null;
}
return capabilityResult.getCapability();
}
public class BolusCancelTask extends AsyncTask<Void, Void, Void> { public class BolusCancelTask extends AsyncTask<Void, Void, Void> {
Context mContext; Context mContext;
@ -266,8 +112,11 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override @Override
protected Void doInBackground(Void... params) { 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()) { if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes = NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
@ -275,16 +124,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_CANCELBOLUS_PATH, null); 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; return null;
} }
@ -303,9 +142,12 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override @Override
protected Void doInBackground(Void... params) { 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()) { if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes = NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
@ -313,22 +155,42 @@ public class ListenerService extends WearableListenerService implements GoogleAp
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), mMessagePath, mActionstring.getBytes()); 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; return null;
} }
} }
public class ResendDataTask extends AsyncTask<Void, Void, Void> {
Context mContext;
ResendDataTask(Context context) {
mContext = context;
}
@Override
protected Void doInBackground(Void... params) {
Log.d(TAG, logPrefix + "ResendDataTask.doInBackground: " + params);
if (!googleApiClient.isConnected()) {
Log.i(TAG, "ResendDataTask.doInBackground: not connected");
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
if (googleApiClient.isConnected()) {
Log.i(TAG, "ResendDataTask.doInBackground: connected");
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_RESEND_PATH, null);
}
} else {
Log.i(TAG, "ResendDataTask.doInBackground: could not connect");
}
return null;
}
}
public void requestData() { public void requestData() {
sendData(WEARABLE_RESEND_PATH, null); new ResendDataTask(this).execute();
} }
public void cancelBolus() { public void cancelBolus() {
@ -343,59 +205,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
new MessageActionTask(this, WEARABLE_INITIATE_ACTIONSTRING_PATH, actionstring).execute(); new MessageActionTask(this, WEARABLE_INITIATE_ACTIONSTRING_PATH, actionstring).execute();
} }
private Node updatePhoneSyncBgsCapability(CapabilityInfo capabilityInfo) {
// Log.d(TAG, "CapabilityInfo: " + capabilityInfo);
Set<Node> connectedNodes = capabilityInfo.getNodes();
return pickBestNode(connectedNodes);
// mPhoneNodeId = pickBestNodeId(connectedNodes);
}
private Node pickBestNode(Set<Node> nodes) {
Node bestNode = null;
// Find a nearby node or pick one arbitrarily
for (Node node : nodes) {
if (node.isNearby()) {
return node;
}
bestNode = node;
}
return bestNode;
}
private synchronized void sendData(String path, byte[] payload) {
// Log.d(TAG, "WR: sendData: path: " + path + ", payload=" + payload);
if (path == null)
return;
if (mDataRequester != null) {
// Log.d(TAG, logPrefix + "sendData DataRequester != null lastRequest:" +
// WearUtil.dateTimeText(lastRequest));
if (mDataRequester.getStatus() != AsyncTask.Status.FINISHED) {
// Log.d(TAG, logPrefix + "sendData Should be canceled? Let run 'til finished.");
// mDataRequester.cancel(true);
}
}
Log.d(TAG,
logPrefix + "sendData: execute lastRequest:" + wearUtil.dateTimeText(lastRequest));
mDataRequester = (DataRequester) new DataRequester(this, path, payload).execute();
// executeTask(mDataRequester);
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Log.d(TAG, "sendData SDK < M call execute lastRequest:" + WearUtil.dateTimeText(lastRequest));
// mDataRequester = (DataRequester) new DataRequester(this, path, payload).execute();
// } else {
// Log.d(TAG, "sendData SDK >= M call executeOnExecutor lastRequest:" + WearUtil.dateTimeText(lastRequest));
// // TODO xdrip executor
// mDataRequester = (DataRequester) new DataRequester(this, path, payload).executeOnExecutor(xdrip.executor);
// }
}
private void googleApiConnect() { private void googleApiConnect() {
if (googleApiClient != null) { if (googleApiClient != null) {
// Remove old listener(s) // Remove old listener(s)
@ -419,20 +228,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
Wearable.MessageApi.addListener(googleApiClient, this); 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 @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
@ -486,7 +281,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
return START_STICKY; return START_STICKY;
} }
@Override @Override
public void onDataChanged(DataEventBuffer dataEvents) { public void onDataChanged(DataEventBuffer dataEvents) {
@ -548,10 +342,13 @@ public class ListenerService extends WearableListenerService implements GoogleAp
SharedPreferences.Editor editor = sharedPreferences.edit(); SharedPreferences.Editor editor = sharedPreferences.edit();
String keyControl = getString(R.string.key_wear_control); String keyControl = getString(R.string.key_wear_control);
if (dataMap.containsKey(keyControl)) { if (dataMap.containsKey(keyControl)) {
boolean previousWearControl = sharedPreferences.getBoolean(keyControl, false);
boolean wearControl = dataMap.getBoolean(keyControl, false); boolean wearControl = dataMap.getBoolean(keyControl, false);
editor.putBoolean(keyControl, wearControl); editor.putBoolean(keyControl, wearControl);
editor.apply(); editor.apply();
updateTiles(); if (wearControl != previousWearControl) {
updateTiles();
}
} }
String keyPercentage = getString(R.string.key_boluswizard_percentage); String keyPercentage = getString(R.string.key_boluswizard_percentage);
if (dataMap.containsKey(keyPercentage)) { if (dataMap.containsKey(keyPercentage)) {
@ -565,6 +362,26 @@ public class ListenerService extends WearableListenerService implements GoogleAp
editor.putBoolean(keyUnits, mgdl); editor.putBoolean(keyUnits, mgdl);
editor.apply(); 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)) { } else if (path.equals(NEW_CHANGECONFIRMATIONREQUEST_PATH)) {
String title = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("title"); String title = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("title");
String message = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("message"); String message = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("message");
@ -592,6 +409,9 @@ public class ListenerService extends WearableListenerService implements GoogleAp
TileService.getUpdater(this) TileService.getUpdater(this)
.requestUpdate(TempTargetTileService.class); .requestUpdate(TempTargetTileService.class);
TileService.getUpdater(this)
.requestUpdate(QuickWizardTileService.class);
} }
} }
@ -666,7 +486,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp
PendingIntent cancelPendingIntent = PendingIntent.getService(this, 0, cancelIntent, 0); PendingIntent cancelPendingIntent = PendingIntent.getService(this, 0, cancelIntent, 0);
NotificationCompat.Builder notificationBuilder = 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) .setSmallIcon(R.drawable.ic_icon)
.setContentTitle(getString(R.string.bolus_progress)) .setContentTitle(getString(R.string.bolus_progress))
.setContentText(progresspercent + "% - " + progresstatus) .setContentText(progresspercent + "% - " + progresstatus)
@ -723,7 +543,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
bolusprogressThread.start(); bolusprogressThread.start();
} }
private class DismissThread extends Thread { private class DismissThread extends Thread {
private final int notificationID; private final int notificationID;
private final int seconds; private final int seconds;
@ -757,7 +576,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp
context.startService(intent); 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 intent = new Intent(context, ListenerService.class);
intent.putExtra("actionstring", actionstring); intent.putExtra("actionstring", actionstring);
intent.setAction(ACTION_INITIATE_ACTION); intent.setAction(ACTION_INITIATE_ACTION);
@ -780,18 +599,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
public void onConnected(Bundle bundle) { public void onConnected(Bundle bundle) {
// Log.d(TAG, logPrefix + "onConnected call requestData"); // 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); Wearable.ChannelApi.addListener(googleApiClient, this);
requestData(); requestData();
} }
@ -806,28 +613,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp
} }
private void setLocalNodeName() {
forceGoogleApiConnect();
PendingResult<NodeApi.GetLocalNodeResult> result = Wearable.NodeApi.getLocalNode(googleApiClient);
result.setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>() {
@Override
public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
if (!getLocalNodeResult.getStatus().isSuccess()) {
Log.e(TAG, "ERROR: failed to getLocalNode Status="
+ getLocalNodeResult.getStatus().getStatusMessage());
} else {
Log.d(TAG, "getLocalNode Status=: " + getLocalNodeResult.getStatus().getStatusMessage());
Node getnode = getLocalNodeResult.getNode();
localnode = getnode != null ? getnode.getDisplayName() + "|" + getnode.getId() : "";
Log.d(TAG, "setLocalNodeName. localnode=" + localnode);
}
}
});
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();

View file

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

View file

@ -31,9 +31,7 @@ public class TempTargetActivity extends ViewSelectorActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (executeInBackground()){
return;
}
setAdapter(new MyGridViewPagerAdapter()); setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
@ -41,21 +39,6 @@ public class TempTargetActivity extends ViewSelectorActivity {
isSingleTarget = sp.getBoolean("singletarget", true); isSingleTarget = sp.getBoolean("singletarget", true);
} }
private boolean executeInBackground() {
Bundle extras = getIntent().getExtras();
if (extras != null) {
String actionString = extras.getString("actionString", "");
boolean inBackground = extras.getBoolean("inBackground", false);
if (inBackground) {
ListenerService.initiateAction(this, actionString);
confirmAction(this, R.string.action_tempt_confirmation);
finishAffinity();
return true;
}
}
return false;
}
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();

View file

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

View file

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

View file

@ -0,0 +1,79 @@
package info.nightscout.androidaps.tile
import android.content.Context
import android.content.res.Resources
import android.util.Base64
import android.util.Log
import androidx.preference.PreferenceManager
import com.google.android.gms.wearable.DataMap
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interaction.actions.BackgroundActionActivity
import java.util.*
object QuickWizardSource : TileSource {
override fun getSelectedActions(context: Context): List<Action> {
val quickList = mutableListOf<Action>()
val quickMap = getDataMap(context)
val sfm = secondsFromMidnight()
for (quick in quickMap) {
val validFrom = quick.getInt("from", 0)
val validTo = quick.getInt("to", 0)
val isActive = sfm in validFrom..validTo
// use from and to to schedule new update for timeline, for now just refresh every minute
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
}
private fun getDataMap(context: Context): ArrayList<DataMap> {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
val key = context.resources.getString(R.string.key_quick_wizard_data_map)
if (sharedPrefs.contains(key)) {
val rawB64Data: String? = sharedPrefs.getString(key, null)
val rawData: ByteArray = Base64.decode(rawB64Data, Base64.DEFAULT)
try {
val map = DataMap.fromByteArray(rawData)
return map.getDataMapArrayList("quick_wizard")
} catch (ex: IllegalArgumentException) {
Log.e(TAG, "getSelectedActions: IllegalArgumentException ", ex)
}
}
return arrayListOf()
}
private fun secondsFromMidnight(): Int {
val c = Calendar.getInstance()
c.set(Calendar.HOUR_OF_DAY, 0)
c.set(Calendar.MINUTE, 0)
c.set(Calendar.SECOND, 0)
c.set(Calendar.MILLISECOND, 0)
val passed: Long = System.currentTimeMillis() - c.timeInMillis
return (passed / 1000).toInt()
}
override fun getResourceReferences(resources: Resources): List<Int> {
return listOf(R.drawable.ic_quick_wizard)
}
}

View file

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

View file

@ -0,0 +1,59 @@
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 {
abstract fun getActions(resources: Resources): List<StaticAction>
abstract val preferencePrefix: String
abstract fun getDefaultConfig(): Map<String, String>
open fun getSelectedActions(context: Context): List<Action> {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
setDefaultSettings(sharedPrefs)
val actionList: MutableList<Action> = mutableListOf()
for (i in 1..4) {
val action = getActionFromPreference(context.resources, sharedPrefs, i)
if (action != null) {
actionList.add(action)
}
}
if (actionList.isEmpty()) {
return getActions(context.resources).take(4)
}
return actionList
}
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 }
}
open fun setDefaultSettings(sharedPrefs: SharedPreferences) {
val defaults = getDefaultConfig()
val firstKey = defaults.firstNotNullOf { settings -> settings.key }
if (!sharedPrefs.contains(firstKey)) {
val editor = sharedPrefs.edit()
for ((key, value) in defaults) {
editor.putString(key, value)
}
editor.apply()
}
}
}

View file

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

View file

@ -2,9 +2,7 @@ package info.nightscout.androidaps.tile
class TempTargetTileService : TileBase() { class TempTargetTileService : TileBase() {
override val preferencePrefix = "tile_tempt_" override val resourceVersion = "TempTargetTileService"
override val resourceVersion = "1"
override val idIconActionPrefix = "ic_tempt_"
override val source = TempTargetSource; override val source = TempTargetSource;
} }

View file

@ -1,8 +1,9 @@
package info.nightscout.androidaps.tile package info.nightscout.androidaps.tile
import android.content.SharedPreferences import android.content.Context
import android.os.Build
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.wear.tiles.ActionBuilders import androidx.wear.tiles.ActionBuilders
@ -42,18 +43,17 @@ private const val LARGE_SCREEN_WIDTH_DP = 210
interface TileSource { interface TileSource {
fun getActions(): List<Action> fun getResourceReferences(resources: android.content.res.Resources): List<Int>
fun getDefaultConfig(): Map<String, String> fun getSelectedActions(context: Context): List<Action>
} }
data class Action( open class Action(
val id: Int, val buttonText: String,
val settingName: String, val buttonTextSub: String? = null,
@StringRes val nameRes: Int,
val activityClass: String, val activityClass: String,
@DrawableRes val iconRes: Int, @DrawableRes val iconRes: Int,
val background: Boolean = false,
val actionString: String? = null, val actionString: String? = null,
val message: String? = null,
) )
enum class WearControl { enum class WearControl {
@ -62,10 +62,7 @@ enum class WearControl {
abstract class TileBase : TileService() { abstract class TileBase : TileService() {
open val resourceVersion = "1" abstract val resourceVersion: String
open val idIconActionPrefix = "ic_action_"
abstract val preferencePrefix: String
abstract val source: TileSource abstract val source: TileSource
private val serviceJob = Job() private val serviceJob = Job()
@ -89,19 +86,25 @@ abstract class TileBase : TileService() {
.build() .build()
} }
private fun getSelectedActions(): List<Action> {
// TODO check why thi scan not be don in scope of the coroutine
return source.getSelectedActions(this)
}
@RequiresApi(Build.VERSION_CODES.N)
override fun onResourcesRequest( override fun onResourcesRequest(
requestParams: ResourcesRequest requestParams: ResourcesRequest
): ListenableFuture<Resources> = serviceScope.future { ): ListenableFuture<Resources> = serviceScope.future {
Resources.Builder() Resources.Builder()
.setVersion(resourceVersion) .setVersion(resourceVersion)
.apply { .apply {
source.getActions().mapNotNull { action -> source.getResourceReferences(resources).forEach { resourceId ->
addIdToImageMapping( addIdToImageMapping(
idIconActionPrefix + action.id, resourceId.toString(),
ImageResource.Builder() ImageResource.Builder()
.setAndroidResourceByResId( .setAndroidResourceByResId(
AndroidImageResourceByResId.Builder() AndroidImageResourceByResId.Builder()
.setResourceId(action.iconRes) .setResourceId(resourceId)
.build() .build()
) )
.build() .build()
@ -157,15 +160,17 @@ abstract class TileBase : TileService() {
.build() .build()
private fun doAction(action: Action): ActionBuilders.Action { private fun doAction(action: Action): ActionBuilders.Action {
val inBackground = ActionBuilders.AndroidBooleanExtra.Builder().setValue(action.background).build()
val builder = ActionBuilders.AndroidActivity.Builder() val builder = ActionBuilders.AndroidActivity.Builder()
.setClassName(action.activityClass) .setClassName(action.activityClass)
.setPackageName(this.packageName) .setPackageName(this.packageName)
.addKeyToExtraMapping("inBackground", inBackground)
if (action.actionString != null) { if (action.actionString != null) {
val actionString = ActionBuilders.AndroidStringExtra.Builder().setValue(action.actionString).build() val actionString = ActionBuilders.AndroidStringExtra.Builder().setValue(action.actionString).build()
builder.addKeyToExtraMapping("actionString", actionString) 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() return ActionBuilders.LaunchAction.Builder()
.setAndroidActivity(builder.build()) .setAndroidActivity(builder.build())
@ -174,8 +179,8 @@ abstract class TileBase : TileService() {
private fun action(action: Action, deviceParameters: DeviceParameters): LayoutElement { private fun action(action: Action, deviceParameters: DeviceParameters): LayoutElement {
val circleDiameter = circleDiameter(deviceParameters) val circleDiameter = circleDiameter(deviceParameters)
val iconSize = dp(circleDiameter * ICON_SIZE_FRACTION) val text = action.buttonText
val text = resources.getString(action.nameRes) val textSub = action.buttonTextSub
return Box.Builder() return Box.Builder()
.setWidth(dp(circleDiameter)) .setWidth(dp(circleDiameter))
.setHeight(dp(circleDiameter)) .setHeight(dp(circleDiameter))
@ -193,7 +198,7 @@ abstract class TileBase : TileService() {
) )
.setSemantics( .setSemantics(
Semantics.Builder() Semantics.Builder()
.setContentDescription(text) .setContentDescription("$text $textSub")
.build() .build()
) )
.setClickable( .setClickable(
@ -203,32 +208,55 @@ abstract class TileBase : TileService() {
) )
.build() .build()
) )
.addContent( .addContent(addTextContent(action, deviceParameters))
Column.Builder()
.addContent(
Image.Builder()
.setWidth(iconSize)
.setHeight(iconSize)
.setResourceId(idIconActionPrefix + action.id)
.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()
).build()
)
.build() .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) { private fun circleDiameter(deviceParameters: DeviceParameters) = when (deviceParameters.screenShape) {
SCREEN_SHAPE_ROUND -> ((sqrt(2f) - 1) * deviceParameters.screenHeightDp) - (2 * SPACING_ACTIONS) SCREEN_SHAPE_ROUND -> ((sqrt(2f) - 1) * deviceParameters.screenHeightDp) - (2 * SPACING_ACTIONS)
else -> 0.5f * deviceParameters.screenHeightDp - SPACING_ACTIONS else -> 0.5f * deviceParameters.screenHeightDp - SPACING_ACTIONS
@ -257,37 +285,4 @@ abstract class TileBase : TileService() {
return WearControl.DISABLED return WearControl.DISABLED
} }
private fun getSelectedActions(): List<Action> {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
setDefaultSettings(sharedPrefs)
val actionList: MutableList<Action> = mutableListOf()
for (i in 1..4) {
val action = getActionFromPreference(sharedPrefs, i)
if (action != null) {
actionList.add(action)
}
}
if (actionList.isEmpty()) {
return source.getActions().take(4)
}
return actionList
}
private fun getActionFromPreference(sharedPrefs: SharedPreferences, index: Int): Action? {
val actionPref = sharedPrefs.getString(preferencePrefix + index, "none")
return source.getActions().find { action -> action.settingName == actionPref }
}
private fun setDefaultSettings(sharedPrefs: SharedPreferences) {
val defaults = source.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()
}
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,7 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="6.35" android:viewportWidth="6.35"
android:viewportHeight="6.3500004"> android:viewportHeight="6.3500004">
<path <path

View file

@ -0,0 +1,12 @@
<vector
android:height="48dp"
android:width="48dp"
android:viewportHeight="24"
android:viewportWidth="24"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FEAF05" android:pathData="M10.751,6.59c-0.561,0.69 -0.796,1.057 -1.25,1.797c-2.517,0.238 -3.932,2.751 -3.379,4.664c0.244,0.843 0.779,1.476 1.498,1.958c2.348,1.573 6.243,1.429 8.487,-0.306c1.187,-0.918 1.723,-2.084 1.334,-3.576c-0.412,-1.58 -1.445,-2.585 -3.039,-2.87c-1.112,-0.199 -1.973,0.37 -2.558,1.313c-0.494,0.796 -0.72,1.616 -1.628,2.345c-0.389,-0.431 -0.458,-0.961 -0.483,-1.481c-0.12,-2.486 2.019,-4.439 4.595,-4.213c2.365,0.207 4.466,1.956 5.114,4.256c0.611,2.172 -0.226,4.432 -2.19,5.909c-2.995,2.253 -7.895,2.327 -10.943,0.165c-2.057,-1.459 -2.88,-3.881 -2.099,-6.18C5.023,7.978 7.368,6.304 9.77,6.4C10.072,6.412 10.365,6.459 10.751,6.59z"/>
<path android:fillColor="#FEAF06" android:pathData="M7.141,13.406c0.526,-0.103 1.178,-0.22 1.665,-0.322c1.522,-0.319 2.625,-1.229 3.273,-2.678c0.289,-0.647 0.702,-1.125 1.448,-1.42c0.418,0.811 0.454,1.641 0.223,2.493c-0.284,1.049 -0.889,1.863 -1.819,2.429c-0.616,0.376 -1.284,0.638 -1.966,0.867C8.814,15.163 7.797,14.773 7.141,13.406z"/>
<path android:fillColor="#FEAF06" android:pathData="M12.313,14.203c0.734,-0.537 1.25,-1.089 1.469,-1.612c0.894,0.159 1.92,0.314 2.804,0.471C16.036,14.799 13.922,15.203 12.313,14.203z"/>
<path android:fillColor="#FEAF06" android:pathData="M2.377,13.882c0.179,-0.123 0.756,-0.189 1.192,-0.123c0.259,0.631 0.587,1.225 1.108,1.923c-0.572,0.116 -1.037,0.253 -1.567,0.224c-0.493,-0.027 -0.993,-0.353 -1.063,-0.989C2.013,14.61 2.047,14.109 2.377,13.882z"/>
<path android:fillColor="#FEAF06" android:pathData="M19.143,15.22c0.196,-0.497 0.362,-0.961 0.561,-1.411c0.235,-0.531 0.618,-0.76 1.121,-0.681c0.419,0.066 0.575,0.376 0.644,0.756c0.098,0.541 -0.078,0.989 -0.529,1.213C20.38,15.377 19.787,15.293 19.143,15.22z"/>
</vector>

View file

@ -16,6 +16,7 @@
<string name="label_actions_tile">AAPS(Actions)</string> <string name="label_actions_tile">AAPS(Actions)</string>
<string name="label_temp_target_tile">AAPS(Temp Target)</string> <string name="label_temp_target_tile">AAPS(Temp Target)</string>
<string name="label_quick_wizard_tile">AAPS(Quick Wizard)</string>
<string name="label_warning_sync">No data!</string> <string name="label_warning_sync">No data!</string>
<string name="label_warning_old">Old data!</string> <string name="label_warning_old">Old data!</string>
@ -86,9 +87,11 @@
<string name="menu_none">None</string> <string name="menu_none">None</string>
<string name="menu_default">Default</string> <string name="menu_default">Default</string>
<string name="menu_menu">Menu</string> <string name="menu_menu">Menu</string>
<string name="quick_wizard_short">XL</string>
<string name="action_duration">Duration</string> <string name="action_duration">Duration</string>
<string name="action_tempt_confirmation">Temp Target Requested</string> <string name="action_tempt_confirmation">Temp Target Requested</string>
<string name="action_quick_wizard_confirmation">Quick Wizard Requested</string>
<string name="action_treatment_confirmation">Treatment Requested</string> <string name="action_treatment_confirmation">Treatment Requested</string>
<string name="action_bolus_confirmation">Bolus Requested</string> <string name="action_bolus_confirmation">Bolus Requested</string>
<string name="action_wizard_confirmation">Calculation Requested</string> <string name="action_wizard_confirmation">Calculation Requested</string>
@ -165,7 +168,7 @@
<string name="bolus_progress_silent_channel_name">AAPS Bolus Progress Silent</string> <string name="bolus_progress_silent_channel_name">AAPS Bolus Progress Silent</string>
<string name="bolus_progress_channel_description">Bolus progress and cancel</string> <string name="bolus_progress_channel_description">Bolus progress and cancel</string>
<string name="bolus_progress_silent_channel_description">Bolus progress and cancel with less vibrations</string> <string name="bolus_progress_silent_channel_description">Bolus progress and cancel with less vibrations</string>
<string name="key_quickwizard" translatable="false">QuickWizard</string>
<string name="key_wear_control" translatable="false">wearcontrol</string> <string name="key_wear_control" translatable="false">wearcontrol</string>
<string name="key_units_mgdl" translatable="false">units_mgdl</string> <string name="key_units_mgdl" translatable="false">units_mgdl</string>
<string name="key_boluswizard_percentage" translatable="false">boluswizard_percentage</string> <string name="key_boluswizard_percentage" translatable="false">boluswizard_percentage</string>
@ -182,5 +185,6 @@
<string name="tile_no_config">No config available</string> <string name="tile_no_config">No config available</string>
<string name="wear_control_not_enabled">Wear controls disabled</string> <string name="wear_control_not_enabled">Wear controls disabled</string>
<string name="wear_control_no_data">No data available</string> <string name="wear_control_no_data">No data available</string>
<string name="key_quick_wizard_data_map" translatable="false">quick_wizard_data_map</string>
</resources> </resources>